diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinClientItemInfoLoader.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinClientItemInfoLoader.java new file mode 100644 index 00000000..05f72f74 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinClientItemInfoLoader.java @@ -0,0 +1,42 @@ +package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources; + +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.client.multiplayer.ClientRegistryLayer; +import net.minecraft.client.resources.model.ClientItemInfoLoader; +import net.minecraft.core.RegistryAccess; +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.Shadow; +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(ClientItemInfoLoader.class) +@ClientOnlyMixin +public abstract class MixinClientItemInfoLoader { + @Shadow + private static ClientItemInfoLoader.PendingLoad lambda$scheduleLoad$3(Identifier resourceId, Resource resource, RegistryAccess.Frozen registries) { + throw new AssertionError(); + } + + /** + * @author embeddedt + * @reason Load client item infos dynamically instead of all at once + */ + @ModifyArg(method = "scheduleLoad", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenCompose(Ljava/util/function/Function;)Ljava/util/concurrent/CompletableFuture;")) + private static Function, ? extends CompletionStage> skipAOTClientItemLoad( + Function, ? extends CompletionStage> original, + @Local(ordinal = 0) RegistryAccess.Frozen staticRegistries) { + return resourceMap -> CompletableFuture.completedFuture(DynamicModelSystem.createDynamicClientInfos(resourceMap, (resourceId, resource) -> { + ClientItemInfoLoader.PendingLoad load = lambda$scheduleLoad$3(resourceId, resource, staticRegistries); + return load.clientItemInfo(); + })); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinClientNeoForgeMod.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinClientNeoForgeMod.java new file mode 100644 index 00000000..50797c44 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinClientNeoForgeMod.java @@ -0,0 +1,27 @@ +package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources; + +import net.minecraft.client.renderer.item.ItemModel; +import net.minecraft.client.resources.model.ModelManager; +import net.minecraft.resources.Identifier; +import net.neoforged.neoforge.client.ClientNeoForgeMod; +import org.embeddedt.modernfix.ModernFix; +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(ClientNeoForgeMod.class) +@ClientOnlyMixin +public class MixinClientNeoForgeMod { + /** + * @author embeddedt + * @reason avoid triggering eager load of every item model + */ + @Redirect(method = "lambda$new$7", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelManager;getItemModel(Lnet/minecraft/resources/Identifier;)Lnet/minecraft/client/renderer/item/ItemModel;")) + private static ItemModel checkExistenceWithoutLoadingModel(ModelManager instance, Identifier id) { + if (!((ModelManagerAccessor)instance).mfix$getBakedItemModels().containsKey(id)) { + ModernFix.LOGGER.warn("Missing item model '{}'", id); + } + return null; + } +} diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinModelBakery.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinModelBakery.java index c8b14a57..5690e3db 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinModelBakery.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/MixinModelBakery.java @@ -1,17 +1,25 @@ package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources; +import com.google.common.collect.Maps; +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import net.minecraft.client.renderer.item.ClientItem; import net.minecraft.client.resources.model.ModelBakery; +import net.minecraft.resources.Identifier; import org.embeddedt.modernfix.annotation.ClientOnlyMixin; import org.embeddedt.modernfix.dynresources.DynamicModelSystem; +import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Constant; import org.spongepowered.asm.mixin.injection.ModifyConstant; import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import java.util.function.BiConsumer; import java.util.function.BiFunction; @Mixin(ModelBakery.class) @@ -26,6 +34,21 @@ public class MixinModelBakery { return CompletableFuture.completedFuture(DynamicModelSystem.createDynamicBakedRegistry(input, baker)); } + @Redirect(method = "bakeModels", + slice = @Slice(from = @At(value = "FIELD", target = "Lnet/minecraft/client/resources/model/ModelBakery;clientInfos:Ljava/util/Map;", opcode = Opcodes.GETFIELD, ordinal = 2)), + at = @At(value = "INVOKE", target = "Ljava/util/Map;forEach(Ljava/util/function/BiConsumer;)V", ordinal = 0)) + private void dynamicItemProperties(Map clientItems, BiConsumer action, + @Local(name = "itemStackModelProperties") LocalRef> modelProperties) { + modelProperties.set(Maps.asMap(clientItems.keySet(), id -> { + var item = clientItems.get(id); + var props = ClientItem.Properties.DEFAULT; + if (item != null && !props.equals(item.properties())) { + props = item.properties(); + } + return props; + })); + } + /** * @author embeddedt * @reason We want log4j to print the stacktrace and not just the exception message diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/ModelManagerAccessor.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/ModelManagerAccessor.java new file mode 100644 index 00000000..7f7ef799 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/ModelManagerAccessor.java @@ -0,0 +1,17 @@ +package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources; + +import net.minecraft.client.renderer.item.ItemModel; +import net.minecraft.client.resources.model.ModelManager; +import net.minecraft.resources.Identifier; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(ModelManager.class) +@ClientOnlyMixin +public interface ModelManagerAccessor { + @Accessor("bakedItemStackModels") + Map mfix$getBakedItemModels(); +} diff --git a/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java b/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java index 4c6b0454..2dc8ad64 100644 --- a/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java +++ b/src/main/java/org/embeddedt/modernfix/dynresources/DynamicModelSystem.java @@ -4,16 +4,16 @@ 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; 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.resources.model.BlockStateModelLoader; +import net.minecraft.client.renderer.item.ClientItem; import net.minecraft.client.resources.model.ClientItemInfoLoader; +import org.jetbrains.annotations.Nullable; import net.minecraft.client.resources.model.ModelDiscovery; import net.minecraft.client.resources.model.ModelManager; import net.minecraft.client.resources.model.ResolvedModel; @@ -38,35 +38,48 @@ import java.util.AbstractSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.BiFunction; -import java.util.function.Function; 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"); + private static final FileToIdConverter ITEM_LISTER = FileToIdConverter.json("items"); 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<>() { + + private interface ResultLoader { + RESULT load(Identifier file, @Nullable RESOURCE resource) throws Exception; + } + + private static Map createCachedResourceBackedMap(Map resourceMap, + FileToIdConverter converter, + String debugName, + ResultLoader loader) { + LoadingCache resultCache = 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"); - } + public RESULT load(Identifier id) throws Exception { + var file = converter.idToFile(id); + var resource = resourceMap.get(file); if (DEBUG_DYNAMIC_MODEL_LOADING) { - ModernFix.LOGGER.info("Loading unbaked model {}", key); - } - try (Reader reader = resource.openAsReader()) { - return UnbakedModelParser.parse(reader); + ModernFix.LOGGER.info("Loading {} {}", debugName, id); } + return loader.load(file, resource); + } + }); + Set idSet = resourceMap.keySet().stream().map(converter::fileToId).collect(Collectors.toUnmodifiableSet()); + return Maps.asMap(idSet, key -> key != null ? resultCache.getUnchecked(key) : null); + } + + public static Map createDynamicUnbakedModelMap(Map resourceMap) { + return createCachedResourceBackedMap(resourceMap, MODEL_LISTER, "unbaked model", (id, resource) -> { + Objects.requireNonNull(resource, "unbaked model not present"); + 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 { @@ -96,17 +109,7 @@ public class DynamicModelSystem { } 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); - } - }); + var blockStateDefinitions = createCachedResourceBackedMap(resourceMap, BLOCKSTATE_LISTER, "blockstate definition", entryLoader::loadEntry); var staticDefinitions = BlockStateDefinitionsAccessor.getStaticDefinitions(); var staticIdentifiers = staticDefinitions.entrySet() .stream() @@ -118,11 +121,20 @@ public class DynamicModelSystem { if (identifier == null) { identifier = state.getBlock().builtInRegistryHolder().getKey().identifier(); } - var loadedModels = definitionCache.getUnchecked(identifier); + var loadedModels = blockStateDefinitions.get(identifier); return loadedModels.models().get(state); })); } + public interface SingleClientItemEntryLoader { + @Nullable ClientItem loadEntry(Identifier resourceId, Resource resource); + } + + public static ClientItemInfoLoader.LoadedClientInfos createDynamicClientInfos(Map resourceMap, SingleClientItemEntryLoader entryLoader) { + var clientItems = createCachedResourceBackedMap(resourceMap, ITEM_LISTER, "client item info", entryLoader::loadEntry); + return new ClientItemInfoLoader.LoadedClientInfos(clientItems); + } + public record DynamicResolver(Map inputModels, BlockStateModelLoader.LoadedModels loadedModels, ClientItemInfoLoader.LoadedClientInfos loadedClientInfos, diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index a90bfbfd..1b8d3b45 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -73,3 +73,5 @@ public net.minecraft.client.resources.model.ModelManager$ResolvedModels (L 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; +public net.minecraft.client.resources.model.ClientItemInfoLoader$PendingLoad +public net.minecraft.client.resources.model.ClientItemInfoLoader$PendingLoad (Lnet/minecraft/resources/Identifier;Lnet/minecraft/client/renderer/item/ClientItem;)V