From 9b35236b858dc0c2e17a8828b7cc3e9ad2b46874 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 27 Dec 2025 12:08:02 -0500 Subject: [PATCH] Begin reimplementing dynamic resources Currently only unbaked models & blockstate definitions are dynamic --- .../dynamic_resources/IdMapperAccessor.java | 12 +++ .../MixinBlockStateModelLoader.java | 39 ++++++++++ .../dynamic_resources/MixinModelManager.java | 30 ++++++++ .../dynresources/DynamicModelSystem.java | 73 +++++++++++++++++++ .../resources/META-INF/accesstransformer.cfg | 2 + 5 files changed, 156 insertions(+) create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/IdMapperAccessor.java create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinBlockStateModelLoader.java create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinModelManager.java create mode 100644 src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/IdMapperAccessor.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/IdMapperAccessor.java new file mode 100644 index 00000000..4c6e0d01 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/IdMapperAccessor.java @@ -0,0 +1,12 @@ +package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources; + +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import net.minecraft.core.IdMapper; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(IdMapper.class) +public interface IdMapperAccessor { + @Accessor("tToId") + Reference2IntMap getReferenceMap(); +} diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinBlockStateModelLoader.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinBlockStateModelLoader.java new file mode 100644 index 00000000..247f6ca1 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinBlockStateModelLoader.java @@ -0,0 +1,39 @@ +package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources; + +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.client.resources.model.BlockStateModelLoader; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.Identifier; +import net.minecraft.server.packs.resources.Resource; +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.dynresources.DynamicModelSystem; +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 org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +@Mixin(BlockStateModelLoader.class) +@ClientOnlyMixin +public abstract class MixinBlockStateModelLoader { + @Shadow + protected static BlockStateModelLoader.LoadedModels lambda$loadBlockStates$1(Map.Entry> entry, Function> locationToBlockStateMapper) { + throw new AssertionError(); + } + + @ModifyArg(method = "loadBlockStates", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenCompose(Ljava/util/function/Function;)Ljava/util/concurrent/CompletableFuture;")) + private static Function>, ? extends CompletionStage> skipAOTBlockStateLoad(Function>, ? extends CompletionStage>> original, @Local(ordinal = 0) Function> mapper) { + return resourceMap -> CompletableFuture.completedFuture(DynamicModelSystem.createDynamicBlockStateLoadedModels(resourceMap, (id, resources) -> { + return lambda$loadBlockStates$1(Map.entry(id, resources), mapper); + })); + } +} 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 new file mode 100644 index 00000000..9f02acde --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinModelManager.java @@ -0,0 +1,30 @@ +package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources; + +import net.minecraft.client.resources.model.ModelManager; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.Identifier; +import net.minecraft.server.packs.resources.Resource; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.embeddedt.modernfix.dynresources.DynamicModelSystem; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +@Mixin(ModelManager.class) +@ClientOnlyMixin +public class MixinModelManager { + /** + * @author embeddedt + * @reason Instead of loading all unbaked models from the resource packs at once, create a dynamic map backed by + * a cache that loads them on demand + */ + @ModifyArg(method = "loadBlockModels", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenCompose(Ljava/util/function/Function;)Ljava/util/concurrent/CompletableFuture;")) + private static Function, ? extends CompletionStage>> skipAOTUnbakedModelLoad(Function, ? extends CompletionStage>> original) { + return resourceMap -> CompletableFuture.completedFuture(DynamicModelSystem.createDynamicUnbakedModelMap(resourceMap)); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java b/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java new file mode 100644 index 00000000..19554b96 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java @@ -0,0 +1,73 @@ +package org.embeddedt.modernfix.dynresources; + +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 net.minecraft.client.resources.model.BlockStateModelLoader; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.FileToIdConverter; +import net.minecraft.resources.Identifier; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.client.model.UnbakedModelParser; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.common.mixin.perf.dynamic_resources.IdMapperAccessor; + +import java.io.Reader; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +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 Map createDynamicUnbakedModelMap(Map resourceMap) { + LoadingCache unbakedModelCache = CacheBuilder.newBuilder().softValues().maximumSize(1000).build(new CacheLoader<>() { + @Override + public UnbakedModel load(Identifier key) throws Exception { + var resource = resourceMap.get(MODEL_LISTER.idToFile(key)); + if (resource == null) { + throw new IllegalArgumentException("Model " + key + " does not exist in map"); + } + if (DEBUG_DYNAMIC_MODEL_LOADING) { + ModernFix.LOGGER.info("Loading unbaked model {}", key); + } + try (Reader reader = resource.openAsReader()) { + return UnbakedModelParser.parse(reader); + } + } + }); + Set unbakedIdSet = resourceMap.keySet().stream().map(MODEL_LISTER::fileToId).collect(Collectors.toUnmodifiableSet()); + return Maps.asMap(unbakedIdSet, key -> key != null ? unbakedModelCache.getUnchecked(key) : null); + } + + public interface SingleBlockStateEntryLoader { + BlockStateModelLoader.LoadedModels loadEntry(Identifier identifier, List blockstateResources); + } + + public static BlockStateModelLoader.LoadedModels createDynamicBlockStateLoadedModels(Map> resourceMap, SingleBlockStateEntryLoader entryLoader) { + LoadingCache definitionCache = CacheBuilder.newBuilder().softValues().maximumSize(1000).build(new CacheLoader<>() { + @Override + public BlockStateModelLoader.LoadedModels load(Identifier key) throws Exception { + if (DEBUG_DYNAMIC_MODEL_LOADING) { + ModernFix.LOGGER.info("Loading blockstate definition for {}", key); + } + var file = BLOCKSTATE_LISTER.idToFile(key); + var resources = resourceMap.getOrDefault(file, List.of()); + return entryLoader.loadEntry(file, resources); + } + }); + Set allStates = ((IdMapperAccessor)Block.BLOCK_STATE_REGISTRY).getReferenceMap().keySet(); + return new BlockStateModelLoader.LoadedModels(Maps.asMap(allStates, state -> { + var identifier = state.getBlock().builtInRegistryHolder().getKey().identifier(); + var loadedModels = definitionCache.getUnchecked(identifier); + return loadedModels.models().get(state); + })); + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index e727b039..bcbffbeb 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -53,3 +53,5 @@ public net.minecraft.server.level.ChunkMap pendingUnloads public net.minecraft.world.level.levelgen.DensityFunctions$MulOrAdd$Type public net.minecraft.client.renderer.entity.EnderDragonRenderer$DragonModel entity public net.minecraft.client.KeyMapping ALL + +public net.minecraft.client.resources.model.BlockStateModelLoader$LoadedBlockModelDefinition \ No newline at end of file