Compare commits

..

134 Commits

Author SHA1 Message Date
thirtyninerealms-cloud
667ac6c6ee
Fix thread leak and graceful shutdown issue in NightConfigWatchThrottler
Fix thread leak and graceful shutdown issue in NightConfigWatchThrottler
2026-06-14 17:30:06 +08:00
thirtyninerealms-cloud
2d760eecbb
Fix thread leak and graceful shutdown issue in NightConfigWatchThrottler
Problem:
- FileSystemWatchService threads accumulate over time (observed 17+ threads)
- Threads cannot be interrupted during container shutdown due to unhandled parkNanos()
- Container fails to stop gracefully, requiring force kill

Root cause:
- LockSupport.parkNanos() called without interruption handling
- No shutdown detection mechanism
- Threads continue polling file system even when JVM is terminating

Changes:
1. Add AtomicBoolean shutdown flag to prevent new watch iterations during shutdown
2. Add proper thread interruption handling with graceful fallback to empty iterator
3. Register shutdown hook to set flag on JVM exit

Testing:
- Verified threads no longer accumulate after multiple config reloads
- Container now responds to SIGTERM and stops within 5 seconds
- CPU usage returns to normal after shutdown sequence
2026-06-14 17:24:20 +08:00
embeddedt
292a6aeab3
Fix optimize_surface_rules breaking mods that provide custom BiomeManagers 2026-06-11 20:01:31 -04:00
embeddedt
7fbfcf1a92
Remove error when missing_block_entities sees null BE
Blocks may legitimately not have a block entity for some states
2026-06-07 21:50:44 -04:00
embeddedt
1bcb28a1ad
Allow feature level requirement to be set at package level 2026-06-07 19:43:28 -04:00
embeddedt
d51b0f60a2
Fix an instance of vanilla leaking a BufferBuilder 2026-06-07 19:19:25 -04:00
embeddedt
ab9880159e
Add experimental KubeJS memory usage optimization 2026-06-06 21:17:34 -04:00
embeddedt
0f94634361
Remove the item stack reference thread 2026-06-06 21:04:09 -04:00
embeddedt
f1492cc829
Allow ZipPackIndex to work with any byte channel 2026-06-04 20:57:13 -04:00
embeddedt
0ecee529d7
Fix Forge calling getResource on every loot table unnecessarily 2026-06-03 18:05:56 -04:00
embeddedt
e9bfd96dd9
Fix Forge pack finder being injected multiple times into pack repository 2026-05-28 22:33:03 -04:00
embeddedt
fb9dcf77c6
Improve ZipPackIndex 2026-05-28 22:20:28 -04:00
embeddedt
33851c1cb6
Fix ImposterProtoChunk leaking live block entities to worldgen 2026-05-24 23:09:52 -04:00
embeddedt
494203ef5a
Fix potential crash during worldgen with release_protochunks enabled
The crash can occur if a protochunk next to a FULL chunk is dropped,
and then later re-requested. If it was not persisted to disk for any
reason, it starts regeneration from scratch. At FEATURES stage, it may
try to place blocks into the adjacent LevelChunk already in the world.

The fix is to prevent this situation from even happening by pinning
protochunks directly next to FULL chunks, and preventing them from
unloading.
2026-05-24 19:45:24 -04:00
embeddedt
74f76f7305
Improvements to ZipPackIndex
- Allow it to work on channels that don't support mapping
- Skip indexing folders that are not part of a pack type
2026-05-23 21:44:14 -04:00
embeddedt
62dbbea083
Optimize ZIP resource packs significantly 2026-05-23 21:28:19 -04:00
embeddedt
538c52bc2a
Run stronghold gen on dedicated thread pool 2026-05-23 17:00:08 -04:00
embeddedt
b62eb1845b
Avoid blocking chunk generation on concentric rings calculation where possible 2026-05-23 16:43:56 -04:00
embeddedt
7c45564979
Fix potential stronghold cache corruption if player exits world too quickly 2026-05-23 16:32:56 -04:00
embeddedt
f8d2425242
Improve accuracy of possible biomes check 2026-05-23 12:50:48 -04:00
embeddedt
50cedfc699
Fix stability level being impossible to override 2026-05-23 12:50:33 -04:00
embeddedt
f4f596ca0c
Fix mixin failing at runtime due to missing AT 2026-05-23 12:50:21 -04:00
embeddedt
85aab426c5
Fix mixin AP complaints 2026-05-23 12:01:33 -04:00
embeddedt
29ff5f152e
Log the state of each mixin at DEBUG level 2026-05-23 11:58:36 -04:00
embeddedt
8213a720a3
Optimize TerraBlender using extended surface biome context
Supersedes TerraBlenderFix
2026-05-23 11:56:45 -04:00
embeddedt
afe3e09a27
Add feature level system for mixins 2026-05-23 11:51:11 -04:00
embeddedt
ae20fa17c9
Fix random CMEs from NightConfigWatchThrottler 2026-05-18 10:05:23 -04:00
embeddedt
a6c03e9928
Rewrite biome condition optimizer inspired by 26.2 changes
Thanks to https://codeberg.org/ZenXArch for making me aware of the
simpler vanilla approach to achieve the same thing
2026-05-16 13:59:01 -04:00
embeddedt
864c751aea
Remove stream in hot path of capability provider construction 2026-05-15 21:14:26 -04:00
embeddedt
f931d5c442
Fix isOptionEnabled being invoked in hot path during capability provider creation
Fixes #664
2026-05-15 21:04:40 -04:00
embeddedt
55cec86e5f
Disable mixin.perf.faster_ingredients with Prefab installed
Prefab relies on the nullity of `Ingredient.itemStacks` matching
vanilla, which is not true with this option enabled

aa5386c78b/Shared/src/com/prefab/recipe/ConditionedShapedRecipe.java (L166)

Fixes #660
2026-05-07 21:48:04 -04:00
embeddedt
4ec8ef753a
Fix scripts not detecting 26.1 branch 2026-05-06 18:19:17 -04:00
embeddedt
3f22e23565
Further optimize OptimizedBiomeLookupSequenceRule 2026-05-06 18:18:14 -04:00
embeddedt
a73dd5ef6a
Update bug report template 2026-05-05 20:27:55 -04:00
embeddedt
653a477060
Fix crash when mods use null attributes
Fixes #658
2026-05-05 20:23:06 -04:00
embeddedt
44113d2536
Improve efficiency of surface rule optimizer when rules are complex 2026-05-05 19:41:28 -04:00
embeddedt
1165d3bdd1
Fix Crash Assistant treating a mixin audit as a crash 2026-04-29 18:47:02 -04:00
embeddedt
c73cdc49a4
Replace CapabilityProvider mixin with ASM transformer
Works around this Mixin bug: https://github.com/FabricMC/Mixin/issues/146

Since CapabilityProvider is the parent of many commonly targeted classes
like Level, ItemStack, etc., this breaks mods

Fixes #650
2026-04-28 18:59:09 -04:00
embeddedt
4e3ecf9b6d
Disable mixin.perf.release_protochunks when Moonrise is present
Fixes #652
2026-04-27 19:52:08 -04:00
embeddedt
a40363c1fb
Improve issue comment workflow [skip ci] 2026-04-22 19:42:45 -04:00
embeddedt
46dd5ecddd
Comment on issues when fix is released
Fixes #649
2026-04-22 19:36:59 -04:00
embeddedt
b765bcb51f
Improve compatibility with mods that inject into ModelBaker.bake
Fixes #646
2026-04-22 19:27:07 -04:00
Mustafa
26bd7116a1
Change log level from warn to debug for successfully created missing block entities
Closes #648
2026-04-22 19:05:18 -04:00
Mustafa
4d2f0da1fc
Reduce log level of mixin.perf.spam_thread_dump to info
Closes #647
2026-04-22 18:48:47 -04:00
embeddedt
c2f585da95
Fix rare crash from HandshakeHandler in 5.27.0+
The existing Forge logic can concurrently modify sentMessages from two threads,
since handleIndexedMessage runs on the Netty thread, while tickServer is on the
server thread. Ticking the handler faster made the race condition significantly
more likely to manifest.
2026-04-14 22:22:06 -04:00
Evoloxi
327c3cd9ff
Fall back to interfaces when resolving capability fields (#643) 2026-04-13 20:32:01 -04:00
embeddedt
c64ca2e54b
Fix potential crash with mods that inject custom surface building logic
Fixes #638
2026-04-12 16:36:44 -04:00
embeddedt
85955ebf75
Ensure integrated server is ticked at least once before player connects
Fixes #639
2026-04-12 16:02:54 -04:00
embeddedt
d749205427
Adjust dynamic_languages for better mod compatibility 2026-04-11 14:39:36 -04:00
embeddedt
438ceb1984
Move auditing to happen later in launch 2026-04-11 14:19:22 -04:00
embeddedt
5acb5115b9
Add mixin audit to CI 2026-04-11 14:14:53 -04:00
embeddedt
37dc9e60eb
Do not intern AttributeSuppliers after launch 2026-04-11 14:04:37 -04:00
embeddedt
c2191df359
Release 5.27.0 & enable continuous deployment 2026-04-10 21:07:34 -04:00
embeddedt
d08da1b3c8
Disable release_protochunks when C2ME is installed 2026-03-29 19:46:04 -04:00
embeddedt
36f425b8cd
Fix excessive recursion from mailbox 2026-03-28 22:07:59 -04:00
embeddedt
dc3c379049
Fix ChunkBiomeLookup leaking a worldgen region 2026-03-28 21:45:59 -04:00
embeddedt
4ff7d4c554
Allow a single low-priority worker thread when cause_lag_by_disabling_threads is enabled
On a system with few cores, we should still benefit from using one low-priority
background thread for worldgen, because it avoids the server thread stopping
to handle it itself. The thread will be blocked
from progressing while higher-priority work (e.g. rendering or server ticking)
is in progress.
2026-03-28 21:45:14 -04:00
embeddedt
db13f39b30
Implement dynamic language loading 2026-03-28 20:55:27 -04:00
embeddedt
5a9c49f8d4
Add option to reduce memory usage of entity models 2026-03-28 20:02:30 -04:00
embeddedt
8ee85f2c16
Remove duplicate list held by DebugLevelSource 2026-03-28 19:31:24 -04:00
embeddedt
2081b63b56
Fix looking up private static final Capability fields 2026-03-27 22:38:18 -04:00
embeddedt
94f1fbf4db
Rewrite AttachCapabilitiesEvent hoisting to not rely on phases 2026-03-27 21:18:38 -04:00
embeddedt
ab8a8068e0
Avoid synchronizing layer list in LivingEntityRenderer 2026-03-26 22:58:18 -04:00
embeddedt
79d2b28d5b
Fix Forge handshake taking extremely long time with many payloads 2026-03-19 21:25:37 -04:00
embeddedt
18dc488ab9
Avoid spinning in Minecraft.doWorldLoad 2026-03-19 20:36:07 -04:00
embeddedt
a9340b2642
Rewrite and improve mixin.perf.cache_strongholds 2026-03-19 20:11:11 -04:00
embeddedt
670e06816b
Reduce work done while waiting for singleplayer client to initiate connection 2026-03-16 22:15:44 -04:00
embeddedt
53349cbd1a
Remove skip_redundant_saves 2026-03-16 22:14:35 -04:00
embeddedt
1794c81b61
Optimize sequence rules that check many biome conditions in a row 2026-03-15 15:24:54 -04:00
embeddedt
dbe9acb3d8
Heavily optimize the BlockColumn impl used during surface rule evaluation 2026-03-14 22:05:36 -04:00
embeddedt
22915a91a1
Implement a significantly more optimized biome lookup for surface rules 2026-03-14 19:44:42 -04:00
embeddedt
1289897004
Add worldgen benchmarking harness 2026-03-14 18:46:32 -04:00
embeddedt
9692da12b4
Add idle timer to prevent chunks from suspending too quickly 2026-03-14 15:59:52 -04:00
embeddedt
e34a99b38c
Simplify chunk unload logic & fix events not being fired when INACCESSIBLE chunks are unloaded 2026-03-14 14:59:45 -04:00
embeddedt
f79eae8b83
Make integrated server treat game as paused while singleplayer client is still loading 2026-03-14 10:44:04 -04:00
embeddedt
38288d5e6a
Automatically free contents of ChunkHolders only used for worldgen when generation finishes 2026-03-13 22:26:51 -04:00
embeddedt
2050516bf1
Do not cache supported glyphs in lazy provider 2026-03-13 19:53:33 -04:00
embeddedt
02f486ebf4
Avoid loading multiple copies of a lazy glyph provider 2026-03-13 19:36:15 -04:00
embeddedt
9edce9ad91
Dynamically load/unload Unihex font data 2026-03-06 20:52:26 -05:00
embeddedt
ac8d93d5b9
Ensure exceptions thrown in chunk load events are not dropped 2026-03-06 09:00:28 -05:00
embeddedt
bee4536c1a
Tweak full chunk promotion to reduce opportunities for deadlocks 2026-03-05 21:09:33 -05:00
embeddedt
da2206168b
Port AP to Java 17 2026-03-04 19:18:01 -05:00
embeddedt
17f930ea6f
WIP chunk saving optimization 2026-03-04 18:41:28 -05:00
embeddedt
f23348c6cb
Clear unneeded ObjectHolderRefs 2026-03-01 19:28:52 -05:00
embeddedt
21cbcb0e04
Strip signatures from jar manifests at startup to save memory 2026-03-01 17:52:13 -05:00
embeddedt
925c7526ee
Reduce memory usage of ImposterProtoChunks 2026-03-01 15:46:52 -05:00
embeddedt
30e3deb8e2
Avoid unnecessary chunkloads when remove_spawn_chunks is enabled 2026-03-01 15:18:13 -05:00
embeddedt
ee34dcf96e
Drastically simplify and document chunk system memory usage patch 2026-02-28 16:42:42 -05:00
embeddedt
49d800ff27
Avoid calling LazyOptional.isPresent() if possible 2026-02-27 22:19:04 -05:00
embeddedt
15f30b532c
Reduce generated class size slightly 2026-02-27 21:30:35 -05:00
embeddedt
df06010846
Fix superclass capability types being ignored sometimes 2026-02-27 20:53:40 -05:00
embeddedt
696b344ef5
Fix missed detection of certain cap equality checks 2026-02-27 20:35:58 -05:00
embeddedt
e63d99763e
Avoid initializing lazy capability providers for compatibility checks where possible 2026-02-27 19:29:16 -05:00
embeddedt
60850610f9
Group capability providers of known types together when possible 2026-02-27 19:11:24 -05:00
embeddedt
e16179b797
Emit more debug info to the generated dispatcher classes 2026-02-27 19:08:06 -05:00
embeddedt
784b914a43
Optimize runs of ICapabilityProvider calls into hash lookups 2026-02-26 22:26:57 -05:00
embeddedt
b9933b1158
Add bytecode analysis to filter ICapabilityProvider impls where possible
Currently disabled by default till more testing is completed
2026-02-26 21:45:31 -05:00
embeddedt
878b3798f3
Detect mods causing CMEs with the client resource reload listener list
Related: #512
2026-02-05 21:10:39 -05:00
embeddedt
bc0e9a09fc
Prevent model locations added in RegisterAdditional from being early baked 2026-02-02 21:29:14 -05:00
embeddedt
8c34c0de50
Dump stats on permanently loaded baked models to debug log 2026-02-02 20:50:21 -05:00
embeddedt
5a93bc6109
Use identityHashCode for attribute 2026-01-25 21:31:06 -05:00
embeddedt
8125da7882
Avoid propagating unbaked model load errors to higher-level code
Related: #625
2026-01-25 21:28:23 -05:00
embeddedt
d699187006
Fix AttachCapabilitiesEvent dispatch being very slow
EventBus strikes again...
2026-01-25 20:38:18 -05:00
embeddedt
cff29149db
Intern map keys in BlockStateData 2026-01-25 19:41:29 -05:00
embeddedt
3926f27d33
Optimize memory usage of entity attribute templates 2026-01-25 19:27:27 -05:00
embeddedt
9bc5f06a19
Ensure correct order of properties in generated ModelResourceLocation variant strings
Related: https://github.com/malte0811/FerriteCore/issues/219
2026-01-24 10:41:23 -05:00
embeddedt
a70f76a34d
Document the reason for lack of optimization 2026-01-23 20:20:48 -05:00
embeddedt
4dcdf09a01
Do not convert ModFileScanData annotation values to immutable lists
Related: #627
2026-01-23 20:19:28 -05:00
embeddedt
f26d35070e
Remove changelog step from release workflow [skip ci] 2026-01-22 20:01:26 -05:00
embeddedt
a04266df54
Fix bugs in release process 2026-01-22 19:47:26 -05:00
embeddedt
2ec6a6afbc
Fix error running publishMods 2026-01-22 19:39:49 -05:00
embeddedt
49f5b527db
Add JVM argument to help prevent mass Spark profile uploads 2026-01-22 19:03:01 -05:00
embeddedt
00287612de
Reimplement publish task 2026-01-17 21:20:21 -05:00
embeddedt
4b18cc2cc6
Fix crash when user home/config folders are inaccessible 2026-01-09 21:05:49 -05:00
embeddedt
b2ed5b9341
Adjust mixin for CIT Reforged compatibility
Related: https://github.com/embeddedt/ModernFix/issues/624
2026-01-09 21:05:06 -05:00
embeddedt
a30dd08cd1
Optimize memory usage of ModFileScanData 2026-01-06 21:58:21 -05:00
embeddedt
7420a7c7ab
Dispatch getCapability calls using specialized ASM loop per provider types
Idea suggested by @eigenraven
2026-01-01 13:09:17 -05:00
embeddedt
3f9148fa62
Update to Gradle 9.2.1 2025-12-27 18:27:09 -05:00
embeddedt
8cc41fa222
Delete old Gradle files and resolve deprecation warning 2025-12-27 18:26:15 -05:00
embeddedt
f06fb8c32e
Fix several issues running production jar 2025-12-26 18:29:35 -05:00
embeddedt
6ee15122f9
Add jar copying tasks 2025-12-26 18:20:35 -05:00
embeddedt
c9843e08bd
Remove Loom cache [skip ci] 2025-12-26 18:16:41 -05:00
embeddedt
7b47c39e6b
Update scripts to use root folder 2025-12-26 18:15:21 -05:00
embeddedt
b26ab375b5
Merge common & forge projects, replace Arch Loom with MDG 2025-12-26 18:13:42 -05:00
embeddedt
9c4da7fa68
Remove Fabric subproject 2025-12-26 16:26:58 -05:00
embeddedt
d64a1c760b
Fix compile errors 2025-12-26 16:16:15 -05:00
embeddedt
555213714f
Delete more old code 2025-12-26 16:10:07 -05:00
embeddedt
7f27141a16
Remove more old code 2025-12-26 15:53:26 -05:00
embeddedt
a8227a964d
Remove deduplicate_climate_parameters, not worth the overhead
Also remove some other misc classes
2025-12-26 15:49:44 -05:00
embeddedt
a7a9aac23a
Begin cleaning up a large volume of code 2025-12-26 15:37:32 -05:00
embeddedt
1176cc98e3
Compact NBT used for Flattening world upgrades 2025-12-26 14:02:42 -05:00
embeddedt
a7622f58ae
Run formatter 2025-12-07 20:36:07 -05:00
embeddedt
67814db6ad
Mitigate dynres cache thrashing for blocks with more than 10,000 states
The previous implementation kept trying to load the models for all
states at once in this scenario, which would cause thrashing as
most of the loads would be thrown out. The new implementation
limits how many models it will load at a time, but still tries
to batch as much as possible
2025-12-07 20:26:42 -05:00
embeddedt
8959c2ff91
Remove dynamic_sounds
This option is unlikely to offer substantial memory savings,
especially when considering that sounds are located off-heap
(meaning the OS should be able to effectively swap out the
unreferenced sound pages to disk). It does not seem worth
the effort to hack this into a game not designed for sounds
to unload at runtime.
2025-11-08 11:49:58 -05:00
477 changed files with 11160 additions and 5996 deletions

View File

@ -4,51 +4,75 @@ body:
- type: markdown
attributes:
value: >-
**Note: This issue tracker is not intended for support requests!** If you need help with crashes or other issues, then
you should [ask on our Discord server](https://discord.gg/rN9Y7caguP) instead. Unless you are certain that you
have found a defect, and you are able to point to where the problem is, you should not open an issue.
<br><br>
Additionally, please make sure you have done the following:
**Need help?** Ask on [Discord](https://discord.gg/rN9Y7caguP) instead of opening an issue.
- **Have you ensured that all of your mods (including ModernFix) are up-to-date?** The latest version of ModernFix
can always be found [on Modrinth](https://modrinth.com/mod/modernfix).
- **Have you used the [search tool](https://github.com/embeddedt/ModernFix/issues) to check whether your issue
has already been reported?** If it has been, then consider adding more information to the existing issue instead.
- **Have you determined the minimum set of instructions to reproduce the issue?** If your problem only occurs
with other mods installed, then you should narrow down exactly which mods are causing the issue. Please do not
provide your entire list of mods to us and expect that we will be able to figure out the problem.
**Issues that do not meet the requirements below (or are otherwise impossible to address with the given info) will be closed without investigation.**
- type: checkboxes
id: confirmations
attributes:
label: Checklist
options:
- label: I am reporting a defect, not asking for help
required: true
- label: I have searched existing issues and this has not been reported
required: true
- label: I have reduced my mod list to the minimum required to reproduce this issue (see below)
required: true
- type: textarea
id: description
attributes:
label: Bug Description
description: >-
Use this section to describe the issue you are experiencing in as much depth as possible. The description should
explain what behavior you were expecting, and why you believe the issue to be a bug. If the issue you are reporting
only occurs with specific mods installed, then provide the name and version of each mod.
Describe the issue in detail. Be sure to include what you expected to happen and what actually happened.
validations:
required: true
- type: textarea
id: minimal-mods
attributes:
label: Minimal Mod List
description: >-
List ONLY the mods required to reproduce this issue. Maintainers have debugging tools that help them
locate problems quickly, but these generally don't work well in modpacks or large mod sets.
A minimal list should typically contain fewer than 10 mods.
**Hint:** If you have any screenshots, videos, or other information that you feel is necessary to
explain the issue, you can attach them here.
Reports with large mod lists will likely be closed without investigation, unless the problem is very clear.
If you don't know which mods are causing your problem, use binary search:
1. Remove half your mods
2. Test if the issue still occurs
3. If yes, remove half again. If no, restore the last removed half and repeat from step 1.
4. Repeat until only the necessary mods remain
placeholder: "- ModernFix 5.x.x\n- SomeMod 1.2.3"
validations:
required: true
- type: textarea
id: description-reproduction-steps
attributes:
label: Reproduction Steps
description: >-
Provide as much information as possible on how to reproduce this bug. Make sure your instructions are as clear and
concise as possible, because other people will need to be able to follow your guide in order to re-create the issue.
**Hint:** A common way to fill this section out is to write a step-by-step guide.
Provide clear steps to reproduce the bug. Each step should be a single concrete action.
Maintainers are busy and need to be able to quickly replicate your problem. Your reproduction steps should be
clear enough for someone who is unfamiliar with your mods to follow in 5 minutes or less (not counting time
to launch the game).
Providing vague steps is likely to result in the issue being closed.
placeholder: "1. \n2. \n3. "
validations:
required: true
- type: textarea
id: log-file
id: diagnostic-info
attributes:
label: Log File
label: Diagnostic Info
description: >-
**Hint:** You can usually find the log files within the folder `.minecraft/logs`. Most often, you will want the `latest.log`
file, since that file belongs to the last played session of the game.
placeholder: >-
Drag-and-drop the log file here.
Drag and drop `latest.log` from `.minecraft/logs/` for the session where the issue occurred.
Do not paste log text inline. Issues without a valid `latest.log` will be closed.
If a crash occurred, also attach the relevant file from `.minecraft/crash-reports/`.
validations:
required: true

View File

@ -11,6 +11,11 @@ on:
jobs:
build:
runs-on: ubuntu-22.04
permissions:
issues: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
steps:
- name: Checkout Repository
uses: actions/checkout@v4
@ -22,20 +27,108 @@ jobs:
distribution: 'temurin'
java-version: 21
check-latest: true
- name: Check if release branch
id: check_branch
if: github.event_name == 'push'
run: |
if [[ "${{ github.ref }}" =~ ^refs/heads/[0-9]+\. ]]; then
echo "is_release=true" >> $GITHUB_OUTPUT
else
echo "is_release=false" >> $GITHUB_OUTPUT
fi
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/1.') }}
cache-read-only: ${{ steps.check_branch.outputs.is_release != 'true' }}
gradle-home-cache-cleanup: true
- name: Setup project Loom cache
uses: actions/cache@v4
with:
path: |
.gradle/loom-cache
key: ${{ runner.os }}-gradle-${{ hashFiles('**/gradle.properties', '**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: ${{ runner.os }}-gradle-
- name: Remove tags for release on other versions
if: steps.check_branch.outputs.is_release == 'true'
run: ./scripts/tagcleaner.sh
- name: Build ModernFix using Gradle
run: ./gradlew build
- name: Run mixin audit
run: timeout 60 xvfb-run ./gradlew runAuditClient
- name: Publish mod to CurseForge & Modrinth
if: steps.check_branch.outputs.is_release == 'true'
run: ./gradlew publishMods copyJarToBin
env:
CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }}
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
- name: Capture mod version
if: steps.check_branch.outputs.is_release == 'true'
run: |
echo "MOD_VERSION=$(./gradlew properties -q | grep '^version:' | awk '{print $2}')" >> $GITHUB_ENV
echo "MC_VERSION=$(grep '^minecraft_version=' gradle.properties | cut -d= -f2)" >> $GITHUB_ENV
- name: Comment on fixed issues
if: steps.check_branch.outputs.is_release == 'true'
uses: actions/github-script@v7
with:
script: |
const { execSync } = require('child_process');
const branch = context.ref.replace('refs/heads/', '');
const { data: runs } = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'gradle.yml',
branch,
status: 'success',
per_page: 1
});
const logArgs = runs.workflow_runs.length > 0
? `${runs.workflow_runs[0].head_sha}..${context.sha}`
: `-1 ${context.sha}`;
const log = execSync(`git log ${logArgs} --format=%s%n%b`, { encoding: 'utf8' });
const issueNumbers = new Set();
const pattern = /(?:fix(?:es|ed)?|close[sd]?|resolve[sd]?)\s+#(\d+)/gi;
let match;
while ((match = pattern.exec(log)) !== null) {
issueNumbers.add(parseInt(match[1]));
}
if (issueNumbers.size === 0) {
console.log('No fixed issues found in commits');
return;
}
const MARKER = '<!-- modernfix-fix-tracker -->';
const modVersion = process.env.MOD_VERSION;
const mcVersion = process.env.MC_VERSION;
const newLine = `- ${modVersion} for Minecraft ${mcVersion}`;
for (const issueNumber of issueNumbers) {
try {
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
per_page: 100
});
const existing = comments.find(c => c.body.includes(MARKER));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: existing.body + `\n${newLine}`
});
console.log(`Updated comment on issue #${issueNumber}`);
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: `${MARKER}\nThe fix for this issue has been released in the following versions of ModernFix:\n${newLine}`
});
console.log(`Created comment on issue #${issueNumber}`);
}
} catch (e) {
console.log(`Could not comment on #${issueNumber}: ${e.message}`);
}
}
- name: Upload Artifacts to GitHub
uses: actions/upload-artifact@v4
with:

