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] 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; }