From 01fb138c8abe3cf604331ae3f3e28a904419c3a5 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 27 Dec 2025 19:33:30 -0500 Subject: [PATCH 1/8] Make maps provided to ModifyBakingResult mutable --- .../dynresources/DynamicModelSystem.java | 2 +- .../dynresources/DynamicRegistryMap.java | 161 ++++++++++++++++++ 2 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/embeddedt/modernfix/dynresources/DynamicRegistryMap.java diff --git a/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java b/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java index 0a1c8180..3bb117e3 100644 --- a/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java +++ b/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java @@ -172,7 +172,7 @@ public class DynamicModelSystem { } } }); - return Maps.asMap(input.keySet(), k -> { + return new DynamicRegistryMap<>(input.keySet(),k -> { if (k != null) { Object value = bakedCache.getUnchecked(k); if (value == NULL_BAKED) { diff --git a/src/main/java/org/embeddedt/modernfix/dynresources/DynamicRegistryMap.java b/src/main/java/org/embeddedt/modernfix/dynresources/DynamicRegistryMap.java new file mode 100644 index 00000000..65c8e6ef --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/dynresources/DynamicRegistryMap.java @@ -0,0 +1,161 @@ +package org.embeddedt.modernfix.dynresources; + +import com.google.common.collect.Collections2; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * A map that behaves like Guava's Maps.asMap but allows for additional entries to be written that override the backing + * map's entries. + */ +public final class DynamicRegistryMap implements Map { + private static final Object NULL_OVERRIDE = new Object(); + private final Set originalKeys; + private final Function fallbackGetter; + private final ConcurrentHashMap overrides; + private final EntrySet entrySet; + + public DynamicRegistryMap(Set originalKeys, Function fallbackGetter) { + this.originalKeys = originalKeys; + this.fallbackGetter = fallbackGetter; + this.overrides = new ConcurrentHashMap<>(); + this.entrySet = new EntrySet(); + } + + @Override + public int size() { + return originalKeys.size(); + } + + @Override + public boolean isEmpty() { + return originalKeys.isEmpty(); + } + + @Override + public boolean containsKey(Object o) { + if (o == null) { + return false; + } + var override = overrides.get(o); + if (override == NULL_OVERRIDE) { + return false; + } + return override != null || originalKeys.contains(o); + } + + @Override + public boolean containsValue(Object o) { + if (o == null || o == NULL_OVERRIDE) { + return false; + } + return overrides.containsValue(o); + } + + @Override + public V get(Object o) { + Object value = overrides.get(o); + if (value == NULL_OVERRIDE) { + return null; + } else if (value != null) { + return (V) value; + } else { + return fallbackGetter.apply((K)o); + } + } + + @Override + public @Nullable V put(K k, V v) { + if (v == null) { + return remove(k); + } + overrides.put(k, v); + return null; + } + + @Override + public V remove(Object o) { + overrides.put((K)o, NULL_OVERRIDE); + return null; + } + + @Override + public void putAll(@NotNull Map map) { + map.forEach(this::put); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public @NotNull Set keySet() { + return Collections.unmodifiableSet(originalKeys); + } + + @Override + public @NotNull Collection values() { + return Collections2.transform(originalKeys, this::get); + } + + @Override + public @NotNull Set> entrySet() { + return this.entrySet; + } + + private class ModelEntry implements Map.Entry { + private final K key; + + private ModelEntry(K key) { + this.key = key; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return get(key); + } + + @Override + public V setValue(V value) { + return put(key, value); + } + } + + private class EntrySet extends AbstractSet> { + @Override + public Iterator> iterator() { + var iterator = originalKeys.iterator(); + return new Iterator<>() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Entry next() { + return new ModelEntry(iterator.next()); + } + }; + } + + @Override + public int size() { + return DynamicRegistryMap.this.size(); + } + } +} From df58c05d75d9c601f75e4254a26235e2557dbf8f Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 27 Dec 2025 20:33:26 -0500 Subject: [PATCH 2/8] Add AT --- src/main/resources/META-INF/accesstransformer.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 89dd0f65..6730f70c 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -56,6 +56,7 @@ public net.minecraft.client.KeyMapping ALL public net.minecraft.client.resources.model.BlockStateModelLoader$LoadedBlockModelDefinition public net.minecraft.client.resources.model.ModelManager$ResolvedModels +public net.minecraft.client.resources.model.ModelManager$ResolvedModels (Lnet/minecraft/client/resources/model/ResolvedModel;Ljava/util/Map;)V public net.minecraft.client.resources.model.ModelDiscovery$ModelWrapper public net.minecraft.client.resources.model.ModelDiscovery$ModelWrapper ModelWrapper(Lnet/minecraft/resources/Identifier;Lnet/minecraft/client/resources/model/UnbakedModel;Z)V public net.minecraft.client.resources.model.ModelDiscovery createAndQueueWrapper(Lnet/minecraft/resources/Identifier;Lnet/minecraft/client/resources/model/UnbakedModel;)Lnet/minecraft/client/resources/model/ModelDiscovery$ModelWrapper; \ No newline at end of file From a631e17aab993c5ab823cd7550e92ba554b481a5 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 28 Dec 2025 13:23:14 -0500 Subject: [PATCH 3/8] Reimplement optimized lookup map for block state models --- .../dynamic_resources/MixinBlockState.java | 26 +++++ .../dynamic_resources/MixinModelManager.java | 6 +- .../dynresources/BlockStateModelMap.java | 105 ++++++++++++++++++ .../dynresources/DynamicModelSystem.java | 6 +- 4 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinBlockState.java create mode 100644 src/main/java/org/embeddedt/modernfix/dynresources/BlockStateModelMap.java diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinBlockState.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinBlockState.java new file mode 100644 index 00000000..2032a072 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinBlockState.java @@ -0,0 +1,26 @@ +package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources; + +import net.minecraft.client.renderer.block.model.BlockStateModel; +import net.minecraft.world.level.block.state.BlockBehaviour; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.embeddedt.modernfix.duck.IModelHoldingBlockState; +import org.spongepowered.asm.mixin.Mixin; + +import java.lang.ref.SoftReference; + +@Mixin(BlockBehaviour.BlockStateBase.class) +@ClientOnlyMixin +public class MixinBlockState implements IModelHoldingBlockState { + private volatile SoftReference mfix$model; + + @Override + public BlockStateModel mfix$getModel() { + var ref = mfix$model; + return ref != null ? ref.get() : null; + } + + @Override + public void mfix$setModel(BlockStateModel model) { + mfix$model = model != null ? new SoftReference<>(model) : null; + } +} diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinModelManager.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinModelManager.java index eaec796a..4822334f 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinModelManager.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinModelManager.java @@ -12,6 +12,7 @@ import net.minecraft.resources.Identifier; import net.minecraft.server.packs.resources.Resource; import net.minecraft.world.level.block.state.BlockState; import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.embeddedt.modernfix.dynresources.BlockStateModelMap; import org.embeddedt.modernfix.dynresources.DynamicModelSystem; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; @@ -62,8 +63,7 @@ public class MixinModelManager { */ @Overwrite private static Map createBlockStateToModelDispatch(Map blockStateModels, BlockStateModel missingModel) { - return Maps.asMap(DynamicModelSystem.getAllBlockStates(), state -> { - return blockStateModels.getOrDefault(state, missingModel); - }); + BlockStateModelMap.resetCache(); + return new BlockStateModelMap(blockStateModels, missingModel); } } diff --git a/src/main/java/org/embeddedt/modernfix/dynresources/BlockStateModelMap.java b/src/main/java/org/embeddedt/modernfix/dynresources/BlockStateModelMap.java new file mode 100644 index 00000000..fedf3c9a --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/dynresources/BlockStateModelMap.java @@ -0,0 +1,105 @@ +package org.embeddedt.modernfix.dynresources; + +import net.minecraft.client.renderer.block.model.BlockStateModel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import org.embeddedt.modernfix.duck.IModelHoldingBlockState; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * Optimized blockstate->model dispatch map that stores the models directly on the block state objects using a helper + * field. This relies on the fact that Minecraft should only have one model map in flight at a time. + */ +public record BlockStateModelMap(Map modelMap, + BlockStateModel fallbackModel) implements Map { + + @Override + public int size() { + return Block.BLOCK_STATE_REGISTRY.size(); + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean containsKey(Object o) { + return o instanceof BlockState; + } + + @Override + public boolean containsValue(Object o) { + return modelMap.containsValue(o); + } + + @Override + public BlockStateModel get(Object o) { + if (o instanceof IModelHoldingBlockState modelHolder) { + BlockStateModel model = modelHolder.mfix$getModel(); + + if(model != null) { + return model; + } + + model = modelMap.getOrDefault(o, fallbackModel); + modelHolder.mfix$setModel(model); + return model; + } else { + return modelMap.getOrDefault(o, fallbackModel); + } + } + + @Override + public @Nullable BlockStateModel put(BlockState blockState, BlockStateModel blockStateModel) { + var oldModel = modelMap.put(blockState, blockStateModel); + ((IModelHoldingBlockState)blockState).mfix$setModel(null); + return oldModel; + } + + @Override + public BlockStateModel remove(Object o) { + var old = modelMap.remove(o); + if (o instanceof IModelHoldingBlockState holder) { + holder.mfix$setModel(null); + } + return old; + } + + @Override + public void putAll(@NotNull Map map) { + map.forEach(this::put); + } + + @Override + public void clear() { + modelMap.clear(); + resetCache(); + } + + @Override + public @NotNull Set keySet() { + return modelMap.keySet(); + } + + @Override + public @NotNull Collection values() { + return modelMap.values(); + } + + @Override + public @NotNull Set> entrySet() { + return modelMap.entrySet(); + } + + public static void resetCache() { + for (var state : Block.BLOCK_STATE_REGISTRY) { + ((IModelHoldingBlockState) state).mfix$setModel(null); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java b/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java index 3bb117e3..d9d4b814 100644 --- a/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java +++ b/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java @@ -11,6 +11,7 @@ import it.unimi.dsi.fastutil.objects.ObjectSet; import it.unimi.dsi.fastutil.objects.ObjectSets; import it.unimi.dsi.fastutil.objects.ReferenceSets; import net.minecraft.client.color.block.BlockColors; +import net.minecraft.client.renderer.block.model.BlockStateModel; import net.minecraft.client.renderer.block.model.ItemModelGenerator; import net.minecraft.client.resources.model.BlockStateModelLoader; import net.minecraft.client.resources.model.ClientItemInfoLoader; @@ -35,6 +36,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; +import java.util.function.Function; import java.util.stream.Collectors; public class DynamicModelSystem { @@ -172,13 +174,13 @@ public class DynamicModelSystem { } } }); - return new DynamicRegistryMap<>(input.keySet(),k -> { + return new DynamicRegistryMap<>(input.keySet(), k -> { if (k != null) { Object value = bakedCache.getUnchecked(k); if (value == NULL_BAKED) { value = null; } - return (V)value; + return (V) value; } else { return null; } From 0a68a6923a5f28a97fbc59cbbac1bb8ec87a370f Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 28 Dec 2025 19:02:46 -0500 Subject: [PATCH 4/8] Reimplement deduplicate_wall_shapes --- TODO.txt | 1 - .../WallBlockMixin.java | 54 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/perf/deduplicate_wall_shapes/WallBlockMixin.java diff --git a/TODO.txt b/TODO.txt index 93057ed1..4807259e 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,4 +4,3 @@ - Check if BlockStateData patch is still needed in compact_mojang_registries - Check if faster_texture_stitching is still worthwhile with 21.x changes to the stitcher - Sculk deadlock fix looks unnecessary since Mojang is careful about when they send the event -- Rewrite deduplicate_wall_shapes diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/deduplicate_wall_shapes/WallBlockMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/deduplicate_wall_shapes/WallBlockMixin.java new file mode 100644 index 00000000..14ecba4c --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/deduplicate_wall_shapes/WallBlockMixin.java @@ -0,0 +1,54 @@ +package org.embeddedt.modernfix.common.mixin.perf.deduplicate_wall_shapes; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.mojang.datafixers.util.Pair; +import net.minecraft.core.Direction; +import net.minecraft.world.level.block.WallBlock; +import net.minecraft.world.phys.shapes.VoxelShape; +import org.joml.Vector3d; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author embeddedt + * @reason Avoid excessive memory usage in modpacks that add many variations of wall blocks. The strategy here requires + * multiple memoization maps, but is also the simplest way to do it without having to literally replicate the vanilla + * logic (as that uses blockstates rather than property maps). + */ +@Mixin(WallBlock.class) +public class WallBlockMixin { + @Unique + private static final ConcurrentHashMap COLUMN_CACHE = new ConcurrentHashMap<>(); + @Unique + private static final ConcurrentHashMap, VoxelShape> BOX_Z_CACHE = new ConcurrentHashMap<>(); + @Unique + private static final ConcurrentHashMap> ROTATE_HORZ_CACHE = new ConcurrentHashMap<>(); + @Unique + private static final ConcurrentHashMap, VoxelShape> OR_CACHE = new ConcurrentHashMap<>(); + + @WrapOperation(method = "makeShapes", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/Block;column(DDD)Lnet/minecraft/world/phys/shapes/VoxelShape;")) + private VoxelShape memoizeColumn(double sizeXZ, double minY, double postHeight, Operation original) { + return COLUMN_CACHE.computeIfAbsent(new Vector3d(sizeXZ, minY, postHeight), l -> original.call(sizeXZ, minY, postHeight)); + } + + @WrapOperation(method = "makeShapes", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/Block;boxZ(DDDDD)Lnet/minecraft/world/phys/shapes/VoxelShape;")) + private VoxelShape memoizeBoxZ(double sizeX, double minY, double maxY, double minZ, double maxZ, Operation original) { + return BOX_Z_CACHE.computeIfAbsent(List.of(sizeX, minY, maxY, minZ, maxZ), l -> original.call(sizeX, minY, maxY, minZ, maxZ)); + } + + @WrapOperation(method = "makeShapes", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/shapes/Shapes;rotateHorizontal(Lnet/minecraft/world/phys/shapes/VoxelShape;)Ljava/util/Map;")) + private Map memoizeRotateHorizontal(VoxelShape north, Operation> original) { + return ROTATE_HORZ_CACHE.computeIfAbsent(north, l -> original.call(north)); + } + + @WrapOperation(method = "lambda$makeShapes$0", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/shapes/Shapes;or(Lnet/minecraft/world/phys/shapes/VoxelShape;Lnet/minecraft/world/phys/shapes/VoxelShape;)Lnet/minecraft/world/phys/shapes/VoxelShape;")) + private static VoxelShape memoizeOr(VoxelShape one, VoxelShape two, Operation original) { + return OR_CACHE.computeIfAbsent(Pair.of(one, two), l -> original.call(one, two)); + } +} From 617c50ffff2bc616d5f7f314b3ed58d89da65079 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 28 Dec 2025 19:02:59 -0500 Subject: [PATCH 5/8] Add blockstate cache baking progress to the loading screen --- .../BlockCallbacksMixin.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/feature/registry_event_progress/BlockCallbacksMixin.java diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/feature/registry_event_progress/BlockCallbacksMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/feature/registry_event_progress/BlockCallbacksMixin.java new file mode 100644 index 00000000..2a4e8aff --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/feature/registry_event_progress/BlockCallbacksMixin.java @@ -0,0 +1,50 @@ +package org.embeddedt.modernfix.common.mixin.feature.registry_event_progress; + +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; +import net.minecraft.world.level.block.Block; +import net.neoforged.fml.loading.progress.ProgressMeter; +import net.neoforged.fml.loading.progress.StartupNotificationManager; +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.Set; + +@Mixin(targets = {"net/neoforged/neoforge/registries/NeoForgeRegistryCallbacks$BlockCallbacks"}) +public class BlockCallbacksMixin { + @Shadow @Final @Mutable + private Set addedBlocks; + + /** + * @author embeddedt + * @reason Use an ordered set to make the baking order more predictable for users watching the splash screen + */ + @Inject(method = "", at = @At("RETURN")) + private void useOrderedSet(CallbackInfo ci) { + this.addedBlocks = new ReferenceLinkedOpenHashSet<>(this.addedBlocks); + } + + @Inject(method = "onBake", at = @At("HEAD")) + private void startBakeProgress(CallbackInfo ci, @Share("meter") LocalRef meter) { + meter.set(StartupNotificationManager.prependProgressBar("Build blockstate caches", addedBlocks.size())); + } + + @Inject(method = "onBake", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/Block;getStateDefinition()Lnet/minecraft/world/level/block/state/StateDefinition;", ordinal = 0)) + private void showBakeProgressPerBlock(CallbackInfo ci, @Local(ordinal = 0) Block block, @Share("meter") LocalRef meter) { + var id = block.builtInRegistryHolder().getKey().identifier(); + meter.get().label("Build blockstate caches - " + id.toString()); + meter.get().increment(); + } + + @Inject(method = "onBake", at = @At(value = "INVOKE", target = "Ljava/util/Set;clear()V", ordinal = 0)) + private void stopBakeProgress(CallbackInfo ci, @Share("meter") LocalRef meter) { + meter.get().complete(); + } +} From dbf343bf910d22bff13d670c61343f97733b89ff Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 28 Dec 2025 19:24:01 -0500 Subject: [PATCH 6/8] Reimplement mixin.bugfix.entity_pose_stack --- .../AvatarRendererMixin.java | 29 +++++++++++++++++++ .../LivingEntityRendererMixin.java | 29 +++++++++++++++++++ .../entity_pose_stack/PoseStackAccessor.java | 13 +++++++++ 3 files changed, 71 insertions(+) create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/entity_pose_stack/AvatarRendererMixin.java create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/entity_pose_stack/LivingEntityRendererMixin.java create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/entity_pose_stack/PoseStackAccessor.java diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/entity_pose_stack/AvatarRendererMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/entity_pose_stack/AvatarRendererMixin.java new file mode 100644 index 00000000..a47aa8d8 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/entity_pose_stack/AvatarRendererMixin.java @@ -0,0 +1,29 @@ +package org.embeddedt.modernfix.common.mixin.bugfix.entity_pose_stack; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.renderer.entity.player.AvatarRenderer; +import net.neoforged.bus.api.Event; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.neoforge.client.event.RenderPlayerEvent; +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.Redirect; + +@Mixin(AvatarRenderer.class) +@ClientOnlyMixin +public class AvatarRendererMixin { + @Redirect(method = "submit(Lnet/minecraft/client/renderer/entity/state/AvatarRenderState;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;Lnet/minecraft/client/renderer/state/CameraRenderState;)V", at = @At(value = "INVOKE", target = "Lnet/neoforged/bus/api/IEventBus;post(Lnet/neoforged/bus/api/Event;)Lnet/neoforged/bus/api/Event;", ordinal = 0)) + private Event fireCheckingPoseStack(IEventBus instance, Event event) { + PoseStack stack = ((RenderPlayerEvent)event).getPoseStack(); + int size = ((PoseStackAccessor)stack).mfix$getLastIndex(); + instance.post(event); + if (((RenderPlayerEvent.Pre)event).isCanceled()) { + // Pop the stack if someone pushed it in the event + while (((PoseStackAccessor)stack).mfix$getLastIndex() > size) { + stack.popPose(); + } + } + return event; + } +} diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/entity_pose_stack/LivingEntityRendererMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/entity_pose_stack/LivingEntityRendererMixin.java new file mode 100644 index 00000000..f9c19e9b --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/entity_pose_stack/LivingEntityRendererMixin.java @@ -0,0 +1,29 @@ +package org.embeddedt.modernfix.common.mixin.bugfix.entity_pose_stack; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.renderer.entity.LivingEntityRenderer; +import net.neoforged.neoforge.client.event.RenderLivingEvent; +import net.neoforged.bus.api.Event; +import net.neoforged.bus.api.IEventBus; +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.Redirect; + +@Mixin(LivingEntityRenderer.class) +@ClientOnlyMixin +public class LivingEntityRendererMixin { + @Redirect(method = "submit(Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;Lnet/minecraft/client/renderer/state/CameraRenderState;)V", at = @At(value = "INVOKE", target = "Lnet/neoforged/bus/api/IEventBus;post(Lnet/neoforged/bus/api/Event;)Lnet/neoforged/bus/api/Event;", ordinal = 0)) + private Event fireCheckingPoseStack(IEventBus instance, Event event) { + PoseStack stack = ((RenderLivingEvent)event).getPoseStack(); + int size = ((PoseStackAccessor)stack).mfix$getLastIndex(); + instance.post(event); + if (((RenderLivingEvent.Pre)event).isCanceled()) { + // Pop the stack if someone pushed it in the event + while (((PoseStackAccessor)stack).mfix$getLastIndex() > size) { + stack.popPose(); + } + } + return event; + } +} \ No newline at end of file diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/entity_pose_stack/PoseStackAccessor.java b/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/entity_pose_stack/PoseStackAccessor.java new file mode 100644 index 00000000..a459165c --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/entity_pose_stack/PoseStackAccessor.java @@ -0,0 +1,13 @@ +package org.embeddedt.modernfix.common.mixin.bugfix.entity_pose_stack; + +import com.mojang.blaze3d.vertex.PoseStack; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(PoseStack.class) +@ClientOnlyMixin +public interface PoseStackAccessor { + @Accessor("lastIndex") + int mfix$getLastIndex(); +} From e9836ceac6f30910e721aeb77ed211d3a69c48cf Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 28 Dec 2025 19:24:11 -0500 Subject: [PATCH 7/8] Disable dynamic resources debug mode by default --- .../embeddedt/modernfix/dynresources/DynamicModelSystem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java b/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java index d9d4b814..9db05520 100644 --- a/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java +++ b/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java @@ -43,7 +43,7 @@ public class DynamicModelSystem { private static final FileToIdConverter MODEL_LISTER = FileToIdConverter.json("models"); private static final FileToIdConverter BLOCKSTATE_LISTER = FileToIdConverter.json("blockstates"); - public static final boolean DEBUG_DYNAMIC_MODEL_LOADING = true; // Boolean.getBoolean("modernfix.debugDynamicModelLoading"); + public static final boolean DEBUG_DYNAMIC_MODEL_LOADING = Boolean.getBoolean("modernfix.debugDynamicModelLoading"); public static Map createDynamicUnbakedModelMap(Map resourceMap) { LoadingCache unbakedModelCache = CacheBuilder.newBuilder().softValues().maximumSize(1000).build(new CacheLoader<>() { From 0f992164171baae59e99cb3184e775c5790dc947 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:42:00 -0500 Subject: [PATCH 8/8] Handle vanilla mapping blockstates to fake StateDefinitions Closes #621 Co-authored-by: coredex-source --- .../BlockStateDefinitionsAccessor.java | 21 +++++++ .../dynresources/DisjointSetUnion.java | 57 +++++++++++++++++++ .../dynresources/DynamicModelSystem.java | 17 +++++- 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/BlockStateDefinitionsAccessor.java create mode 100644 src/main/java/org/embeddedt/modernfix/dynresources/DisjointSetUnion.java diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/BlockStateDefinitionsAccessor.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/BlockStateDefinitionsAccessor.java new file mode 100644 index 00000000..76d8cbe3 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/BlockStateDefinitionsAccessor.java @@ -0,0 +1,21 @@ +package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources; + +import net.minecraft.client.resources.model.BlockStateDefinitions; +import net.minecraft.resources.Identifier; +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.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(BlockStateDefinitions.class) +@ClientOnlyMixin +public interface BlockStateDefinitionsAccessor { + @Accessor("STATIC_DEFINITIONS") + static Map> getStaticDefinitions() { + throw new AssertionError(); + } +} \ No newline at end of file diff --git a/src/main/java/org/embeddedt/modernfix/dynresources/DisjointSetUnion.java b/src/main/java/org/embeddedt/modernfix/dynresources/DisjointSetUnion.java new file mode 100644 index 00000000..67312bf5 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/dynresources/DisjointSetUnion.java @@ -0,0 +1,57 @@ +package org.embeddedt.modernfix.dynresources; + +import com.google.common.collect.Iterators; + +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Set; + +/** + * Optimized alternative to {@link com.google.common.collect.Sets#union(Set, Set)} in cases where the sets + * are known to be disjoint. + * @param element type + */ +public class DisjointSetUnion extends AbstractSet { + private final Set set1, set2; + + public DisjointSetUnion(Set set1, Set set2) { + this.set1 = set1; + this.set2 = set2; + this.assertDisjoint(); + } + + private void assertDisjoint() { + Set iterate = set1.size() < set2.size() ? set1 : set2; + Set contains = set1 == iterate ? set2 : set1; + for (T obj : iterate) { + if (contains.contains(obj)) { + throw new IllegalArgumentException("Provided sets are not disjoint"); + } + } + } + + @Override + public Iterator iterator() { + return Iterators.concat(set1.iterator(), set2.iterator()); + } + + @Override + public int size() { + return set1.size() + set2.size(); + } + + @Override + public boolean remove(Object o) { + return set1.remove(o) || set2.remove(o); + } + + @Override + public boolean contains(Object o) { + return set1.contains(o) || set2.contains(o); + } + + @Override + public int hashCode() { + return set1.hashCode() + set2.hashCode(); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java b/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java index 9db05520..ecd0a8d1 100644 --- a/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java +++ b/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java @@ -4,6 +4,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.objects.AbstractObject2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; @@ -28,10 +29,13 @@ import net.minecraft.world.level.block.state.BlockState; import net.neoforged.neoforge.client.model.UnbakedModelParser; import net.neoforged.neoforge.client.model.standalone.StandaloneModelLoader; import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.common.mixin.perf.dynamic_resources.BlockStateDefinitionsAccessor; import org.embeddedt.modernfix.common.mixin.perf.dynamic_resources.IdMapperAccessor; import org.embeddedt.modernfix.common.mixin.perf.dynamic_resources.ModelDiscoveryAccessor; import java.io.Reader; +import java.util.AbstractSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -85,8 +89,17 @@ public class DynamicModelSystem { return entryLoader.loadEntry(file, resources); } }); - return new BlockStateModelLoader.LoadedModels(Maps.asMap(getAllBlockStates(), state -> { - var identifier = state.getBlock().builtInRegistryHolder().getKey().identifier(); + var staticDefinitions = BlockStateDefinitionsAccessor.getStaticDefinitions(); + var staticIdentifiers = staticDefinitions.entrySet() + .stream() + .flatMap(e -> e.getValue().getPossibleStates().stream().map(s -> Map.entry(s, e.getKey()))) + .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); + var blockStateSet = new DisjointSetUnion<>(getAllBlockStates(), staticIdentifiers.keySet()); + return new BlockStateModelLoader.LoadedModels(Maps.asMap(blockStateSet, state -> { + var identifier = staticIdentifiers.get(state); + if (identifier == null) { + identifier = state.getBlock().builtInRegistryHolder().getKey().identifier(); + } var loadedModels = definitionCache.getUnchecked(identifier); return loadedModels.models().get(state); }));