View File

@ -1,41 +0,0 @@
name: Release ModernFix Artifacts
on:
release:
types:
- published
jobs:
release:
if: github.repository_owner == 'embeddedt'
runs-on: ubuntu-22.04
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 21
check-latest: true
- name: Remove tags for release on other versions
run: ./scripts/tagcleaner.sh
- name: Build and publish mod to CurseForge & Modrinth
run: ./gradlew publishToModSites copyJarToBin
env:
CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }}
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
- name: Upload assets to GitHub
uses: AButler/upload-release-assets@v3.0
with:
files: 'bin/*'
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Add changelog to release
uses: irongut/EditRelease@v1.2.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
id: ${{ github.event.release.id }}
replacebody: true
files: "CHANGELOG.md"

View File

@ -1,7 +1,6 @@
plugins {
id 'com.github.johnrengelman.shadow'
id 'com.gradleup.shadow' version '8.3.9'
id 'java-library'
id 'com.diffplug.spotless'
}
repositories {
@ -31,7 +30,7 @@ dependencies {
}
tasks.withType(JavaCompile) {
options.release = 21
options.release = 17
}
shadowJar {
@ -52,9 +51,4 @@ shadowJar {
include {it.getName() == 'EnvType.class'}
}
spotless {
java {
removeUnusedImports()
}
}
version = '1.1.4'

View File

@ -90,24 +90,19 @@ public class ClientMixinValidator {
}
private boolean targetsClient(Object classTarget) {
return switch (classTarget) {
case TypeElement te ->
isClientMarked(te);
case TypeMirror tm -> {
var el = types.asElement(tm);
yield el != null ? targetsClient(el) : warn("TypeMirror of " + tm);
}
// If you're using a dollar sign in class names you are insane
case String s -> {
var te =
elemUtils.getTypeElement(toSourceString(s.split("\\$")[0]));
yield te != null ? targetsClient(te) : warn(s);
}
default ->
throw new IllegalArgumentException("Unhandled type: "
if (classTarget instanceof TypeElement te) {
return isClientMarked(te);
} else if (classTarget instanceof TypeMirror tm) {
var el = types.asElement(tm);
return el != null ? targetsClient(el) : warn("TypeMirror of " + tm);
} else if (classTarget instanceof String s) {
var te = elemUtils.getTypeElement(toSourceString(s.split("\\$")[0]));
return te != null ? targetsClient(te) : warn(s);
} else {
throw new IllegalArgumentException("Unhandled type: "
+ classTarget.getClass() + "\n" + "Stringified contents: "
+ classTarget.toString());
};
}
}
private boolean isClientMarked(TypeElement te) {
@ -166,7 +161,6 @@ public class ClientMixinValidator {
clzsses = wrappedClzss.stream()
.map(AnnotationValue::getValue)
.filter(o -> o instanceof TypeMirror)
.map(TypeMirror.class::cast)
.collect(Collectors.toSet());

View File

@ -25,7 +25,7 @@ public record MixinConfig(
InjectorOptions injectors, OverwriteOptions overwrites
) {
public MixinConfig(String packageName, List<String> commonMixins, List<String> clientMixins) {
this(true, "0.8", packageName, "org.embeddedt.modernfix.core.ModernFixMixinPlugin", "JAVA_21",
this(true, "0.8", packageName, "org.embeddedt.modernfix.core.ModernFixMixinPlugin", "JAVA_17",
commonMixins, clientMixins, InjectorOptions.DEFAULT, OverwriteOptions.DEFAULT);
}
public record InjectorOptions(int defaultRequire) {

View File

@ -1,6 +0,0 @@
plugins {
id 'modernfix.common-conventions'
id 'java-library'
}
version = '1.1.0'

View File

@ -0,0 +1,10 @@
plugins {
id("java")
}
version = "1.1.0"
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@ -0,0 +1,9 @@
package org.embeddedt.modernfix.annotation;
public enum FeatureLevel {
GA, BETA;
public boolean isAtLeast(FeatureLevel required) {
return this.ordinal() >= required.ordinal();
}
}

View File

@ -0,0 +1,12 @@
package org.embeddedt.modernfix.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE, ElementType.PACKAGE})
public @interface RequiresFeatureLevel {
FeatureLevel value() default FeatureLevel.GA;
}

View File

@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
@Target({ElementType.TYPE, ElementType.PACKAGE})
public @interface RequiresMod {
String value() default "";
}

View File

@ -1,67 +0,0 @@
plugins {
id "architectury-plugin" version "3.4-SNAPSHOT"
id "dev.architectury.loom" version "1.9-SNAPSHOT" apply false
id "maven-publish"
id 'com.matthewprenger.cursegradle' version '1.4.0' apply false
id 'com.palantir.git-version' version '1.0.0'
id 'org.ajoberstar.grgit' version '5.2.0'
id 'se.bjurr.gitchangelog.git-changelog-gradle-plugin' version '1.79.0'
id "com.modrinth.minotaur" version "2.+" apply false
id("com.diffplug.spotless") version "6.25.0" apply false
id 'modernfix.common-conventions' apply false
}
architectury {
minecraft = rootProject.minecraft_version
}
ext.archives_base_name = 'modernfix'
apply plugin: 'modernfix.common-conventions'
tasks.withType(JavaCompile).configureEach {
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
// If Javadoc is generated, this must be specified in that task too.
options.encoding = "UTF-8"
// The Minecraft launcher currently installs Java 8 for users, so your mod probably wants to target Java 8 too
// JDK 9 introduced a new way of specifying this that will make sure no newer classes or methods are used.
// We'll use that if it's available, but otherwise we'll use the older option.
def targetVersion = 8
/*
if (JavaVersion.current().isJava9Compatible()) {
options.release = targetVersion
}
*/
}
tasks.register('generateChangelog', se.bjurr.gitchangelog.plugin.gradle.GitChangelogTask) {
def details = versionDetails();
def theVersionRef
if (details.commitDistance > 0) {
theVersionRef = details.lastTag;
} else {
def secondLastTagCmd = "git describe --abbrev=0 " + details.lastTag + "^"
def secondLastTag = secondLastTagCmd.execute().text.trim()
theVersionRef = secondLastTag;
}
fromRef = theVersionRef
file = new File("${rootDir}/CHANGELOG.md");
templateContent = new File("${rootDir}/gradle/changelog.mustache").getText('UTF-8').replace("[[modernFixVersionRef]]", theVersionRef);
toCommit = "HEAD";
}
tasks.register('checkCleanTag') {
doLast {
def details = versionDetails()
if (!details.isCleanTag || versionDetails().commitDistance != 0) {
throw new GradleException('Not a clean tree.')
}
}
}
println "ModernFix: " + version

210
build.gradle.kts Normal file
View File

@ -0,0 +1,210 @@
plugins {
id("net.neoforged.moddev.legacyforge") version("2.0.134")
id("me.modmuss50.mod-publish-plugin") version("1.1.0")
}
val minecraft_version = rootProject.properties["minecraft_version"].toString()
group = "org.embeddedt"
val gitVersion = providers.of(GitVersionSource::class) {
parameters {
minecraftVersion.set(minecraft_version)
projectDir.set(rootProject.layout.projectDirectory)
}
}
version = gitVersion.get()
base.archivesName = "modernfix-forge"
legacyForge {
enable {
forgeVersion = rootProject.properties["forge_version"].toString()
isDisableRecompilation = System.getenv("CI") == "true"
}
rootProject.properties["parchment_version"]?.let { parchmentVer ->
parchment {
minecraftVersion = minecraft_version
mappingsVersion = parchmentVer.toString()
}
}
runs {
create("client") {
client()
}
create("server") {
server()
}
create("auditClient") {
client()
jvmArguments.addAll("-Dmodernfix.auditAndExit=true", "-Djava.awt.headless=true")
}
}
mods {
create("modernfix") {
sourceSet(sourceSets.main.get())
}
}
}
mixin {
add(sourceSets.main.get(), "modernfix.refmap.json")
config("modernfix-modernfix.mixins.json")
}
tasks.named<Jar>("jar") {
manifest.attributes(mapOf(
"MixinConfigs" to "modernfix-modernfix.mixins.json",
"Specification-Version" to "1",
"Implementation-Title" to project.name,
"Implementation-Version" to version
))
}
java {
val curSourceCompatLevel = JavaVersion.VERSION_17
sourceCompatibility = curSourceCompatLevel
targetCompatibility = curSourceCompatLevel
}
repositories {
exclusiveContent {
forRepository {
maven {
// location of the maven that hosts JEI files
name = "Progwml6 maven"
url = uri("https://dvs1.progwml6.com/files/maven/")
}
}
forRepository {
maven {
name = "ModMaven"
url = uri("https://modmaven.dev")
}
}
filter {
includeGroup("mezz.jei")
}
}
exclusiveContent {
forRepository {
maven("https://cursemaven.com")
}
filter {
includeGroup("curse.maven")
}
}
}
val embed by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
isTransitive = true
}
dependencies {
implementation(project(":annotations"))
embed(project(":annotations"))
"additionalRuntimeClasspath"(project(":annotations"))
annotationProcessor(project(path = ":annotation-processor", configuration = "shadow"))
val mixinextrasVersion = rootProject.properties["mixinextras_version"].toString()
implementation("io.github.llamalad7:mixinextras-common:${mixinextrasVersion}")
annotationProcessor("net.fabricmc:sponge-mixin:0.12.5+mixin.0.8.5")
annotationProcessor("io.github.llamalad7:mixinextras-common:${mixinextrasVersion}")
implementation("io.github.llamalad7:mixinextras-forge:${mixinextrasVersion}")
"jarJar"("io.github.llamalad7:mixinextras-forge:${mixinextrasVersion}")
val jei_version = rootProject.properties["jei_version"].toString()
modCompileOnly("mezz.jei:jei-${minecraft_version}-forge:${jei_version}")
modCompileOnly("curse.maven:spark-361579:${rootProject.properties["spark_version"].toString()}")
modCompileOnly("curse.maven:ctm-267602:${rootProject.properties["ctm_version"].toString()}")
modCompileOnly("curse.maven:ldlib-626676:${rootProject.properties["ldlib_version"].toString()}")
modCompileOnly("curse.maven:supermartijncore-454372:4455391")
modCompileOnly("curse.maven:patchouli-306770:6164575")
modCompileOnly("curse.maven:cofhcore-69162:5374122")
modCompileOnly("curse.maven:resourcefullib-570073:5659871")
modCompileOnly("curse.maven:kubejs-238086:5853326")
modCompileOnly("curse.maven:terrablender-563928:6290448")
}
tasks.named<Jar>("jar") {
from(embed.map { if (it.isDirectory) it else zipTree(it) })
}
// For the AP
tasks.withType<JavaCompile>().configureEach {
if (!name.lowercase().contains("test")) {
options.compilerArgs.addAll(
listOf(
"-ArootProject.name=${rootProject.name}",
"-Aproject.name=${project.name}"
)
)
}
}
sourceSets {
main {
resources.srcDir(
layout.buildDirectory.dir("generated/sources/annotationProcessor/java/main/resources")
)
}
}
tasks.named<ProcessResources>("processResources") {
dependsOn(tasks.named("compileJava"))
inputs.property("version", project.version)
filesMatching("META-INF/mods.toml") {
expand("version" to project.version)
}
}
val finalJarTask = "reobfJar"
tasks.register<Copy>("copyJarNameConsistent") {
from(tasks.named<Jar>(finalJarTask).get().outputs.files)
into(project.file("build/libs"))
rename { _ -> "modernfix-" + project.name + "-latest.jar" }
}
tasks.register<Copy>("copyJarToBin") {
from(tasks.named<Jar>(finalJarTask).get().outputs.files)
into(rootProject.file("bin"))
mustRunAfter(tasks.named("copyJarNameConsistent"))
}
tasks.named("build") {
dependsOn("copyJarToBin", "copyJarNameConsistent")
}
publishMods {
file.set(tasks.named<Jar>(finalJarTask).flatMap { it.archiveFile })
displayName.set(tasks.named<Jar>(finalJarTask).flatMap { it.archiveFileName })
changelog = "Please check the [GitHub wiki](https://github.com/embeddedt/ModernFix/wiki/Changelog) for major changes."
type = STABLE
modLoaders.add("forge")
curseforge {
projectId = "790626"
projectSlug = "modernfix"
accessToken = providers.environmentVariable("CURSEFORGE_TOKEN")
minecraftVersions.add(minecraft_version)
}
modrinth {
projectId = "nmDcB62a"
accessToken = providers.environmentVariable("MODRINTH_TOKEN")
minecraftVersions.add(minecraft_version)
}
}
tasks.named("publishMods") {
dependsOn(finalJarTask)
}

View File

@ -1,3 +0,0 @@
plugins {
id 'groovy-gradle-plugin'
}

View File

@ -0,0 +1,7 @@
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}

View File

@ -1,128 +0,0 @@
plugins {
id 'java'
id 'architectury-plugin'
id 'maven-publish'
id 'com.diffplug.spotless'
}
spotless {
java {
removeUnusedImports()
}
}
architectury {
compileOnly()
}
group = 'org.embeddedt'
// extract base version from tag, generate other metadata ourselves
def details = versionDetails()
def plusIndex = details.lastTag.indexOf("+")
if(plusIndex == -1) {
plusIndex = details.lastTag.length()
}
def baseVersion = details.lastTag.substring(0, plusIndex)
def dirtyMarker = grgit.status().clean ? "" : ".dirty"
def commitHashMarker = details.commitDistance > 0 ? ("." + details.gitHash.substring(0, Math.min(4, details.gitHash.length()))) : ""
def preMarker = (details.commitDistance > 0 || !details.isCleanTag) ? ("-beta." + details.commitDistance) : ""
if(preMarker.length() > 0) {
// bump to next patch release
def versionParts = baseVersion.tokenize(".")
baseVersion = "${versionParts[0]}.${versionParts[1]}.${versionParts[2].toInteger() + 1}"
}
def versionString = "${baseVersion}${preMarker}+mc${minecraft_version}${commitHashMarker}${dirtyMarker}"
version = versionString
archivesBaseName = rootProject.archives_base_name + '-' + project.name
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_21
repositories {
exclusiveContent {
forRepository {
maven {
url "https://modmaven.dev"
}
}
filter {
includeGroup "appeng"
includeGroup "vazkii.patchouli"
includeGroup "mezz.jei"
}
}
exclusiveContent {
forRepository {
maven {
url "https://cursemaven.com"
}
}
filter {
includeGroup "curse.maven"
}
}
exclusiveContent {
forRepository {
maven {
name = 'ParchmentMC'
url = 'https://maven.parchmentmc.org'
}
}
filter {
includeGroup "org.parchmentmc.data"
}
}
exclusiveContent {
forRepository {
maven {
url = 'https://maven.architectury.dev'
}
}
filter {
includeGroup "me.shedaniel"
}
}
exclusiveContent {
forRepository {
maven {
url = 'https://maven.latvian.dev/releases'
}
}
filter {
includeGroup "dev.latvian.mods"
includeGroup "dev.latvian.apps"
}
}
exclusiveContent {
forRepository {
maven {
name = "Fuzs Mod Resources"
url = "https://raw.githubusercontent.com/Fuzss/modresources/main/maven/"
}
}
filter {
includeGroup "fuzs"
}
}
exclusiveContent {
forRepository {
maven {
name = "Fabric maven"
url = "https://maven.fabricmc.net/"
}
}
filter {
includeGroup "net.fabricmc"
}
}
exclusiveContent {
forRepository {
maven {
name = "Mod Menu"
url = "https://maven.terraformersmc.com/releases/"
}
}
filter {
includeGroup "com.terraformersmc"
}
}
}

View File

@ -1,42 +0,0 @@
plugins {
id 'modernfix.common-conventions'
id 'dev.architectury.loom'
}
loom {
silentMojangMappingsLicense()
accessWidenerPath = file("${rootDir}/common/src/main/resources/modernfix.accesswidener")
mixin {
useLegacyMixinAp = true
}
}
dependencies {
minecraft "com.mojang:minecraft:${rootProject.minecraft_version}"
mappings loom.layered() {
officialMojangMappings()
if(rootProject.hasProperty("parchment_version")) {
parchment("org.parchmentmc.data:parchment-${parchment_mc_version}:${parchment_version}@zip")
}
}
implementation project(":annotations")
annotationProcessor project(path: ":annotation-processor", configuration: 'shadow')
}
project.sourceSets {
main.resources.srcDirs += [layout.buildDirectory.dir("generated/sources/annotationProcessor/java/main/resources")]
}
// hack to shut up gradle about the hack to include generated resources
tasks {
processResources {
dependsOn compileJava
}
}
tasks.withType(JavaCompile) {
configure(options) {
if (!name.toLowerCase().contains('test')) {
options.compilerArgs << "-ArootProject.name=${rootProject.name}" << "-Aproject.name=${project.name}"
}
}
}

View File

@ -1,69 +0,0 @@
plugins {
id 'com.matthewprenger.cursegradle'
id 'com.modrinth.minotaur'
}
loom {
mods {
main { // to match the default mod generated for Forge
sourceSet project.sourceSets.main
sourceSet project(':common').sourceSets.main
}
}
runs {
client {
vmArgs "-Xmx1G"
vmArgs "-Xms1G"
property("mixin.debug.export", "true")
}
}
}
def copyJarNameConsistent = tasks.register('copyJarNameConsistent', Copy) {
from remapJar // shortcut for createJar.outputs.files
into project.file("build/libs")
rename { name -> "modernfix-" + project.name + "-latest.jar" }
}
def copyJarToBin = tasks.register('copyJarToBin', Copy) {
from remapJar // shortcut for createJar.outputs.files
into rootProject.file("bin")
mustRunAfter "copyJarNameConsistent"
}
tasks.build.dependsOn(copyJarToBin, copyJarNameConsistent)
def isBeta = project.version.toString().contains("beta")
curseforge {
if (System.getenv("CURSEFORGE_TOKEN") != null) {
apiKey = System.getenv("CURSEFORGE_TOKEN")
project {
id = "790626"
changelog = file("${rootDir}/CHANGELOG.md")
changelogType = "markdown"
releaseType = isBeta ? "beta" : "release"
addGameVersion (project.name.equals("neoforge") ? "NeoForge" : project.name.capitalize())
gameVersionStrings.addAll(supported_minecraft_versions.tokenize(","))
mainArtifact remapJar
}
}
}
modrinth {
token = System.getenv("MODRINTH_TOKEN")
projectId = "modernfix" // This can be the project ID or the slug. Either will work!
versionType = isBeta ? "beta" : "release" // This is the default -- can also be `beta` or `alpha`
uploadFile = remapJar
gameVersions = supported_minecraft_versions.tokenize(",")
loaders = [project.name]
changelog.set(provider { file("${rootDir}/CHANGELOG.md").getText('UTF-8') })
}
tasks.curseforge.dependsOn(rootProject.generateChangelog)
tasks.modrinth.dependsOn(rootProject.generateChangelog)
tasks.register('publishToModSites') {
publishToModSites.dependsOn(tasks.modrinth)
publishToModSites.dependsOn(tasks.curseforge)
}

View File

@ -0,0 +1,61 @@
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.ValueSource
import org.gradle.api.provider.ValueSourceParameters
import org.gradle.process.ExecOperations
import java.io.ByteArrayOutputStream
import java.io.File
import javax.inject.Inject
abstract class GitVersionSource : ValueSource<String, GitVersionSource.Parameters> {
interface Parameters : ValueSourceParameters {
val minecraftVersion: Property<String>
val projectDir: DirectoryProperty
}
@get:Inject
abstract val execOperations: ExecOperations
override fun obtain(): String {
val minecraftVersion = parameters.minecraftVersion.get()
val workDir = parameters.projectDir.get().asFile
val releaseLine = workDir.resolve("release_line.txt").readText().trim()
val patch = try {
// Find the most recent first-parent commit that touched release_line.txt
val lineStartCommit = git(workDir,
"log", "--first-parent",
"-n", "1",
"--format=%H",
"--",
"release_line.txt"
).trim()
if (lineStartCommit.isEmpty()) {
// count all first-parent commits as a safe fallback
git(workDir, "rev-list", "--count", "--first-parent", "HEAD")
.trim().toIntOrNull() ?: 0
} else {
git(workDir, "rev-list", "--count", "--first-parent", "$lineStartCommit..HEAD")
.trim().toIntOrNull() ?: 0
}
} catch (_: Exception) {
// Git is unavailable or this is not a git repository
999
}
return "$releaseLine.$patch+mc$minecraftVersion"
}
private fun git(workDir: File, vararg args: String): String {
val output = ByteArrayOutputStream()
execOperations.exec {
commandLine("git", *args)
standardOutput = output
workingDir(workDir)
}
return output.toString(Charsets.UTF_8)
}
}

View File

@ -1,42 +0,0 @@
plugins {
id "modernfix.mod-common-conventions"
}
architectury {
common(rootProject.enabled_platforms.split(","))
}
dependencies {
// We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies
// Do NOT use other classes from fabric loader
modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
implementation(annotationProcessor("io.github.llamalad7:mixinextras-common:${rootProject.mixinextras_version}"))
modCompileOnly("dev.latvian.mods:kubejs-neoforge:${kubejs_version}") {
transitive = false
}
modApi("dev.latvian.mods:rhino:${rhino_version}") {
transitive = false
}
modCompileOnly "curse.maven:spark-361579:${rootProject.spark_version}"
// Remove the next line if you don't want to depend on the API
// modApi "me.shedaniel:architectury:${rootProject.architectury_version}"
}
// don't need remapped common jar
tasks.named('remapJar') { enabled = false }
publishing {
publications {
mavenCommon(MavenPublication) {
artifactId = rootProject.archives_base_name
from components.java
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
}
}

View File

@ -1,21 +0,0 @@
package org.embeddedt.modernfix;
import com.google.common.cache.CacheLoader;
import org.apache.commons.lang3.tuple.Pair;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FileWalker extends CacheLoader<Pair<Path, Integer>, List<Path>> {
public static final FileWalker INSTANCE = new FileWalker();
@Override
public List<Path> load(Pair<Path, Integer> key) throws Exception {
try(Stream<Path> stream = Files.walk(key.getLeft(), key.getRight())) {
return stream.collect(Collectors.toList());
}
}
}

View File

@ -1,37 +0,0 @@
package org.embeddedt.modernfix.api.entrypoint;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.UnbakedModel;
/**
* Implement this interface in a mod class and add it to "modernfix:integration_v1" in your mod metadata file
* to integrate with ModernFix's features.
*/
public interface ModernFixClientIntegration {
/**
* Called when the dynamic resources status has changed during a model reload so mods know whether to run their
* normal codepath or the dynamic version.
*
* @param enabled whether dynamic resources is enabled
*/
default void onDynamicResourcesStatusChange(boolean enabled) {
}
/**
* Called to allow mods to observe the loading of a baked model and either make changes to it or wrap it with their
* own instance.
*
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
* @param originalModel the original model
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
* with dynamic resources on
* @param textureGetter function to retrieve textures for this model
* @return the model which should actually be loaded for this resource location
*/
default BakedModel onBakedModelLoad(ModelResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery, ModelBakery.TextureGetter textureGetter) {
return originalModel;
}
}

View File

@ -1,62 +0,0 @@
package org.embeddedt.modernfix.blockstate;
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.StateHolder;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
public class FerriteCorePostProcess {
private static final boolean willPostProcess;
private static final MethodHandle theTable, toKeyIndex;
static {
boolean success = true;
MethodHandle table = null, keyIndex = null;
try {
Class<?> fastMap = Class.forName("malte0811.ferritecore.fastmap.FastMap");
Field field = fastMap.getDeclaredField("toKeyIndex");
field.setAccessible(true);
keyIndex = MethodHandles.publicLookup().unreflectSetter(field);
field = StateHolder.class.getDeclaredField("ferritecore_globalTable");
field.setAccessible(true);
table = MethodHandles.publicLookup().unreflectGetter(field);
} catch(ReflectiveOperationException | RuntimeException e) {
e.printStackTrace();
success = false;
}
willPostProcess = success;
theTable = table;
toKeyIndex = keyIndex;
}
private static final Object2IntMap<?> EMPTY_MAP;
static {
Object2IntArrayMap<?> map = new Object2IntArrayMap<>();
map.defaultReturnValue(-1);
EMPTY_MAP = Object2IntMaps.unmodifiable(map);
}
public static <O, S extends StateHolder<O, S>> void postProcess(StateDefinition<O, S> state) {
if(!willPostProcess)
return;
try {
if(state.getProperties().size() == 0) {
for(S holder : state.getPossibleStates()) {
// deduplicate Object2IntMap objects from FerriteCore
// will probably be fixed upstream at some point, but likely not for older versions
Object table = theTable.invoke(holder);
toKeyIndex.invoke(table, EMPTY_MAP);
}
}
} catch(Throwable e) {
e.printStackTrace();
}
}
}

View File

@ -1,78 +0,0 @@
package org.embeddedt.modernfix.chunk;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import org.jetbrains.annotations.Nullable;
public class SafeBlockGetter implements BlockGetter {
private final ServerLevel wrapped;
private final Thread mainThread;
public SafeBlockGetter(ServerLevel wrapped) {
this.wrapped = wrapped;
this.mainThread = Thread.currentThread();
}
public boolean shouldUse() {
return Thread.currentThread() != this.mainThread;
}
@Nullable
private BlockGetter getChunkSafe(BlockPos pos) {
// can safely call getChunkForLighting off-thread
BlockGetter access = this.wrapped.getChunkSource().getChunkForLighting(pos.getX() >> 4, pos.getZ() >> 4);
if(!(access instanceof ChunkAccess))
return null;
ChunkAccess chunk = (ChunkAccess)access;
if(!chunk.getPersistedStatus().isOrAfter(ChunkStatus.FULL))
return null;
return chunk;
}
@Override
public int getMaxBuildHeight() {
return this.wrapped.getMaxBuildHeight();
}
@Override
public int getMaxLightLevel() {
return this.wrapped.getMaxLightLevel();
}
@Override
public int getMinBuildHeight() {
return this.wrapped.getMinBuildHeight();
}
@Override
public int getHeight() {
return this.wrapped.getHeight();
}
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos) {
BlockGetter g = getChunkSafe(pos);
return g == null ? null : g.getBlockEntity(pos);
}
@Override
public BlockState getBlockState(BlockPos pos) {
BlockGetter g = getChunkSafe(pos);
return g == null ? Blocks.AIR.defaultBlockState() : g.getBlockState(pos);
}
@Override
public FluidState getFluidState(BlockPos pos) {
BlockGetter g = getChunkSafe(pos);
return g == null ? Fluids.EMPTY.defaultFluidState() : g.getFluidState(pos);
}
}

View File

@ -1,35 +0,0 @@
package org.embeddedt.modernfix.command;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import org.embeddedt.modernfix.duck.IProfilingServerFunctionManager;
import static net.minecraft.commands.Commands.literal;
public class ModernFixCommands {
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(literal("modernfix")
.then(literal("mcfunctions").requires(source -> source.hasPermission(3))
.executes(context -> {
ServerLevel level = context.getSource().getLevel();
if(level == null) {
context.getSource().sendFailure(Component.literal("Couldn't find server level"));
return 0;
}
if (level.getServer().getFunctions() instanceof IProfilingServerFunctionManager profiler) {
context.getSource().sendSuccess(() -> Component.literal("mcfunction runtime breakdown:"), false);
for(String line : profiler.mfix$getProfilingResults().split("\n")) {
context.getSource().sendSuccess(() -> Component.literal(line), false);
}
return 1;
} else {
context.getSource().sendFailure(Component.literal("ModernFix mcfunction profiling is not enabled on this server."));
return 0;
}
}))
);
}
}

View File

@ -1,22 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockBehaviour;
import org.embeddedt.modernfix.chunk.SafeBlockGetter;
import org.embeddedt.modernfix.duck.ISafeBlockGetter;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
@Mixin(value = BlockBehaviour.BlockStateBase.class, priority = 100)
public class BlockStateBaseMixin {
@ModifyVariable(method = "getOffset", at = @At("HEAD"), argsOnly = true, index = 1)
private BlockGetter useSafeGetter(BlockGetter g) {
if(g instanceof ISafeBlockGetter) {
SafeBlockGetter replacement = ((ISafeBlockGetter) g).mfix$getSafeBlockGetter();
if(replacement.shouldUse())
return replacement;
}
return g;
}
}

View File

@ -1,18 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
import net.minecraft.server.level.ServerLevel;
import org.embeddedt.modernfix.chunk.SafeBlockGetter;
import org.embeddedt.modernfix.duck.ISafeBlockGetter;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(ServerLevel.class)
public class ServerLevelMixin implements ISafeBlockGetter {
@Unique
private final SafeBlockGetter mfix$safeBlockGetter = new SafeBlockGetter((ServerLevel)(Object)this);
@Override
public SafeBlockGetter mfix$getSafeBlockGetter() {
return mfix$safeBlockGetter;
}
}

View File

@ -1,36 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.concurrency;
import net.minecraft.client.Minecraft;
import net.minecraft.util.thread.BlockableEventLoop;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import java.util.function.BooleanSupplier;
@Mixin(Minecraft.class)
@ClientOnlyMixin
public abstract class MinecraftMixin<R extends Runnable> extends BlockableEventLoop<R> {
protected MinecraftMixin(String p_i50403_1_) {
super(p_i50403_1_);
}
@Override
public void managedBlock(BooleanSupplier pIsDone) {
if(!this.isSameThread()) {
ModernFix.LOGGER.warn("A mod is calling Minecraft.managedBlock from the wrong thread. This is most likely related to one of our parallelizations.");
ModernFix.LOGGER.warn("ModernFix will work around this, however ideally the issue should be patched in the other mod.");
ModernFix.LOGGER.warn("Stacktrace", new IllegalThreadStateException());
while(!pIsDone.getAsBoolean()) {
try {
Thread.sleep(100);
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} else {
super.managedBlock(pIsDone);
}
}
}

View File

@ -1,24 +0,0 @@
package org.embeddedt.modernfix.common.mixin.core;
import net.minecraft.Util;
import net.minecraft.server.MinecraftServer;
import org.embeddedt.modernfix.duck.ITimeTrackingServer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(MinecraftServer.class)
public class MinecraftServerMixin implements ITimeTrackingServer {
private long mfix$lastTickStartTime = -1L;
@Override
public long mfix$getLastTickStartTime() {
return mfix$lastTickStartTime;
}
@Inject(method = "runServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;tickServer(Ljava/util/function/BooleanSupplier;)V"))
private void trackTickTime(CallbackInfo ci) {
mfix$lastTickStartTime = Util.getMillis();
}
}

View File

@ -1,22 +0,0 @@
package org.embeddedt.modernfix.common.mixin.feature.direct_stack_trace;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(CrashReport.class)
public class CrashReportMixin {
@Shadow @Final private Throwable exception;
@Inject(method = "addCategory(Ljava/lang/String;I)Lnet/minecraft/CrashReportCategory;", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;error(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V", remap = false))
private void dumpStacktrace(String s, int i, CallbackInfoReturnable<CrashReportCategory> cir) {
new Exception("ModernFix crash stacktrace").printStackTrace();
if(this.exception != null)
this.exception.printStackTrace();
}
}

View File

@ -1,70 +0,0 @@
package org.embeddedt.modernfix.common.mixin.feature.stalled_chunk_load_detection;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.level.ChunkResult;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.chunk.EmptyLevelChunk;
import org.embeddedt.modernfix.ModernFix;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.concurrent.*;
@Mixin(value = ServerChunkCache.class, priority = 1100)
public abstract class ServerChunkCacheMixin {
@Shadow @Final private Thread mainThread;
@Shadow @Final public ServerLevel level;
@Shadow protected abstract CompletableFuture<ChunkResult<ChunkAccess>> getChunkFutureMainThread(int k, int l, ChunkStatus arg, boolean bl);
@Shadow @Final private ServerChunkCache.MainThreadExecutor mainThreadProcessor;
private final boolean debugDeadServerAccess = Boolean.getBoolean("modernfix.debugBadChunkloading");
@Inject(method = "getChunk", at = @At("HEAD"), cancellable = true)
private void bailIfServerDead(int chunkX, int chunkZ, ChunkStatus requiredStatus, boolean load, CallbackInfoReturnable<ChunkAccess> cir) {
if(!this.level.getServer().isRunning() && !this.mainThread.isAlive()) {
ModernFix.LOGGER.fatal("A mod is accessing chunks from a stopped server (this will also cause memory leaks)");
if(debugDeadServerAccess) {
new Exception().printStackTrace();
}
Holder<Biome> plains = this.level.registryAccess().registryOrThrow(Registries.BIOME).getHolderOrThrow(Biomes.PLAINS);
cir.setReturnValue(new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ), plains));
} else if(Thread.currentThread() != this.mainThread) {
CompletableFuture<ChunkResult<ChunkAccess>> future = CompletableFuture.supplyAsync(() -> this.getChunkFutureMainThread(chunkX, chunkZ, requiredStatus, false), this.mainThreadProcessor).join();
if(!future.isDone()) {
// Wait at least 500 milliseconds before printing anything
ChunkResult<ChunkAccess> resultingChunk = null;
try {
resultingChunk = future.get(500, TimeUnit.MILLISECONDS);
} catch(InterruptedException | ExecutionException | TimeoutException ignored) {
}
if(resultingChunk != null && resultingChunk.isSuccess()) {
cir.setReturnValue(resultingChunk.orElse(null));
return;
}
if(debugDeadServerAccess)
ModernFix.LOGGER.warn("Async loading of a chunk was requested, this might not be desirable", new Exception());
try {
resultingChunk = future.get(10, TimeUnit.SECONDS);
if(resultingChunk.isSuccess()) {
cir.setReturnValue(resultingChunk.orElse(null));
return;
}
} catch(InterruptedException | ExecutionException | TimeoutException e) {
ModernFix.LOGGER.error("Async chunk load took way too long, this needs to be reported to the appropriate mod.", e);
}
//cir.setReturnValue(new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ)));
}
}
}
}

View File

@ -1,28 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_model_materials;
import net.minecraft.client.renderer.block.model.multipart.MultiPart;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Collection;
@Mixin(MultiPart.class)
@ClientOnlyMixin
public class MultipartMixin {
private Collection<ResourceLocation> dependencyCache = null;
@Inject(method = "getDependencies", at = @At("HEAD"), cancellable = true)
private void useDependencyCache(CallbackInfoReturnable<Collection<ResourceLocation>> cir) {
if(dependencyCache != null)
cir.setReturnValue(dependencyCache);
}
@Inject(method = "getDependencies", at = @At("RETURN"))
private void storeDependencyCache(CallbackInfoReturnable<Collection<ResourceLocation>> cir) {
if(dependencyCache == null)
dependencyCache = cir.getReturnValue();
}
}

View File

@ -1,68 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
import net.minecraft.Util;
import net.minecraft.core.Holder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.duck.IChunkGenerator;
import org.embeddedt.modernfix.duck.IServerLevel;
import org.embeddedt.modernfix.world.StrongholdLocationCache;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Mixin(ChunkGeneratorStructureState.class)
public class ChunkGeneratorMixin implements IChunkGenerator {
private WeakReference<ServerLevel> mfix$serverLevel;
@Override
public void mfix$setAssociatedServerLevel(ServerLevel level) {
mfix$serverLevel = new WeakReference<>(level);
}
@Inject(method = "generateRingPositions", at = @At("HEAD"), cancellable = true)
private void useCachedDataIfAvailable(Holder<StructureSet> structureSet, ConcentricRingsStructurePlacement placement, CallbackInfoReturnable<CompletableFuture<List<ChunkPos>>> cir) {
if(placement.count() == 0)
return;
ServerLevel level = searchLevel();
if(level == null)
return;
StrongholdLocationCache cache = ((IServerLevel)level).mfix$getStrongholdCache();
List<ChunkPos> positions = cache.getChunkPosList();
if(positions.isEmpty())
return;
ModernFix.LOGGER.debug("Loaded stronghold cache for dimension {} with {} positions", level.dimension().location(), positions.size());
cir.setReturnValue(CompletableFuture.completedFuture(positions));
}
private ServerLevel searchLevel() {
if(mfix$serverLevel != null)
return mfix$serverLevel.get();
else
return null;
}
@Inject(method = "generateRingPositions", at = @At("RETURN"), cancellable = true)
private void saveCachedData(Holder<StructureSet> structureSet, ConcentricRingsStructurePlacement placement, CallbackInfoReturnable<CompletableFuture<List<ChunkPos>>> cir) {
cir.setReturnValue(cir.getReturnValue().thenApplyAsync(list -> {
if(list.size() == 0)
return list;
ServerLevel level = searchLevel();
if(level != null) {
StrongholdLocationCache cache = ((IServerLevel)level).mfix$getStrongholdCache();
cache.setChunkPosList(list);
ModernFix.LOGGER.debug("Saved stronghold cache for dimension {}", level.dimension().location());
}
return list;
}, Util.backgroundExecutor()));
}
}

View File

@ -1,61 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.storage.DimensionDataStorage;
import net.minecraft.world.level.storage.WritableLevelData;
import org.embeddedt.modernfix.duck.IChunkGenerator;
import org.embeddedt.modernfix.duck.IServerLevel;
import org.embeddedt.modernfix.world.StrongholdLocationCache;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.function.Supplier;
@Mixin(ServerLevel.class)
public abstract class ServerLevelMixin extends Level implements IServerLevel {
protected ServerLevelMixin(WritableLevelData arg, ResourceKey<Level> arg2, RegistryAccess arg3, Holder<DimensionType> arg4, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
super(arg, arg2, arg3, arg4, supplier, bl, bl2, l, i);
}
@Shadow public abstract DimensionDataStorage getDataStorage();
@Shadow @Final private ServerChunkCache chunkSource;
private StrongholdLocationCache mfix$strongholdCache;
/**
* Initialize the stronghold cache but don't force any structure generation yet.
*/
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/ChunkGeneratorStructureState;ensureStructuresGenerated()V"))
private void hookStrongholdCache(ChunkGeneratorStructureState generator) {
((IChunkGenerator)generator).mfix$setAssociatedServerLevel((ServerLevel)(Object)this);
}
/**
* Now start the stronghold generation process.
*/
@Inject(method = "<init>", at = @At("TAIL"))
private void ensureGeneration(CallbackInfo ci) {
mfix$strongholdCache = this.getDataStorage().computeIfAbsent(
StrongholdLocationCache.factory((ServerLevel)(Object)this),
StrongholdLocationCache.getFileId(this.dimensionTypeRegistration()));
this.chunkSource.getGeneratorState().ensureStructuresGenerated();
}
@Override
public StrongholdLocationCache mfix$getStrongholdCache() {
return mfix$strongholdCache;
}
}

View File

@ -1,15 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.deduplicate_climate_parameters;
import net.minecraft.world.level.biome.Climate;
import org.embeddedt.modernfix.dedup.ClimateCache;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin({ Climate.Parameter.class, Climate.ParameterPoint.class })
public class ParameterMixin {
@Redirect(method = "*", at = @At(value = "NEW", target = "net/minecraft/world/level/biome/Climate$Parameter"), require = 0)
private static Climate.Parameter internParameterStatic(long min, long max) {
return ClimateCache.MFIX_INTERNER.intern(new Climate.Parameter(min, max));
}
}

View File

@ -1,30 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.deduplicate_location;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.dedup.IdentifierCaches;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ResourceLocation.class)
public class MixinResourceLocation {
@Mutable
@Shadow
@Final
private String namespace;
@Mutable
@Shadow
@Final
private String path;
@Inject(method = "<init>", at = @At("RETURN"))
private void reinit(String string, String string2, CallbackInfo ci) {
this.namespace = IdentifierCaches.NAMESPACES.deduplicate(this.namespace);
this.path = IdentifierCaches.PATH.deduplicate(this.path);
}
}

View File

@ -1,17 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_dfu;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.mojang.datafixers.DataFixerBuilder;
import net.minecraft.util.datafix.DataFixers;
import org.embeddedt.modernfix.dfu.DFUBlaster;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(DataFixers.class)
public class DataFixersMixin {
@ModifyReturnValue(method = "createFixerUpper", at = @At("RETURN"))
private static DataFixerBuilder.Result setupMapBlasting(DataFixerBuilder.Result original) {
DFUBlaster.blastMaps();
return original;
}
}

View File

@ -1,38 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_entity_renderers;
import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.entity.EntityRendererMap;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@Mixin(EntityRenderDispatcher.class)
@ClientOnlyMixin
public class EntityRenderDispatcherMixin {
@Shadow private Map<EntityType<?>, EntityRenderer<?>> renderers;
private EntityRendererMap mfix$dynamicRenderers;
@Inject(method = "getRenderer", at = @At("RETURN"), cancellable = true)
private <T extends Entity> void checkNullness(T entity, CallbackInfoReturnable<EntityRenderer<? super T>> cir) {
// apparently some mods yeet the renderers map and cause issues
if(cir.getReturnValue() == null)
cir.setReturnValue((EntityRenderer<? super T>)mfix$dynamicRenderers.get(entity.getType()));
}
@Redirect(method = "onResourceManagerReload", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/renderer/entity/EntityRenderDispatcher;renderers:Ljava/util/Map;"))
private void setRendererField(EntityRenderDispatcher instance, Map<EntityType<?>, EntityRenderer<?>> incomingMap) {
this.renderers = incomingMap;
this.mfix$dynamicRenderers = (EntityRendererMap)incomingMap;
}
}

View File

@ -1,29 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_entity_renderers;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.EntityRenderers;
import net.minecraft.world.entity.EntityType;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.entity.EntityRendererMap;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@Mixin(EntityRenderers.class)
@ClientOnlyMixin
public class EntityRenderersMixin {
@Shadow @Final private static Map<EntityType<?>, EntityRendererProvider<?>> PROVIDERS;
@Inject(method = "createEntityRenderers", at = @At("HEAD"), cancellable = true)
private static void createDynamicRendererLoader(EntityRendererProvider.Context context, CallbackInfoReturnable<Map<EntityType<?>, EntityRenderer<?>>> cir) {
cir.setReturnValue(new EntityRendererMap(PROVIDERS, context));
ModernFix.LOGGER.info("Dynamic entity renderer hook setup");
}
}

View File

@ -1,117 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableList;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
import it.unimi.dsi.fastutil.objects.ReferenceObjectImmutablePair;
import net.minecraft.client.color.block.BlockColors;
import net.minecraft.client.renderer.block.model.BlockModelDefinition;
import net.minecraft.client.resources.model.BlockStateModelLoader;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.core.DefaultedRegistry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.IBlockStateModelLoader;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
@Mixin(BlockStateModelLoader.class)
@ClientOnlyMixin
public abstract class BlockStateModelLoaderMixin implements IBlockStateModelLoader {
@Shadow protected abstract void loadBlockStateDefinitions(ResourceLocation resourceLocation, StateDefinition<Block, BlockState> stateDefinition);
@Shadow @Mutable @Final private Object2IntMap<BlockState> modelGroups;
private ImmutableList<BlockState> filteredStates;
@Inject(method = "<init>", at = @At("RETURN"))
private void makeModelGroupsSynchronized(Map map, ProfilerFiller profilerFiller, UnbakedModel unbakedModel, BlockColors blockColors, BiConsumer biConsumer, CallbackInfo ci) {
this.modelGroups = Object2IntMaps.synchronize(this.modelGroups);
}
@Override
public void loadSpecificBlock(ModelResourceLocation location) {
var optionalBlock = BuiltInRegistries.BLOCK.getOptional(location.id());
if(optionalBlock.isPresent()) {
// embeddedt note - filtering is currently disabled as it's quite inefficient to do vs. just loading
// the extra models and letting LRU deal with it
/*
try {
// Only filter states if we are in a world and not in the loading overlay
filteredStates = (Minecraft.getInstance().getOverlay() == null && Minecraft.getInstance().level != null) ? ModelBakeryHelpers.getBlockStatesForMRL(optionalBlock.get().getStateDefinition(), location) : null;
} catch(RuntimeException e) {
ModernFix.LOGGER.error("Exception filtering states on {}", location, e);
filteredStates = null;
}
*/
try {
this.loadBlockStateDefinitions(location.id(), optionalBlock.get().getStateDefinition());
} finally {
filteredStates = null;
}
}
}
@Redirect(method = "loadAllBlockStates", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/DefaultedRegistry;iterator()Ljava/util/Iterator;"))
private Iterator<?> skipIteratingBlocks(DefaultedRegistry instance) {
return Collections.emptyIterator();
}
@Redirect(method = "loadBlockStateDefinitions", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/StateDefinition;getPossibleStates()Lcom/google/common/collect/ImmutableList;"))
private ImmutableList<BlockState> getFilteredStates(StateDefinition<Block, BlockState> instance) {
return this.filteredStates != null ? this.filteredStates : instance.getPossibleStates();
}
// Add some caching around key hot paths
private final Cache<ReferenceObjectImmutablePair<BlockStateModelLoader.LoadedJson, ResourceLocation>, BlockModelDefinition> cachedBlockModelDefs = CacheBuilder.newBuilder()
.maximumSize(100)
.build();
private static final Cache<Pair<StateDefinition<Block, BlockState>, String>, Predicate<BlockState>> cachedBlockStatePredicates = CacheBuilder.newBuilder()
.maximumSize(100)
.build();
@WrapOperation(method = "loadBlockStateDefinitions", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/BlockStateModelLoader$LoadedJson;parse(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/renderer/block/model/BlockModelDefinition$Context;)Lnet/minecraft/client/renderer/block/model/BlockModelDefinition;"))
private BlockModelDefinition avoidMultipleParses(BlockStateModelLoader.LoadedJson instance, ResourceLocation blockStateId, BlockModelDefinition.Context context, Operation<BlockModelDefinition> original) {
try {
return cachedBlockModelDefs.get(ReferenceObjectImmutablePair.of(instance, blockStateId), () -> original.call(instance, blockStateId, context));
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
@WrapMethod(method = "predicate")
private static Predicate<BlockState> memoizePredicate(StateDefinition<Block, BlockState> stateDefentition, String properties, Operation<Predicate<BlockState>> original) {
try {
return cachedBlockStatePredicates.get(Pair.of(stateDefentition, properties), () -> original.call(stateDefentition, properties));
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -1,22 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.ModelManager;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.IExtendedModelManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Minecraft.class)
@ClientOnlyMixin
public abstract class MinecraftMixin_ModelTicking {
@Shadow public abstract ModelManager getModelManager();
@Inject(method = "tick", at = @At(value = "RETURN"))
private void tickModels(CallbackInfo ci) {
((IExtendedModelManager)this.getModelManager()).mfix$tick();
}
}

View File

@ -1,57 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(ModelBakery.ModelBakerImpl.class)
@ClientOnlyMixin
public abstract class ModelBakerImplMixin {
@Shadow public abstract UnbakedModel getModel(ResourceLocation location);
@Shadow(aliases = {"this$0"}) @Final private ModelBakery field_40571;
@Unique
private int mfix$getDepth = 0;
/**
* @author embeddedt
* @reason force parent resolution to happen before model gets baked
*/
@ModifyReturnValue(method = "getModel", at = @At("RETURN"))
private UnbakedModel resolveParents(UnbakedModel model) {
mfix$getDepth++;
if(mfix$getDepth == 1) {
try {
model.resolveParents(this::getModel);
} catch(Exception e) {
ModernFix.LOGGER.warn("Exception encountered resolving parents", e);
}
}
mfix$getDepth--;
return model;
}
@WrapMethod(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;)Lnet/minecraft/client/resources/model/BakedModel;")
private BakedModel mfix$lockWhenBaking(ResourceLocation location, ModelState transform, Operation<BakedModel> original) {
var lock = ((IExtendedModelBakery)this.field_40571).mfix$getLock();
lock.lock();
try {
return original.call(location, transform);
} finally {
lock.unlock();
}
}
}

View File

@ -1,274 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.client.color.block.BlockColors;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.BlockModelRotation;
import net.minecraft.client.resources.model.BlockStateModelLoader;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.core.DefaultedRegistry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.profiling.ProfilerFiller;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
import org.embeddedt.modernfix.duck.IBlockStateModelLoader;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
import org.embeddedt.modernfix.util.DynamicOverridableMap;
import org.embeddedt.modernfix.util.LRUMap;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
@Mixin(ModelBakery.class)
@ClientOnlyMixin
public abstract class ModelBakeryMixin implements IExtendedModelBakery {
@Unique
private BlockStateModelLoader dynamicLoader;
@Unique
private final ReentrantLock modelBakeryLock = new ReentrantLock();
@Unique
private ModelBakery.TextureGetter textureGetter;
@Unique
private BakedModel bakedMissingModel;
@Shadow abstract UnbakedModel getModel(ResourceLocation resourceLocation);
@Shadow @Final private UnbakedModel missingModel;
@Unique
private static final boolean DEBUG_MODEL_LOADS = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
/**
* Bake a model using the provided texture getter and location. The model is stored in {@link ModelBakeryMixin#bakedTopLevelModels}.
*/
@Shadow(aliases = "lambda$bakeModels$6") protected abstract void method_61072(ModelBakery.TextureGetter getter, ModelResourceLocation location, UnbakedModel model);
@Shadow @Mutable @Final private Map<ModelResourceLocation, BakedModel> bakedTopLevelModels;
@Shadow @Mutable @Final public Map<ModelResourceLocation, UnbakedModel> topLevelModels;
@Shadow @Mutable @Final private Map<ResourceLocation, UnbakedModel> unbakedCache;
@Shadow @Mutable @Final public Map<ModelBakery.BakedCacheKey, BakedModel> bakedCache;
@Shadow protected abstract void loadItemModelAndDependencies(ResourceLocation resourceLocation);
@Shadow @Final public static ModelResourceLocation MISSING_MODEL_VARIANT;
@Shadow protected abstract void registerModelAndLoadDependencies(ModelResourceLocation modelLocation, UnbakedModel model);
private final Map<ModelResourceLocation, BakedModel> mfix$emulatedBakedRegistry = new DynamicOverridableMap<>(ModelResourceLocation.class, this::loadBakedModelDynamic);
@Override
public UnbakedModel mfix$loadUnbakedModelDynamic(ModelResourceLocation location) {
if(location.equals(MISSING_MODEL_VARIANT)) {
return missingModel;
}
modelBakeryLock.lock();
try {
UnbakedModel existing = this.topLevelModels.get(location);
if (existing != null) {
return existing;
}
if(DEBUG_MODEL_LOADS) {
ModernFix.LOGGER.info("Loading model {}", location);
}
if(location.variant().equals("inventory")) {
this.loadItemModelAndDependencies(location.id());
} else if (location.variant().equals("fabric_resource") || location.variant().equals("standalone")) {
UnbakedModel unbakedModel = this.getModel(location.id());
this.registerModelAndLoadDependencies(location, unbakedModel);
} else {
((IBlockStateModelLoader)dynamicLoader).loadSpecificBlock(location);
}
return this.topLevelModels.getOrDefault(location, this.missingModel);
} finally {
modelBakeryLock.unlock();
}
}
@WrapMethod(method = "getModel")
private UnbakedModel mfix$lockWhenGettingModel(ResourceLocation modelLocation, Operation<UnbakedModel> original) {
modelBakeryLock.lock();
try {
return original.call(modelLocation);
} finally {
modelBakeryLock.unlock();
}
}
@Override
public UnbakedModel mfix$getMissingModel() {
return missingModel;
}
@Unique
private BakedModel loadBakedModelDynamic(ModelResourceLocation location) {
if(location.equals(MISSING_MODEL_VARIANT)) {
return bakedMissingModel;
}
BakedModel model;
modelBakeryLock.lock();
try {
model = bakedTopLevelModels.get(location);
if(model == null) {
UnbakedModel prototype = mfix$loadUnbakedModelDynamic(location);
if(prototype == missingModel) {
model = bakedMissingModel;
} else {
prototype.resolveParents(this::getModel);
if(DEBUG_MODEL_LOADS) {
ModernFix.LOGGER.info("Baking model {}", location);
}
this.method_61072(this.textureGetter, location, prototype);
model = bakedTopLevelModels.remove(location);
if(model == null) {
ModernFix.LOGGER.error("Failed to load model " + location);
model = bakedMissingModel;
}
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
try {
model = integration.onBakedModelLoad(location, prototype, model, BlockModelRotation.X0_Y0, (ModelBakery)(Object)this, this.textureGetter);
} catch (RuntimeException e) {
ModernFix.LOGGER.error("Exception encountered running dynamic resources integration", e);
}
}
}
}
} finally {
modelBakeryLock.unlock();
}
return model;
}
@ModifyExpressionValue(method = "<init>", at = @At(value = "CONSTANT", args = "stringValue=missing_model"))
private String replaceBackingMaps(String original) {
this.unbakedCache = new LRUMap<>(this.unbakedCache);
this.bakedCache = new LRUMap<>(this.bakedCache);
this.topLevelModels = new LRUMap<>(this.topLevelModels);
this.bakedTopLevelModels = new LRUMap<>(this.bakedTopLevelModels);
return original;
}
@WrapOperation(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/BlockStateModelLoader;loadAllBlockStates()V"))
private void noInitialBlockStateLoad(BlockStateModelLoader instance, Operation<Void> original) {
dynamicLoader = instance;
original.call(instance);
}
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/DefaultedRegistry;keySet()Ljava/util/Set;"))
private Set<?> skipLoadingItems(DefaultedRegistry instance) {
return Collections.emptySet();
}
@Inject(method = "bakeModels", at = @At("HEAD"))
private void storeTextureGetterAndBakeMissing(ModelBakery.TextureGetter textureGetter, CallbackInfo ci) {
this.textureGetter = textureGetter;
this.method_61072(textureGetter, MISSING_MODEL_VARIANT, Objects.requireNonNull(this.topLevelModels.get(MISSING_MODEL_VARIANT)));
this.bakedMissingModel = this.bakedTopLevelModels.get(MISSING_MODEL_VARIANT);
}
private boolean inInitialLoad = true;
@Inject(method = "bakeModels", at = @At("RETURN"))
private void onInitialBakeFinish(ModelBakery.TextureGetter textureGetter, CallbackInfo ci) {
var permanentMRLs = new ObjectOpenHashSet<>(this.bakedTopLevelModels.keySet());
((LRUMap<ModelResourceLocation, BakedModel>)this.bakedTopLevelModels).setPermanentEntries(permanentMRLs);
ModernFix.LOGGER.info("Dynamic model bakery initial baking finished, with {} permanent top level baked models", this.bakedTopLevelModels.size());
}
@Inject(method = "<init>", at = @At("RETURN"))
private void onInitialLoadFinish(BlockColors blockColors, ProfilerFiller profilerFiller, Map map, Map map2, CallbackInfo ci) {
var permanentMRLs = new ObjectOpenHashSet<>(this.topLevelModels.keySet());
((LRUMap<ModelResourceLocation, UnbakedModel>)this.topLevelModels).setPermanentEntries(permanentMRLs);
ModernFix.LOGGER.info("Dynamic model bakery loading finished, with {} permanent top level models", this.topLevelModels.size());
}
@Unique
private int tickCount;
@Unique
private static final int MAXIMUM_CACHE_SIZE = 1000;
private void runCleanup() {
try {
((LRUMap<?, ?>)this.unbakedCache).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
} catch(RuntimeException e) {
throw new IllegalStateException("Exception dropping entries in unbaked cache", e);
}
try {
((LRUMap<?, ?>)this.bakedCache).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
} catch(RuntimeException e) {
throw new IllegalStateException("Exception dropping entries in baked cache", e);
}
try {
((LRUMap<?, ?>)this.topLevelModels).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
} catch(RuntimeException e) {
throw new IllegalStateException("Exception dropping entries in top level models", e);
}
try {
((LRUMap<?, ?>)this.bakedTopLevelModels).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
} catch(RuntimeException e) {
throw new IllegalStateException("Exception dropping entries in baked top level models", e);
}
}
@Override
public void mfix$finishLoading() {
inInitialLoad = false;
}
@Override
public void mfix$tick() {
if(inInitialLoad) {
return;
}
tickCount++;
if((tickCount % 200) == 0) {
if(modelBakeryLock.tryLock()) {
try {
runCleanup();
} finally {
modelBakeryLock.unlock();
}
}
}
}
/**
* @author embeddedt
* @reason We provide a fake baked registry to the rest of Minecraft, that dynamically loads models.
*/
@Overwrite
public Map<ModelResourceLocation, BakedModel> getBakedTopLevelModels() {
return this.mfix$emulatedBakedRegistry;
}
@Override
public ReentrantLock mfix$getLock() {
return this.modelBakeryLock;
}
}

View File

@ -1,29 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_sounds;
import com.mojang.blaze3d.audio.SoundBuffer;
import net.minecraft.client.sounds.SoundBufferLibrary;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.dynamicresources.DynamicSoundHelpers;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.concurrent.CompletableFuture;
import java.util.Map;
@Mixin(SoundBufferLibrary.class)
@ClientOnlyMixin
public abstract class SoundBufferLibraryMixin {
@Shadow @Final @Mutable
private Map<ResourceLocation, CompletableFuture<SoundBuffer>> cache;
@Inject(method = "<init>", at = @At("RETURN"))
private void replaceCache(CallbackInfo ci) {
this.cache = new DynamicSoundHelpers.Cache(cache);
}
}

View File

@ -1,36 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_sounds;
import com.mojang.blaze3d.audio.SoundBuffer;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.dynamicresources.DynamicSoundHelpers;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import javax.sound.sampled.AudioFormat;
import java.nio.ByteBuffer;
@ClientOnlyMixin
@Mixin(SoundBuffer.class)
public class SoundBufferMixin implements DynamicSoundHelpers.SoundBufAccess {
@Unique
private long mfix$durationNanos;
@Inject(method = "<init>", at = @At("RETURN"))
private void computeDuration(ByteBuffer data, AudioFormat format, CallbackInfo ci) {
if (data != null) {
int numFrames = data.capacity() / format.getFrameSize();
double seconds = ((double)numFrames / format.getFrameRate());
mfix$durationNanos = Math.max(0, (long)Math.ceil(seconds * 1_000_000_000.0));
} else {
mfix$durationNanos = 0;
}
}
@Override
public long mfix$getDurationNanos() {
return mfix$durationNanos;
}
}

View File

@ -1,44 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.lazy_search_tree_registry;
import com.google.common.base.Stopwatch;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.minecraft.client.gui.screens.recipebook.RecipeCollection;
import net.minecraft.client.multiplayer.SessionSearchTrees;
import net.minecraft.client.searchtree.SearchTree;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
@Mixin(SessionSearchTrees.class)
@ClientOnlyMixin
public class SessionSearchTreesMixin {
@Shadow private CompletableFuture<SearchTree<RecipeCollection>> recipeSearch;
private Supplier<SearchTree<RecipeCollection>> mfix$deferredSearchTreeSupplier;
@ModifyArg(method = { "method_60367", "lambda$updateRecipes$8" }, at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;supplyAsync(Ljava/util/function/Supplier;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
private Supplier<SearchTree<RecipeCollection>> mfix$deferProcessing(Supplier<SearchTree<RecipeCollection>> supplier) {
this.mfix$deferredSearchTreeSupplier = supplier;
return SearchTree::empty;
}
@WrapMethod(method = "recipes")
private SearchTree<RecipeCollection> mfix$processDeferredBuild(Operation<SearchTree<RecipeCollection>> original) {
synchronized (this) {
if (mfix$deferredSearchTreeSupplier != null) {
Stopwatch watch = Stopwatch.createStarted();
this.recipeSearch = CompletableFuture.completedFuture(mfix$deferredSearchTreeSupplier.get());
watch.stop();
ModernFix.LOGGER.info("Building recipe book search tree took {}", watch);
mfix$deferredSearchTreeSupplier = null;
}
return original.call();
}
}
}

View File

@ -1,28 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.mojang_registry_size;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@Mixin(ResourceKey.class)
public class ResourceKeyMixin<T> {
private static final Map<ResourceLocation, Map<ResourceLocation, ResourceKey<?>>> INTERNING_MAP = new Object2ObjectOpenHashMap<>();
@Inject(method = "create(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/resources/ResourceKey;", at = @At("HEAD"), cancellable = true)
private static <T> void createEfficient(ResourceLocation parent, ResourceLocation location, CallbackInfoReturnable<ResourceKey<T>> cir) {
synchronized (ResourceKey.class) {
Map<ResourceLocation, ResourceKey<?>> keys = INTERNING_MAP.computeIfAbsent(parent, k -> new Object2ObjectOpenHashMap<>());
ResourceKey<?> key = keys.get(location);
if(key == null) {
key = new ResourceKey<>(parent, location);
keys.put(location, key);
}
cir.setReturnValue((ResourceKey<T>)key);
}
}
}

View File

@ -1,15 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuilds;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(BlockBehaviour.class)
public interface BlockBehaviourInvoker {
@Invoker
FluidState invokeGetFluidState(BlockState blockState);
@Invoker
boolean invokeIsRandomlyTicking(BlockState blockState);
}

View File

@ -1,73 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.resourcepacks;
import net.minecraft.server.packs.PackLocationInfo;
import net.minecraft.server.packs.PathPackResources;
import org.embeddedt.modernfix.resources.ICachingResourcePack;
import org.embeddedt.modernfix.resources.PackResourcesCacheEngine;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.nio.file.Path;
@Mixin(value = PathPackResources.class, priority = 1100)
public abstract class PathPackResourcesMixin implements ICachingResourcePack {
@Shadow @Final private Path root;
private PackResourcesCacheEngine cacheEngine;
@Inject(method = "<init>", at = @At("TAIL"))
private void cacheResources(PackLocationInfo location, Path root, CallbackInfo ci) {
invalidateCache();
}
private PackResourcesCacheEngine generateResourceCache() {
synchronized (this) {
PackResourcesCacheEngine engine = this.cacheEngine;
if(engine != null)
return engine;
this.cacheEngine = engine = new PackResourcesCacheEngine((type) -> this.root.resolve(type.getDirectory()));
return engine;
}
}
@Override
public void invalidateCache() {
this.cacheEngine = null;
}
/*
@Inject(method = "getNamespaces", at = @At("HEAD"), cancellable = true)
private void useCacheForNamespaces(PackType type, CallbackInfoReturnable<Set<String>> cir) {
PackResourcesCacheEngine engine = cacheEngine;
if(engine != null) {
Set<String> namespaces = engine.getNamespaces(type);
if(namespaces != null)
cir.setReturnValue(namespaces);
}
}
@Redirect(method = "getRootResource", at = @At(value = "INVOKE", target = "Ljava/nio/file/Files;exists(Ljava/nio/file/Path;[Ljava/nio/file/LinkOption;)Z"))
private boolean useCacheForExistence(Path path, LinkOption[] options, String[] originalPaths) {
// the cache only stores things with a namespace and pack type
if(originalPaths.length < 3 || (!Objects.equals(originalPaths[0], "assets") && !Objects.equals(originalPaths[0], "data")))
return Files.exists(path, options);
else
return this.generateResourceCache().hasResource(originalPaths);
}
@Inject(method = "listResources", at = @At("HEAD"), cancellable = true)
private void fastGetResources(PackType type, String namespace, String path, PackResources.ResourceOutput resourceOutput, CallbackInfo ci)
{
if(!PackTypeHelper.isVanillaPackType(type))
return;
ci.cancel();
this.generateResourceCache().collectResources(type, namespace, path.split("/"), Integer.MAX_VALUE, resourceOutput);
}
*/
}

View File

@ -1,28 +0,0 @@
package org.embeddedt.modernfix.common.mixin.safety;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Collections;
import java.util.List;
@Mixin(LivingEntityRenderer.class)
@ClientOnlyMixin
public class LivingEntityRendererMixin {
@Shadow @Final @Mutable
protected List<RenderLayer<?, ?>> layers;
@Inject(method = "<init>", at = @At("RETURN"))
private void synchronizeLayerList(CallbackInfo ci) {
/* allows buggy mods to call addLayer concurrently, order is not deterministic but can't fix that */
this.layers = Collections.synchronizedList(layers);
}
}

View File

@ -1,9 +0,0 @@
package org.embeddedt.modernfix.dedup;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import net.minecraft.world.level.biome.Climate;
public class ClimateCache {
public static final Interner<Climate.Parameter> MFIX_INTERNER = Interners.newStrongInterner();
}

View File

@ -1,56 +0,0 @@
package org.embeddedt.modernfix.dedup;
import it.unimi.dsi.fastutil.Hash;
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
import java.util.Objects;
public class DeduplicationCache<T> {
private final ObjectOpenCustomHashSet<T> pool;
private int attemptedInsertions = 0;
private int deduplicated = 0;
public DeduplicationCache(Hash.Strategy<T> strategy) {
this.pool = new ObjectOpenCustomHashSet<>(strategy);
}
public DeduplicationCache() {
this.pool = new ObjectOpenCustomHashSet<>(new Hash.Strategy<T>() {
@Override
public int hashCode(T o) {
return Objects.hashCode(o);
}
@Override
public boolean equals(T a, T b) {
return Objects.equals(a, b);
}
});
}
public synchronized T deduplicate(T item) {
this.attemptedInsertions++;
T result = this.pool.addOrGet(item);
if (result != item) {
this.deduplicated++;
}
return result;
}
public synchronized void clearCache() {
this.attemptedInsertions = 0;
this.deduplicated = 0;
this.pool.clear();
}
@Override
public synchronized String toString() {
return String.format("DeduplicationCache ( %d/%d de-duplicated, %d pooled )",
this.deduplicated, this.attemptedInsertions, this.pool.size());
}
}

View File

@ -1,16 +0,0 @@
package org.embeddedt.modernfix.dedup;
import org.embeddedt.modernfix.ModernFix;
public class IdentifierCaches {
public static final DeduplicationCache<String> NAMESPACES = new DeduplicationCache<>();
public static final DeduplicationCache<String> PATH = new DeduplicationCache<>();
public static final DeduplicationCache<String> PROPERTY = new DeduplicationCache<>();
public static void printDebug() {
ModernFix.LOGGER.info("[[[ Identifier de-duplication statistics ]]]");
ModernFix.LOGGER.info("Namespace cache: {}", NAMESPACES);
ModernFix.LOGGER.info("Path cache: {}", PATH);
}
}

View File

@ -1,7 +0,0 @@
package org.embeddedt.modernfix.duck;
import net.minecraft.client.resources.model.ModelResourceLocation;
public interface IBlockStateModelLoader {
void loadSpecificBlock(ModelResourceLocation location);
}

View File

@ -1,5 +0,0 @@
package org.embeddedt.modernfix.duck;
public interface ICachedMaterialsModel {
public void clearMaterialsCache();
}

View File

@ -1,7 +0,0 @@
package org.embeddedt.modernfix.duck;
import net.minecraft.server.level.ServerLevel;
public interface IChunkGenerator {
void mfix$setAssociatedServerLevel(ServerLevel level);
}

View File

@ -1,14 +0,0 @@
package org.embeddedt.modernfix.duck;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import java.util.concurrent.locks.ReentrantLock;
public interface IExtendedModelBakery {
void mfix$tick();
void mfix$finishLoading();
UnbakedModel mfix$loadUnbakedModelDynamic(ModelResourceLocation location);
UnbakedModel mfix$getMissingModel();
ReentrantLock mfix$getLock();
}

View File

@ -1,5 +0,0 @@
package org.embeddedt.modernfix.duck;
public interface IExtendedModelManager {
void mfix$tick();
}

View File

@ -1,7 +0,0 @@
package org.embeddedt.modernfix.duck;
import net.minecraft.world.level.storage.LevelStorageSource;
public interface ILevelSave {
public void runWorldPersistenceHooks(LevelStorageSource format);
}

View File

@ -1,7 +0,0 @@
package org.embeddedt.modernfix.duck;
import org.embeddedt.modernfix.chunk.SafeBlockGetter;
public interface ISafeBlockGetter {
SafeBlockGetter mfix$getSafeBlockGetter();
}

View File

@ -1,7 +0,0 @@
package org.embeddedt.modernfix.duck;
import org.embeddedt.modernfix.world.StrongholdLocationCache;
public interface IServerLevel {
StrongholdLocationCache mfix$getStrongholdCache();
}

View File

@ -1,10 +0,0 @@
package org.embeddedt.modernfix.duck.reuse_datapacks;
import net.minecraft.server.ReloadableServerResources;
import java.util.Collection;
public interface ICachingResourceClient {
void setCachedResources(ReloadableServerResources r);
void setCachedDataPackConfig(Collection<String> c);
}

View File

@ -1,8 +0,0 @@
package org.embeddedt.modernfix.dynamicresources;
import net.minecraft.client.renderer.block.model.BlockFaceUV;
public class UVController {
public static final ThreadLocal<Boolean> useDummyUv = ThreadLocal.withInitial(() -> Boolean.FALSE);
public static final BlockFaceUV dummyUv = new BlockFaceUV(new float[4], 0);
}

View File

@ -1,185 +0,0 @@
package org.embeddedt.modernfix.entity;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.EntityRenderers;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.entity.EntityType;
import org.embeddedt.modernfix.ModernFix;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@SuppressWarnings("OptionalAssignedToNull")
public class EntityRendererMap implements Map<EntityType<?>, EntityRenderer<?>> {
private final Map<EntityType<?>, EntityRendererProvider<?>> rendererProviders;
private final LoadingCache<EntityType<?>, Optional<EntityRenderer<?>>> rendererMap;
private final EntityRendererProvider.Context context;
public EntityRendererMap(Map<EntityType<?>, EntityRendererProvider<?>> rendererProviders, EntityRendererProvider.Context context) {
this.rendererProviders = rendererProviders;
this.context = context;
this.rendererMap = CacheBuilder.newBuilder().build(new RenderConstructor());
}
class RenderConstructor extends CacheLoader<EntityType<?>, Optional<EntityRenderer<?>>> {
@Override
public Optional<EntityRenderer<?>> load(EntityType<?> key) throws Exception {
EntityRendererProvider<?> provider = rendererProviders.get(key);
if(provider == null)
return Optional.empty();
synchronized(EntityRenderers.class) {
EntityRenderer<?> renderer;
try {
renderer = provider.create(context);
ModernFix.LOGGER.info("Loaded entity {}", BuiltInRegistries.ENTITY_TYPE.getKey(key));
} catch(RuntimeException e) {
ModernFix.LOGGER.error("Failed to create entity model for " + BuiltInRegistries.ENTITY_TYPE.getKey(key) + ":", e);
renderer = new ErroredEntityRenderer<>(context);
}
return Optional.ofNullable(renderer);
}
}
}
@Override
public int size() {
return rendererProviders.size();
}
@Override
public boolean isEmpty() {
return rendererProviders.isEmpty();
}
@Override
public boolean containsKey(Object o) {
return rendererProviders.containsKey(o);
}
@Override
public boolean containsValue(Object o) {
return false;
}
@Override
public EntityRenderer<?> get(Object o) {
try {
Optional<EntityRenderer<?>> renderer = rendererMap.get((EntityType<?>)o);
return renderer.orElse(null);
} catch (IllegalStateException e) {
return null; /* emulate value not being present if recursive load occurs */
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
@Nullable
@Override
public EntityRenderer<?> put(EntityType<?> entityType, EntityRenderer<?> entityRenderer) {
Optional<EntityRenderer<?>> old = rendererMap.getIfPresent(entityType);
rendererMap.put(entityType, Optional.ofNullable(entityRenderer));
return old != null ? old.orElse(null) : null;
}
@Override
public EntityRenderer<?> remove(Object o) {
Optional<EntityRenderer<?>> old = rendererMap.getIfPresent(o);
rendererMap.invalidate(o);
return old != null ? old.orElse(null) : null;
}
@Override
public void putAll(@NotNull Map<? extends EntityType<?>, ? extends EntityRenderer<?>> map) {
rendererMap.putAll(Maps.transformValues(map, Optional::ofNullable));
}
@Override
public void clear() {
rendererMap.invalidateAll();
}
@NotNull
@Override
public Set<EntityType<?>> keySet() {
return rendererProviders.keySet();
}
@NotNull
@Override
public Collection<EntityRenderer<?>> values() {
return new AbstractCollection<>() {
@Override
public Iterator<EntityRenderer<?>> iterator() {
return Iterators.transform(Iterators.unmodifiableIterator(rendererProviders.keySet().iterator()), EntityRendererMap.this::get);
}
@Override
public int size() {
return rendererProviders.size();
}
};
}
private class Entry implements Map.Entry<EntityType<?>, EntityRenderer<?>> {
private final EntityType<?> key;
private Entry(EntityType<?> key) {
this.key = key;
}
@Override
public EntityType<?> getKey() {
return key;
}
@Override
public EntityRenderer<?> getValue() {
return get(key);
}
@Override
public EntityRenderer<?> setValue(EntityRenderer<?> value) {
return put(key, value);
}
}
@NotNull
@Override
public Set<Map.Entry<EntityType<?>, EntityRenderer<?>>> entrySet() {
return new AbstractSet<>() {
@Override
public Iterator<Map.Entry<EntityType<?>, EntityRenderer<?>>> iterator() {
return Iterators.transform(Iterators.unmodifiableIterator(rendererProviders.keySet().iterator()), Entry::new);
}
@Override
public boolean contains(Object o) {
if (o instanceof Map.Entry<?,?> e) {
return rendererProviders.containsKey(e.getKey()) && Objects.equals(get(e.getKey()), e.getValue());
} else {
return false;
}
}
@Override
public int size() {
return rendererProviders.size();
}
};
}
}

View File

@ -1,30 +0,0 @@
package org.embeddedt.modernfix.entity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.culling.Frustum;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
public class ErroredEntityRenderer<T extends Entity> extends EntityRenderer<T> {
public ErroredEntityRenderer(EntityRendererProvider.Context arg) {
super(arg);
}
@Override
public ResourceLocation getTextureLocation(T entity) {
return TextureAtlas.LOCATION_BLOCKS;
}
@Override
public boolean shouldRender(T livingEntity, Frustum camera, double camX, double camY, double camZ) {
return false;
}
@Override
public void render(T entity, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight) {
}
}

View File

@ -1,15 +0,0 @@
package org.embeddedt.modernfix.sound;
import com.mojang.blaze3d.audio.SoundBuffer;
import net.minecraft.resources.ResourceLocation;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class SoundBufferCache extends LinkedHashMap<ResourceLocation, CompletableFuture<SoundBuffer>> {
@Override
protected boolean removeEldestEntry(Map.Entry<ResourceLocation, CompletableFuture<SoundBuffer>> eldest) {
return super.removeEldestEntry(eldest);
}
}

View File

@ -1,47 +0,0 @@
package org.embeddedt.modernfix.tickables;
import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class LoadableTickableObject<T> implements TickableObject {
private volatile int ticksInactive = 0;
private final int timeout;
private final Supplier<T> loader;
private final Consumer<T> finalizer;
private volatile T theObject = null;
public LoadableTickableObject(int timeout, Supplier<T> loader, Consumer<T> finalizer) {
this(timeout, loader, finalizer, null);
}
public LoadableTickableObject(int timeout, Supplier<T> loader, Consumer<T> finalizer, @Nullable T initialValue) {
this.timeout = timeout;
this.loader = loader;
this.finalizer = finalizer;
this.theObject = initialValue;
}
public T get() {
synchronized (this) {
ticksInactive++;
T obj = theObject;
if(obj == null) {
obj = loader.get();
theObject = obj;
}
return obj;
}
}
public final void tick() {
synchronized (this) {
ticksInactive++;
if(ticksInactive >= this.timeout) {
finalizer.accept(theObject);
theObject = null;
}
}
}
}

View File

@ -1,5 +0,0 @@
package org.embeddedt.modernfix.tickables;
public interface TickableObject {
void tick();
}

View File

@ -1,18 +0,0 @@
package org.embeddedt.modernfix.tickables;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class TickableObjectManager {
private static final List<TickableObject> TICKABLE_OBJECT_LIST = new CopyOnWriteArrayList<>();
public static void register(TickableObject object) {
TICKABLE_OBJECT_LIST.add(object);
}
public static void runTick() {
for(TickableObject o : TICKABLE_OBJECT_LIST) {
o.tick();
}
}
}

View File

@ -1,48 +0,0 @@
package org.embeddedt.modernfix.util;
import com.google.common.base.Function;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import com.google.common.collect.Maps;
import java.util.HashMap;
import java.util.Map;
/**
* Replacement backing map for CompoundTags that interns keys.
*/
public class CanonizingStringMap<T> extends HashMap<String, T> {
private static final Interner<String> KEY_INTERNER = Interners.newWeakInterner();
private static String intern(String key) {
return key != null ? KEY_INTERNER.intern(key) : null;
}
public CanonizingStringMap() {
super();
}
@Override
public T put(String key, T value) {
return super.put(intern(key), value);
}
@Override
public void putAll(Map<? extends String, ? extends T> m) {
if(m.isEmpty())
return;
HashMap<String, T> tmp = new HashMap<>();
m.forEach((k, v) -> tmp.put(intern(k), v));
super.putAll(tmp);
}
private void putAllWithoutInterning(Map<? extends String, ? extends T> m) {
super.putAll(m);
}
public static <T> CanonizingStringMap<T> deepCopy(CanonizingStringMap<T> incomingMap, Function<T, T> deepCopier) {
CanonizingStringMap<T> newMap = new CanonizingStringMap<>();
newMap.putAllWithoutInterning(Maps.transformValues(incomingMap, deepCopier));
return newMap;
}
}

View File

@ -1,43 +0,0 @@
package org.embeddedt.modernfix.util;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.TimeUnit;
public class DirectExecutorService extends AbstractExecutorService {
private boolean isShutdown;
@Override
public void shutdown() {
isShutdown = true;
}
@NotNull
@Override
public List<Runnable> shutdownNow() {
isShutdown = true;
return List.of();
}
@Override
public boolean isShutdown() {
return isShutdown;
}
@Override
public boolean isTerminated() {
return isShutdown;
}
@Override
public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) throws InterruptedException {
return true;
}
@Override
public void execute(@NotNull Runnable command) {
command.run();
}
}

View File

@ -1,162 +0,0 @@
package org.embeddedt.modernfix.util;
import com.google.common.collect.ImmutableSet;
import com.mojang.serialization.Lifecycle;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.*;
import net.minecraft.core.RegistryAccess;
import net.minecraft.world.Difficulty;
import net.minecraft.world.level.dimension.end.EndDragonFight;
import net.minecraft.world.level.levelgen.WorldOptions;
import net.minecraft.world.level.storage.WorldData;
import net.minecraft.world.level.storage.ServerLevelData;
import java.util.Collections;
import java.util.Set;
public class DummyServerConfiguration implements WorldData {
@Override
public WorldDataConfiguration getDataConfiguration() {
return null;
}
@Override
public void setDataConfiguration(WorldDataConfiguration arg) {
}
@Override
public Set<String> getRemovedFeatureFlags() {
return Collections.emptySet();
}
@Override
public boolean wasModded() {
return true;
}
@Override
public Set<String> getKnownServerBrands() {
return ImmutableSet.of("forge");
}
@Override
public void setModdedInfo(String name, boolean isModded) {
}
@Override
public CompoundTag getCustomBossEvents() {
return null;
}
@Override
public void setCustomBossEvents(CompoundTag nbt) {
}
@Override
public ServerLevelData overworldData() {
return null;
}
@Override
public LevelSettings getLevelSettings() {
return null;
}
@Override
public CompoundTag createTag(RegistryAccess registries, CompoundTag hostPlayerNBT) {
return null;
}
@Override
public boolean isHardcore() {
return false;
}
@Override
public int getVersion() {
return 0;
}
@Override
public String getLevelName() {
return null;
}
@Override
public GameType getGameType() {
return null;
}
@Override
public void setGameType(GameType type) {
}
@Override
public boolean isAllowCommands() {
return false;
}
@Override
public Difficulty getDifficulty() {
return null;
}
@Override
public void setDifficulty(Difficulty difficulty) {
}
@Override
public boolean isDifficultyLocked() {
return false;
}
@Override
public void setDifficultyLocked(boolean locked) {
}
@Override
public GameRules getGameRules() {
return null;
}
@Override
public CompoundTag getLoadedPlayerTag() {
return null;
}
@Override
public EndDragonFight.Data endDragonFightData() {
return EndDragonFight.Data.DEFAULT;
}
@Override
public void setEndDragonFightData(EndDragonFight.Data data) {
}
@Override
public WorldOptions worldGenOptions() {
return null;
}
@Override
public boolean isFlatWorld() {
return false;
}
@Override
public boolean isDebugWorld() {
return false;
}
@Override
public Lifecycle worldGenSettingsLifecycle() {
return Lifecycle.stable();
}
}

View File

@ -1,50 +0,0 @@
package org.embeddedt.modernfix.util;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
import java.util.Map;
import java.util.Set;
public class LRUMap<K, V> extends Object2ObjectLinkedOpenHashMap<K, V> {
private Set<K> permanentEntries = Set.of();
public LRUMap(Map<K, V> map) {
super(map);
}
@Override
public V put(K k, V v) {
return putAndMoveToLast(k, v);
}
@Override
public V get(Object k) {
return getAndMoveToLast((K)k);
}
public void setPermanentEntries(Set<K> permanentEntries) {
this.permanentEntries = permanentEntries;
}
public void dropEntriesToMeetSize(int size) {
int expectedQuota = size;
// Increase allowed size quota to include permanent entries
size += permanentEntries.size();
int prevSize = size();
if(size() > size) {
var iterator = entrySet().iterator();
while(size() > size && iterator.hasNext()) {
var entry = iterator.next();
var key = entry.getKey();
if(key != null && !this.permanentEntries.contains(key)) {
iterator.remove();
}
}
trim(size() + expectedQuota);
if(ModernFixPlatformHooks.INSTANCE.isDevEnv()) {
ModernFix.LOGGER.warn("Trimmed map from {} to {} entries", prevSize, size);
}
}
}
}

View File

@ -1,132 +0,0 @@
package org.embeddedt.modernfix.util;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Simple forwarding map implementation that allows layering multiple maps together.
*/
public class LayeredForwardingMap<K, V> implements Map<K, V> {
private final Map<K, V>[] layers;
public LayeredForwardingMap(Map<K, V>[] layers) {
if(layers.length < 1)
throw new IllegalArgumentException();
for(Map<K, V> layer : layers) {
if(layer == null)
throw new IllegalArgumentException();
}
this.layers = layers;
}
@Override
public int size() {
return 1;
}
@Override
public boolean isEmpty() {
for(Map<K, V> map : layers) {
if(!map.isEmpty())
return false;
}
return true;
}
@Override
public boolean containsKey(Object key) {
for(Map<K, V> map : layers) {
if(map.containsKey(key))
return true;
}
return false;
}
@Override
public boolean containsValue(Object value) {
for(Map<K, V> map : layers) {
if(map.containsValue(value))
return true;
}
return false;
}
@Override
public V get(Object key) {
for(Map<K, V> map : layers) {
V value = map.get(key);
if(value != null)
return value;
}
return null;
}
@Nullable
@Override
public V put(K key, V value) {
if(value == null)
throw new IllegalArgumentException();
V originalValue = null;
for(Map<K, V> map : layers) {
V oldVal = map.remove(key);
if(originalValue == null)
originalValue = oldVal;
map.put(key, value);
}
return originalValue;
}
@Override
public V remove(Object key) {
for(Map<K, V> map : layers) {
map.remove(key);
}
return null;
}
@Override
public void putAll(@NotNull Map<? extends K, ? extends V> m) {
for(V value : m.values()) {
if(value == null)
throw new IllegalArgumentException();
}
for(Map<K, V> map : layers) {
map.putAll(m);
}
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<K> keySet() {
Set<K> keys = new ObjectOpenHashSet<>();
for(Map<K, V> map : layers) {
keys.addAll(map.keySet());
}
return Collections.unmodifiableSet(keys);
}
@NotNull
@Override
public Collection<V> values() {
Set<K> keys = keySet();
List<V> vals = new ArrayList<>();
for(K key : keys) {
vals.add(get(key));
}
return vals;
}
@NotNull
@Override
public Set<Entry<K, V>> entrySet() {
throw new UnsupportedOperationException();
}
}

View File

@ -1,62 +0,0 @@
package org.embeddedt.modernfix.world;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.datafix.DataFixTypes;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.saveddata.SavedData;
import java.util.ArrayList;
import java.util.List;
public class StrongholdLocationCache extends SavedData {
private List<ChunkPos> chunkPosList;
public StrongholdLocationCache() {
super();
chunkPosList = new ArrayList<>();
}
public static SavedData.Factory<StrongholdLocationCache> factory(ServerLevel serverLevel) {
// FIXME datafixer will probably throw on update
return new SavedData.Factory<>(StrongholdLocationCache::new, StrongholdLocationCache::load, DataFixTypes.SAVED_DATA_FORCED_CHUNKS);
}
public List<ChunkPos> getChunkPosList() {
return new ArrayList<>(chunkPosList);
}
public void setChunkPosList(List<ChunkPos> positions) {
this.chunkPosList = new ArrayList<>(positions);
this.setDirty();
}
public static StrongholdLocationCache load(CompoundTag arg, HolderLookup.Provider provider) {
StrongholdLocationCache cache = new StrongholdLocationCache();
if(arg.contains("Positions", Tag.TAG_LONG_ARRAY)) {
long[] positions = arg.getLongArray("Positions");
for(long position : positions) {
cache.chunkPosList.add(new ChunkPos(position));
}
}
return cache;
}
@Override
public CompoundTag save(CompoundTag compoundTag, HolderLookup.Provider provider) {
long[] serialized = new long[chunkPosList.size()];
for(int i = 0; i < chunkPosList.size(); i++) {
ChunkPos thePos = chunkPosList.get(i);
serialized[i] = thePos.toLong();
}
compoundTag.putLongArray("Positions", serialized);
return compoundTag;
}
public static String getFileId(Holder<DimensionType> dimensionType) {
return "mfix_strongholds";
}
}

View File

@ -1,66 +0,0 @@
accessWidener v2 named
accessible field net/minecraft/client/multiplayer/ClientChunkCache storage Lnet/minecraft/client/multiplayer/ClientChunkCache$Storage;
accessible field net/minecraft/client/multiplayer/ClientChunkCache lightEngine Lnet/minecraft/world/level/lighting/LevelLightEngine;
mutable field net/minecraft/client/multiplayer/ClientChunkCache lightEngine Lnet/minecraft/world/level/lighting/LevelLightEngine;
accessible class net/minecraft/client/multiplayer/ClientChunkCache$Storage
accessible field net/minecraft/client/multiplayer/ClientChunkCache$Storage chunks Ljava/util/concurrent/atomic/AtomicReferenceArray;
accessible field net/minecraft/world/level/Level blockEntityTickers Ljava/util/List;
accessible class net/minecraft/client/renderer/RenderType$CompositeRenderType
accessible method net/minecraft/nbt/CompoundTag <init> (Ljava/util/Map;)V
accessible class net/minecraft/world/level/levelgen/SurfaceRules$Condition
accessible class net/minecraft/world/level/levelgen/SurfaceRules$LazyCondition
accessible class net/minecraft/world/level/levelgen/SurfaceRules$SequenceRule
accessible class net/minecraft/world/level/levelgen/SurfaceRules$SurfaceRule
accessible class net/minecraft/world/level/levelgen/SurfaceRules$Context
accessible class net/minecraft/world/level/levelgen/DensityFunctions$Marker
accessible class net/minecraft/world/level/levelgen/DensityFunctions$Marker$Type
accessible method net/minecraft/world/level/levelgen/DensityFunctions$Marker <init> (Lnet/minecraft/world/level/levelgen/DensityFunctions$Marker$Type;Lnet/minecraft/world/level/levelgen/DensityFunction;)V
accessible class net/minecraft/world/level/levelgen/DensityFunctions$Mapped
accessible class net/minecraft/world/level/levelgen/DensityFunctions$Mapped$Type
accessible method net/minecraft/world/level/levelgen/DensityFunctions$Mapped <init> (Lnet/minecraft/world/level/levelgen/DensityFunctions$Mapped$Type;Lnet/minecraft/world/level/levelgen/DensityFunction;DD)V
accessible class net/minecraft/world/level/levelgen/DensityFunctions$MulOrAdd
accessible class net/minecraft/world/level/levelgen/DensityFunctions$MulOrAdd$Type
accessible method net/minecraft/world/level/levelgen/DensityFunctions$MulOrAdd <init> (Lnet/minecraft/world/level/levelgen/DensityFunctions$MulOrAdd$Type;Lnet/minecraft/world/level/levelgen/DensityFunction;DDD)V
accessible class net/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase$Cache
accessible class net/minecraft/server/level/ServerChunkCache$MainThreadExecutor
accessible field net/minecraft/world/level/block/state/BlockBehaviour properties Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;
accessible class net/minecraft/client/renderer/block/model/BlockElementFace$Deserializer
accessible class net/minecraft/client/renderer/texture/Stitcher$Holder
accessible field net/minecraft/client/renderer/texture/Stitcher$Holder width I
accessible field net/minecraft/client/renderer/texture/Stitcher$Holder height I
accessible field net/minecraft/network/syncher/EntityDataAccessor id I
mutable field net/minecraft/network/syncher/EntityDataAccessor id I
accessible method net/minecraft/Util makeExecutor (Ljava/lang/String;)Ljava/util/concurrent/ExecutorService;
accessible field net/minecraft/server/level/ChunkMap updatingChunkMap Lit/unimi/dsi/fastutil/longs/Long2ObjectLinkedOpenHashMap;
accessible field net/minecraft/server/level/ChunkMap visibleChunkMap Lit/unimi/dsi/fastutil/longs/Long2ObjectLinkedOpenHashMap;
accessible field net/minecraft/server/level/ChunkMap pendingUnloads Lit/unimi/dsi/fastutil/longs/Long2ObjectLinkedOpenHashMap;
accessible method net/minecraft/resources/ResourceKey <init> (Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/resources/ResourceLocation;)V
accessible field net/minecraft/client/renderer/block/model/BlockModel GSON Lcom/google/gson/Gson;
accessible class net/minecraft/server/level/ChunkMap$DistanceManager
accessible class net/minecraft/client/resources/model/ModelBakery$BakedCacheKey
accessible method net/minecraft/client/resources/model/ModelBakery$BakedCacheKey <init> (Lnet/minecraft/resources/ResourceLocation;Lcom/mojang/math/Transformation;Z)V
accessible class net/minecraft/client/resources/model/ModelBakery$ModelBakerImpl
accessible method net/minecraft/client/resources/model/ModelBakery$ModelBakerImpl <init> (Lnet/minecraft/client/resources/model/ModelBakery;Lnet/minecraft/client/resources/model/ModelBakery$TextureGetter;Lnet/minecraft/client/resources/model/ModelResourceLocation;)V
accessible method net/minecraft/client/resources/model/ModelBakery getModel (Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/client/resources/model/UnbakedModel;
accessible class net/minecraft/world/level/chunk/PalettedContainer$Data
accessible field net/minecraft/server/MinecraftServer resources Lnet/minecraft/server/MinecraftServer$ReloadableResources;
accessible class net/minecraft/server/MinecraftServer$ReloadableResources
accessible method net/minecraft/client/gui/screens/Screen addRenderableWidget (Lnet/minecraft/client/gui/components/events/GuiEventListener;)Lnet/minecraft/client/gui/components/events/GuiEventListener;
accessible field net/minecraft/client/KeyMapping ALL Ljava/util/Map;
accessible field net/minecraft/client/renderer/block/model/multipart/MultiPart definition Lnet/minecraft/world/level/block/state/StateDefinition;
accessible field net/minecraft/client/renderer/block/model/ItemOverrides$BakedOverride model Lnet/minecraft/client/resources/model/BakedModel;
mutable field net/minecraft/client/renderer/block/model/ItemOverrides$BakedOverride model Lnet/minecraft/client/resources/model/BakedModel;
accessible field net/minecraft/client/renderer/entity/EnderDragonRenderer$DragonModel entity Lnet/minecraft/world/entity/boss/enderdragon/EnderDragon;
accessible method net/minecraft/world/level/block/state/StateDefinition appendPropertyCodec (Lcom/mojang/serialization/MapCodec;Ljava/util/function/Supplier;Ljava/lang/String;Lnet/minecraft/world/level/block/state/properties/Property;)Lcom/mojang/serialization/MapCodec;
accessible class net/minecraft/client/multiplayer/SessionSearchTrees$Key
accessible field net/minecraft/server/packs/resources/ProfiledReloadInstance$State preparationNanos Ljava/util/concurrent/atomic/AtomicLong;
accessible field net/minecraft/server/packs/resources/ProfiledReloadInstance$State reloadNanos Ljava/util/concurrent/atomic/AtomicLong;
accessible class net/minecraft/world/item/crafting/Ingredient$Value
accessible class net/minecraft/world/item/crafting/Ingredient$ItemValue

View File

@ -1,126 +0,0 @@
plugins {
id "com.github.johnrengelman.shadow"
id 'com.adarshr.test-logger' version '3.2.0'
id "modernfix.mod-common-conventions"
id "modernfix.platform-conventions"
}
architectury {
platformSetupLoomIde()
fabric()
}
configurations {
common
shadowCommon // Don't use shadow from the shadow plugin since it *excludes* files.
compileClasspath.extendsFrom common
runtimeClasspath.extendsFrom common
modIncludeImplementation
include.extendsFrom modIncludeImplementation
modImplementation.extendsFrom modIncludeImplementation
testAgent {
canBeConsumed = false
}
}
dependencies {
modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
testImplementation "net.fabricmc:fabric-loader-junit:${rootProject.fabric_loader_version}"
modCompileOnly(fabricApi.module("fabric-api-base", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modCompileOnly(fabricApi.module("fabric-screen-api-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modCompileOnly(fabricApi.module("fabric-command-api-v2", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modCompileOnly(fabricApi.module("fabric-model-loading-api-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modCompileOnly(fabricApi.module("fabric-resource-loader-v0", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modCompileOnly(fabricApi.module("fabric-data-generation-api-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
if(project.use_fabric_api_at_runtime.toBoolean()) {
modImplementation("com.terraformersmc:modmenu:${rootProject.modmenu_version}") { transitive false }
modImplementation "curse.maven:spark-361579:${rootProject.spark_version}"
modRuntimeOnly("net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}") { exclude group: 'net.fabricmc', module: 'fabric-loader' }
} else {
modCompileOnly("com.terraformersmc:modmenu:${rootProject.modmenu_version}") { transitive false }
modCompileOnly "curse.maven:spark-361579:${rootProject.spark_version}"
}
// Remove the next line if you don't want to depend on the API
// modApi "me.shedaniel:architectury-fabric:${rootProject.architectury_version}"
common(project(path: ":common", configuration: "namedElements")) { transitive false }
testImplementation(shadowCommon(project(path: ":common", configuration: "transformProductionFabric"))) { transitive false }
shadowCommon(project(path: ":annotations"))
testImplementation(platform("org.junit:junit-bom:${project.junit_version}"))
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-params")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testImplementation("org.junit.platform:junit-platform-launcher")
testImplementation("org.assertj:assertj-core:3.19.0")
testImplementation("com.google.guava:guava-testlib:21.0")
testImplementation("org.mockito:mockito-junit-jupiter:5.3.1")
testAgent(project("path": ":test_agent", "configuration": "agentJar"))
}
tasks.named("test") {
useJUnitPlatform()
def runDir = file('test_run')
doFirst() {
runDir.mkdir()
}
workingDir = runDir
systemProperty 'modernfix.ignoreConfigForTesting', 'true'
// inject our custom agent to fix #817
FileCollection agentFile = configurations.getByName("testAgent")
jvmArgs "-javaagent:${agentFile.singleFile.absolutePath}"
dependsOn(agentFile)
}
processResources {
inputs.property "version", project.version
filesMatching("fabric.mod.json") {
expand "version": project.version
}
}
shadowJar {
exclude "architectury.common.json"
configurations = [project.configurations.shadowCommon]
archiveClassifier.set("dev-shadow")
}
remapJar {
injectAccessWidener = true
input.set shadowJar.archiveFile
dependsOn shadowJar
archiveClassifier.set(null)
}
jar {
archiveClassifier.set("dev")
}
components.java {
withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) {
skip()
}
}
publishing {
publications {
mavenFabric(MavenPublication) {
artifactId = rootProject.archives_base_name + "-" + project.name
from components.java
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
}
}

View File

@ -1,18 +0,0 @@
package org.embeddedt.modernfix;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import org.embeddedt.modernfix.fabric.datagen.RuntimeDatagen;
public class ModernFixClientFabric implements ClientModInitializer {
public static ModernFixClient commonMod;
@Override
public void onInitializeClient() {
commonMod = new ModernFixClient();
if(FabricLoader.getInstance().isModLoaded("fabric-data-generation-api-v1")) {
RuntimeDatagen.init();
}
}
}

View File

@ -1,19 +0,0 @@
package org.embeddedt.modernfix;
import net.fabricmc.api.ModInitializer;
import net.minecraft.server.MinecraftServer;
import java.lang.ref.WeakReference;
public class ModernFixFabric implements ModInitializer {
public static ModernFix commonMod;
public static WeakReference<MinecraftServer> theServer = new WeakReference<>(null);
@Override
public void onInitialize() {
commonMod = new ModernFix();
// TODO: implement entity ID desync
}
}

View File

@ -1,35 +0,0 @@
package org.embeddedt.modernfix;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
import net.fabricmc.loader.impl.gui.FabricGuiEntry;
import net.fabricmc.loader.impl.gui.FabricStatusTree;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.spark.SparkLaunchProfiler;
import org.embeddedt.modernfix.util.CommonModUtil;
public class ModernFixPreLaunchFabric implements PreLaunchEntrypoint {
@Override
public void onPreLaunch() {
if(ModernFixMixinPlugin.instance == null) {
System.err.println("Mixin plugin not loaded yet");
return;
}
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnFabric")) {
CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.start("launch"), "Failed to start profiler");
}
// Prevent launching with Continuity when dynamic resources is on
if(false && ModernFixMixinPlugin.instance.isOptionEnabled("perf.dynamic_resources.ContinuityCheck")
&& FabricLoader.getInstance().isModLoaded("continuity")) {
CommonModUtil.runWithoutCrash(() -> {
FabricGuiEntry.displayError("Compatibility warning", null, tree -> {
FabricStatusTree.FabricStatusTab crashTab = tree.addTab("Warning");
crashTab.node.addMessage("Continuity and ModernFix's dynamic resources option are not compatible before Minecraft 1.19.4.", FabricStatusTree.FabricTreeWarningLevel.ERROR);
crashTab.node.addMessage("Remove Continuity or disable dynamic resources in the ModernFix config.", FabricStatusTree.FabricTreeWarningLevel.ERROR);
tree.tabs.removeIf(tab -> tab != crashTab);
}, true);
}, "display Continuity warning");
}
}
}

View File

@ -1,20 +0,0 @@
package org.embeddedt.modernfix.fabric.api.dynresources;
import net.minecraft.resources.ResourceLocation;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class ModelScanController {
public static final List<Predicate<ResourceLocation>> SCAN_PREDICATES = new ArrayList<>();
public static boolean shouldScanAndTestWrapping(ResourceLocation location) {
if(SCAN_PREDICATES.size() > 0) {
for(Predicate<ResourceLocation> predicate : SCAN_PREDICATES) {
if(!predicate.test(location))
return false;
}
}
return true;
}
}

View File

@ -1,40 +0,0 @@
package org.embeddedt.modernfix.fabric.datagen;
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
import net.fabricmc.fabric.impl.datagen.FabricDataGenHelper;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.network.chat.Component;
import org.embeddedt.modernfix.ModernFix;
import java.lang.reflect.Method;
public class RuntimeDatagen {
private static final boolean SHOULD_RUNTIME_DATAGEN = System.getProperty("fabric-api.datagen.output-dir") != null;
private static void runRuntimeDatagen() {
// call runInternal directly to avoid exiting immediately
try {
System.setProperty("fabric-api.datagen", "true");
Method method = FabricDataGenHelper.class.getDeclaredMethod("runInternal");
method.setAccessible(true);
method.invoke(null);
} catch(Throwable e) {
ModernFix.LOGGER.error("Error running datagen", e);
} finally {
System.clearProperty("fabric-api.datagen");
}
}
public static void init() {
if(!SHOULD_RUNTIME_DATAGEN)
return;
ScreenEvents.AFTER_INIT.register(((client, s, scaledWidth, scaledHeight) -> {
if(s instanceof TitleScreen screen) {
screen.addRenderableWidget(Button.builder(Component.literal("DG"), (arg) -> {
runRuntimeDatagen();
}).pos(screen.width / 2 - 100 - 50, screen.height / 4 + 48).size(50, 20).build());
}
}));
}
}

View File

@ -1,18 +0,0 @@
package org.embeddedt.modernfix.fabric.mixin.core;
import net.minecraft.client.multiplayer.ClientConfigurationPacketListenerImpl;
import org.embeddedt.modernfix.ModernFixClientFabric;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientConfigurationPacketListenerImpl.class)
@ClientOnlyMixin
public class ClientCommonPacketListenerImplMixin {
@Inject(method = "handleUpdateTags", at = @At("RETURN"))
private void signalTags(CallbackInfo ci) {
ModernFixClientFabric.commonMod.onTagsUpdated();
}
}

View File

@ -1,18 +0,0 @@
package org.embeddedt.modernfix.fabric.mixin.core;
import net.minecraft.server.MinecraftServer;
import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(MinecraftServer.class)
@ClientOnlyMixin
public class ClientMinecraftServerMixin {
@Inject(method = "runServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;getNanos()J", ordinal = 0))
private void markServerStarted(CallbackInfo ci) {
ModernFixClient.INSTANCE.onServerStarted((MinecraftServer)(Object)this);
}
}

View File

@ -1,18 +0,0 @@
package org.embeddedt.modernfix.fabric.mixin.core;
import net.minecraft.client.multiplayer.ClientPacketListener;
import org.embeddedt.modernfix.ModernFixClientFabric;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = ClientPacketListener.class, priority = 1500)
@ClientOnlyMixin
public class ClientPlayNetHandlerMixin {
@Inject(method = "handleUpdateRecipes", at = @At("RETURN"))
private void signalRecipes(CallbackInfo ci) {
ModernFixClientFabric.commonMod.onRecipesUpdated();
}
}

View File

@ -1,18 +0,0 @@
package org.embeddedt.modernfix.fabric.mixin.core;
import net.minecraft.client.Minecraft;
import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Minecraft.class)
@ClientOnlyMixin
public class MCMixin_Fabric {
@Inject(method = "tick", at = @At("RETURN"))
private void onRenderTickEnd(CallbackInfo ci) {
ModernFixClient.INSTANCE.onRenderTickEnd();
}
}

View File

@ -1,29 +0,0 @@
package org.embeddedt.modernfix.fabric.mixin.core;
import net.minecraft.server.MinecraftServer;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.ModernFixFabric;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.lang.ref.WeakReference;
@Mixin(MinecraftServer.class)
public class MinecraftServerMixin {
@Inject(method = "runServer", at = @At("HEAD"))
private void changeServerReference(CallbackInfo ci) {
ModernFixFabric.theServer = new WeakReference<>((MinecraftServer)(Object)this);
}
@Inject(method = "runServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;getNanos()J", ordinal = 0))
private void hookServerStarted(CallbackInfo ci) {
ModernFix.INSTANCE.onServerStarted();
}
@Inject(method = "stopServer", at = @At("RETURN"))
private void hookServerShutdown(CallbackInfo ci) {
ModernFix.INSTANCE.onServerDead((MinecraftServer)(Object)this);
}
}

View File

@ -1,21 +0,0 @@
package org.embeddedt.modernfix.fabric.mixin.feature.branding;
import net.minecraft.client.gui.components.DebugScreenOverlay;
import org.embeddedt.modernfix.ModernFixClientFabric;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import java.util.List;
@Mixin(DebugScreenOverlay.class)
@ClientOnlyMixin
public class GuiMixin {
@ModifyVariable(method = "getSystemInformation", at = @At("STORE"), ordinal = 0, require = 0)
private List<String> addModernFix(List<String> list) {
list.add("");
list.add(ModernFixClientFabric.commonMod.brandingString);
return list;
}
}

View File

@ -1,18 +0,0 @@
package org.embeddedt.modernfix.fabric.mixin.feature.measure_time;
import net.minecraft.client.Minecraft;
import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Minecraft.class)
@ClientOnlyMixin
public class MinecraftMixin_Fabric {
@Inject(method = "doWorldLoad", at = @At("HEAD"))
private void recordWorldLoadStart(CallbackInfo ci) {
ModernFixClient.worldLoadStartTime = System.nanoTime();
}
}

View File

@ -1,30 +0,0 @@
package org.embeddedt.modernfix.fabric.mixin.perf.faster_command_suggestions;
import com.mojang.brigadier.suggestion.Suggestion;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.List;
/**
* Simple hack-fix to limit the number of suggestions being processed. Not a perfect fix but mitigates lag decently
* on an i3-4150.
*/
@Mixin(SuggestionsBuilder.class)
public class SuggestionsBuilderMixin {
@Unique
private static final int MAX_SUGGESTIONS = 10000;
@Shadow(remap = false) @Final @Mutable
private List<Suggestion> result;
@Redirect(method = "*", at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z"), require = 0)
private <T> boolean addIfFits(List<T> list, T entry) {
if(list != result || list.size() < MAX_SUGGESTIONS) {
return list.add(entry);
}
return false;
}
}

View File

@ -1,32 +0,0 @@
package org.embeddedt.modernfix.fabric.mixin.safety;
import com.mojang.blaze3d.platform.NativeImage;
import net.minecraft.client.renderer.texture.DynamicTexture;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(DynamicTexture.class)
@ClientOnlyMixin
public class DynamicTextureMixin {
@Shadow @Nullable private NativeImage pixels;
private Exception closeTrace;
@Inject(method = "method_22793", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/texture/DynamicTexture;pixels:Lcom/mojang/blaze3d/platform/NativeImage;", ordinal = 0))
private void checkNullPixels(CallbackInfo ci) {
if(pixels == null) {
ModernFix.LOGGER.error("Attempted to upload null texture! This is not allowed, closed here", closeTrace);
}
}
@Inject(method = "close", at = @At("HEAD"))
private void storeCloseTrace(CallbackInfo ci) {
closeTrace = new Exception();
}
}

View File

@ -1,13 +0,0 @@
package org.embeddedt.modernfix.fabric.modmenu;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
import org.embeddedt.modernfix.screen.ModernFixConfigScreen;
@SuppressWarnings("unused")
public class ModernFixModMenuApiImpl implements ModMenuApi {
@Override
public ConfigScreenFactory<ModernFixConfigScreen> getModConfigScreenFactory() {
return ModernFixConfigScreen::new;
}
}

View File

@ -1,116 +0,0 @@
package org.embeddedt.modernfix.platform.fabric;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.mojang.brigadier.CommandDispatcher;
import net.fabricmc.api.EnvType;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.metadata.CustomValue;
import net.fabricmc.loader.api.metadata.ModMetadata;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.embeddedt.modernfix.ModernFixFabric;
import org.embeddedt.modernfix.api.constants.IntegrationConstants;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
import org.embeddedt.modernfix.spark.SparkLaunchProfiler;
import org.embeddedt.modernfix.util.CommonModUtil;
import org.objectweb.asm.tree.ClassNode;
import java.nio.file.Path;
import java.util.Map;
import java.util.function.Consumer;
public class ModernFixPlatformHooksImpl implements ModernFixPlatformHooks {
public boolean isClient() {
return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT;
}
public boolean isDedicatedServer() {
return FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER;
}
private static final String verString = FabricLoader.getInstance().getModContainer("modernfix")
.map(mfModContainer -> mfModContainer.getMetadata().getVersion().getFriendlyString())
.orElse("[unknown]");
public String getVersionString() {
return verString;
}
public boolean modPresent(String modId) {
return FabricLoader.getInstance().getModContainer(modId).isPresent();
}
public boolean isDevEnv() {
return FabricLoader.getInstance().isDevelopmentEnvironment();
}
public MinecraftServer getCurrentServer() {
return ModernFixFabric.theServer.get();
}
public boolean isEarlyLoadingNormally() {
return true;
}
public boolean isLoadingNormally() {
return true;
}
public Path getGameDirectory() {
return FabricLoader.getInstance().getGameDir();
}
public void sendPacket(ServerPlayer player, CustomPacketPayload packet) {
//PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), packet);
}
public void injectPlatformSpecificHacks() {
}
public void applyASMTransformers(String mixinClassName, ClassNode targetClass) {
}
public void onServerCommandRegister(Consumer<CommandDispatcher<CommandSourceStack>> handler) {
if(FabricLoader.getInstance().isModLoaded("fabric-command-api-v2"))
CommandRegistrationCallback.EVENT.register((dispatcher, arg, env) -> handler.accept(dispatcher));
}
private static Multimap<String, String> modOptions;
public Multimap<String, String> getCustomModOptions() {
if(modOptions == null) {
modOptions = ArrayListMultimap.create();
for (ModContainer container : FabricLoader.getInstance().getAllMods()) {
ModMetadata meta = container.getMetadata();
if (meta.containsCustomValue(IntegrationConstants.INTEGRATIONS_KEY)) {
CustomValue integrations = meta.getCustomValue(IntegrationConstants.INTEGRATIONS_KEY);
if (integrations.getType() != CustomValue.CvType.OBJECT) {
continue;
}
for (Map.Entry<String, CustomValue> entry : integrations.getAsObject()) {
if(entry.getValue().getType() != CustomValue.CvType.STRING)
continue;
modOptions.put(entry.getKey(), entry.getValue().getAsString());
}
}
}
}
return modOptions;
}
public void onLaunchComplete() {
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnFabric")) {
CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.stop("launch"), "Failed to stop profiler");
}
}
public String getPlatformName() {
return "Fabric";
}
}

View File

@ -1,50 +0,0 @@
{
"schemaVersion": 1,
"id": "modernfix",
"version": "${version}",
"name": "ModernFix",
"description": "Egregious, yet effective performance improvements for modern Minecraft",
"authors": [
"embeddedt"
],
"contact": {
"sources": "https://github.com/embeddedt/ModernFix",
"homepage": "https://modrinth.com/mod/modernfix",
"issues": "https://github.com/embeddedt/ModernFix/issues"
},
"license": "LGPL-3.0",
"icon": "icon.png",
"custom": {
"modmenu": {
"links": {
"modmenu.kofi": "https://ko-fi.com/embeddedt",
"modmenu.github_releases": "https://github.com/embeddedt/ModernFix/releases",
"modmenu.curseforge": "https://www.curseforge.com/minecraft/mc-mods/modernfix"
}
}
},
"environment": "*",
"entrypoints": {
"main": [
"org.embeddedt.modernfix.ModernFixFabric"
],
"client": [
"org.embeddedt.modernfix.ModernFixClientFabric"
],
"modmenu": [ "org.embeddedt.modernfix.fabric.modmenu.ModernFixModMenuApiImpl" ],
"preLaunch": [
"org.embeddedt.modernfix.ModernFixPreLaunchFabric"
]
},
"mixins": [
"modernfix-fabric.mixins.json",
"modernfix-common.mixins.json"
],
"depends": {
"minecraft": ">=1.16.2",
"fabricloader": ">=0.16.10"
},
"breaks": {
"dashloader": "<5.0.0-beta.1"
}
}

View File

@ -1,97 +0,0 @@
package net.minecraft.world.level.block.state;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.EmptyBlockGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import org.embeddedt.modernfix.duck.IBlockState;
import org.embeddedt.modernfix.testing.util.BootstrapMinecraft;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
@BootstrapMinecraft
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BlockStateCacheTest {
@BeforeEach
public void rebuildCache() {
Blocks.rebuildCache();
}
/**
* Initially, the cache should be invalid, and null.
*/
@Test
@Order(1)
public void testCacheNullInitially() {
BlockState stoneBlock = Blocks.STONE.defaultBlockState();
assertTrue(((IBlockState)stoneBlock).isCacheInvalid());
assertNull(stoneBlock.cache);
// make sure lazy cache correctly handles solid
assertTrue(stoneBlock.isSolid());
}
/**
* When an API that needs the cache is called, it should be built and the invalid flag
* becomes false.
*/
@Test
@Order(2)
public void testCacheBuiltByRequest() {
BlockState stoneBlock = Blocks.STONE.defaultBlockState();
stoneBlock.getCollisionShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
assertFalse(((IBlockState)stoneBlock).isCacheInvalid());
assertNotNull(stoneBlock.cache);
}
/**
* When a second rebuild occurs, the invalid flag should be set to true, but the old cache
* is not set to null, in order to prevent NPEs if a second thread is accessing the cache
* when this takes place.
*/
@Test
@Order(3)
public void testCacheInvalidatedByLateRebuild() {
BlockState stoneBlock = Blocks.STONE.defaultBlockState();
stoneBlock.getCollisionShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
Blocks.rebuildCache();
assertTrue(((IBlockState)stoneBlock).isCacheInvalid());
assertNotNull(stoneBlock.cache);
}
/**
* Tests that the fluidState and isRandomlyTicking caching fields added by Mojang to blockstates are correctly
* handled by the dynamic cache system.
*/
@Test
@Order(4)
public void testExtraFieldCachingCorrect() {
Block[] blocksToCheck = new Block[] { Blocks.WATER, Blocks.FARMLAND };
for(Block block : blocksToCheck) {
for(BlockState state : block.getStateDefinition().getPossibleStates()) {
// check that the fluid states match
assertEquals(block.getFluidState(state), state.getFluidState(), "mismatched fluid state on " + state);
// check that random ticking flag matches
assertEquals(block.isRandomlyTicking(state), state.isRandomlyTicking(), "mismatched random tick state on " + state);
}
}
}
/*
@Test
@Order(5)
public void checkRecursiveFluidState() {
Block b = new Block(BlockBehaviour.Properties.copy(Blocks.STONE)) {
@Override
public FluidState getFluidState(BlockState state) {
return state.getFluidState();
}
};
BlockState state = b.getStateDefinition().any();
((IBlockState)state).clearCache();
// this should not throw
state.getFluidState();
}
*/
}

View File

@ -1,26 +0,0 @@
package org.embeddedt.modernfix.dynamicresources;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.BuiltInModel;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import org.embeddedt.modernfix.testing.util.BootstrapMinecraft;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@BootstrapMinecraft
public class DynamicModelCacheTest {
@Test
public void testCacheReturnsNullForNullGetter() {
DynamicModelCache<Item> cache = new DynamicModelCache(k -> null, true);
assertNull(cache.get(Items.STONE));
}
@Test
public void testCacheFunctions() {
BakedModel model = new BuiltInModel(null, null, null, false);
DynamicModelCache<Item> cache = new DynamicModelCache(k -> model, true);
assertEquals(model, cache.get(Items.STONE));
}
}

View File

@ -1,14 +0,0 @@
package org.embeddedt.modernfix.testing.util;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ExtendWith({ BootstrapMinecraftExtension.class })
public @interface BootstrapMinecraft {
}

View File

@ -1,38 +0,0 @@
package org.embeddedt.modernfix.testing.util;
import net.minecraft.SharedConstants;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.server.Bootstrap;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import java.lang.reflect.Field;
import java.util.IdentityHashMap;
/**
* Simple extension to run vanilla bootstrap, inspired by AE2.
*/
public class BootstrapMinecraftExtension implements Extension, BeforeAllCallback, AfterAllCallback {
@Override
public void beforeAll(ExtensionContext context) throws Exception {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
// Allow blocks to be created in tests
Field field = MappedRegistry.class.getDeclaredField("unregisteredIntrusiveHolders");
field.setAccessible(true);
if(field.get(BuiltInRegistries.BLOCK) == null) {
field.set(BuiltInRegistries.BLOCK, new IdentityHashMap<>());
field = MappedRegistry.class.getDeclaredField("frozen");
field.setAccessible(true);
field.setBoolean(BuiltInRegistries.BLOCK, false);
}
}
@Override
public void afterAll(ExtensionContext context) throws Exception {
}
}

View File

@ -1,45 +0,0 @@
plugins {
id 'dev.architectury.loom'
}
loom {
accessWidenerPath = project(":common").loom.accessWidenerPath
runs {
client {
vmArgs "-Xmx8G"
property("modernfix.config.mixin.perf.blast_search_trees", "true")
property("modernfix.config.mixin.perf.dynamic_resources", "true")
property("modernfix.config.mixin.perf.dynamic_block_codecs", "true")
}
}
}
dependencies {
minecraft "com.mojang:minecraft:${rootProject.minecraft_version}"
mappings loom.layered() {
officialMojangMappings()
}
modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
modImplementation(fabricApi.module("fabric-resource-loader-v0", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modImplementation(fabricApi.module("fabric-models-v0", "0.84.0+1.20.1")) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modImplementation(fabricApi.module("fabric-registry-sync-v0", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modImplementation(fabricApi.module("fabric-renderer-api-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modImplementation("net.fabricmc.fabric-api:fabric-rendering-data-attachment-v1:0.3.36+92a0d36777") { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modImplementation(fabricApi.module("fabric-rendering-fluids-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modRuntimeOnly(fabricApi.module("fabric-renderer-indigo", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
implementation project(path: ":common", configuration: "namedElements")
implementation project(path: ":fabric", configuration: "namedElements")
}
processResources {
inputs.property "version", project.version
filesMatching("fabric.mod.json") {
expand "version": project.version
}
}
// Make genSources do nothing in this project
project.gradle.startParameter.excludedTaskNames.add("genSources")

Some files were not shown because too many files have changed in this diff Show More