From a1246358bacb2a1931cf983031fa05aabcecfdb6 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Tue, 2 May 2023 14:13:35 -0400 Subject: [PATCH 1/5] Load all models initially on Fabric for texture scanning --- .../dynamic_resources/ModelBakeryMixin.java | 111 ++++++------------ 1 file changed, 35 insertions(+), 76 deletions(-) diff --git a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/dynamic_resources/ModelBakeryMixin.java b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/dynamic_resources/ModelBakeryMixin.java index c1caf35b..0d6d6839 100644 --- a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/dynamic_resources/ModelBakeryMixin.java +++ b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/dynamic_resources/ModelBakeryMixin.java @@ -5,27 +5,19 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.RemovalNotification; import com.google.common.collect.ForwardingMap; import com.google.common.collect.ImmutableList; -import com.google.gson.JsonElement; import com.mojang.math.Transformation; import net.minecraft.client.renderer.block.model.BlockModel; import net.minecraft.client.renderer.block.model.ItemModelGenerator; import net.minecraft.client.renderer.texture.AtlasSet; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureManager; -import net.minecraft.client.resources.ClientPackSource; import net.minecraft.client.resources.model.*; -import net.minecraft.core.Registry; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.FilePackResources; -import net.minecraft.server.packs.FolderPackResources; -import net.minecraft.server.packs.PackResources; -import net.minecraft.server.packs.VanillaPackResources; import net.minecraft.server.packs.resources.ResourceManager; 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 net.minecraft.world.level.block.state.properties.Property; import org.apache.commons.lang3.tuple.Triple; import org.apache.logging.log4j.Logger; import org.embeddedt.modernfix.ModernFix; @@ -39,13 +31,11 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.Redirect; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.*; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; import java.util.function.Function; /* high priority so that our injectors are added before other mods' */ @@ -80,13 +70,17 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { @Shadow @Nullable public abstract BakedModel bake(ResourceLocation location, ModelState transform); + @Shadow @Final private Map topLevelModels; private Cache, BakedModel> loadedBakedModels; private Cache loadedModels; private HashMap smallLoadingCache = new HashMap<>(); + private boolean inTextureGatheringPass; + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiling/ProfilerFiller;push(Ljava/lang/String;)V", ordinal = 0)) private void replaceTopLevelBakedModels(ProfilerFiller filler, String s) { + this.inTextureGatheringPass = true; this.loadedBakedModels = CacheBuilder.newBuilder() .expireAfterAccess(3, TimeUnit.MINUTES) .maximumSize(1000) @@ -101,12 +95,12 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { .removalListener(this::onModelRemoved) .softValues() .build(); - this.bakedCache = loadedBakedModels.asMap(); - ConcurrentMap unbakedCacheBackingMap = loadedModels.asMap(); + // temporarily replace this map to capture models into the small loading cache + Map oldMap = this.unbakedCache; this.unbakedCache = new ForwardingMap() { @Override protected Map delegate() { - return unbakedCacheBackingMap; + return oldMap; } @Override @@ -115,7 +109,6 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { return super.put(key, value); } }; - this.bakedTopLevelModels = new DynamicBakedModelProvider((ModelBakery)(Object)this, bakedCache); filler.push(s); } @@ -138,75 +131,41 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { private UnbakedModel missingModel; - private Set blockStateFiles; - private Set modelFiles; - @ModifyArg(method = "", at = @At(value = "INVOKE", target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", ordinal = 0), index = 1) private Object captureMissingModel(Object model) { this.missingModel = (UnbakedModel)model; - this.blockStateFiles = new HashSet<>(); - this.modelFiles = new HashSet<>(); return this.missingModel; } - /** - * @author embeddedt - * @reason don't actually load the model. instead, keep track of if we need to load a blockstate or a model, - * and save the info into the two lists - */ - @Inject(method = "loadTopLevel", at = @At("HEAD"), cancellable = true) - private void addTopLevelFile(ModelResourceLocation location, CallbackInfo ci) { - if(location == MISSING_MODEL_LOCATION) - return; /* needed for FAPI compat */ - ci.cancel(); - if(Objects.equals(location.getVariant(), "inventory")) { - modelFiles.add(new ResourceLocation(location.getNamespace(), "item/" + location.getPath())); - } else { - blockStateFiles.add(new ResourceLocation(location.getNamespace(), location.getPath())); - } - } - - @Redirect(method = "", at = @At(value = "INVOKE", target = "Ljava/util/Set;addAll(Ljava/util/Collection;)Z", ordinal = 0)) - private boolean gatherModelTextures(Set materialSet, Collection collection) { - materialSet.addAll(collection); - gatherModelMaterials(materialSet); - return true; - } - - @Redirect(method = "", at = @At(value = "INVOKE", target = "Ljava/util/Map;forEach(Ljava/util/function/BiConsumer;)V", ordinal = 0)) - private void fetchStaticDefinitions(Map> map, BiConsumer> func) { - map.forEach((loc, def) -> blockStateFiles.add(loc)); - } - - @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/StateDefinition;getPossibleStates()Lcom/google/common/collect/ImmutableList;", ordinal = 0)) - private ImmutableList fetchBlocks(StateDefinition def) { - blockStateFiles.add(Registry.BLOCK.getKey(def.any().getBlock())); - return ImmutableList.of(); - } - - private boolean trustedResourcePack(PackResources pack) { - return pack instanceof VanillaPackResources || - pack instanceof ClientPackSource || - pack instanceof FolderPackResources || - pack instanceof FilePackResources; - } - - /** - * Load all blockstate JSONs and model files, collect textures. - */ - private void gatherModelMaterials(Set materialSet) { - Function modelDeserializer = model -> BlockModel.GSON.fromJson(model, BlockModel.class); - ModelBakeryHelpers.gatherModelMaterials(this.resourceManager, this::trustedResourcePack, materialSet, blockStateFiles, - modelFiles, missingModel, modelDeserializer, this::getModel); - loadedModels.invalidateAll(); - loadedModels.put(MISSING_MODEL_LOCATION, missingModel); - } - @Inject(method = "uploadTextures", at = @At(value = "FIELD", target = "Lnet/minecraft/client/resources/model/ModelBakery;topLevelModels:Ljava/util/Map;", ordinal = 0), cancellable = true) private void skipBake(TextureManager resourceManager, ProfilerFiller profiler, CallbackInfoReturnable cir) { profiler.pop(); + this.inTextureGatheringPass = false; + // hand off to the dynamic model system + this.loadedModels.put(MISSING_MODEL_LOCATION, this.missingModel); + this.bakedCache = loadedBakedModels.asMap(); + ConcurrentMap unbakedCacheBackingMap = loadedModels.asMap(); + this.unbakedCache = new ForwardingMap() { + @Override + protected Map delegate() { + return unbakedCacheBackingMap; + } + + @Override + public UnbakedModel put(ResourceLocation key, UnbakedModel value) { + smallLoadingCache.put(key, value); + return super.put(key, value); + } + }; + this.bakedTopLevelModels = new DynamicBakedModelProvider((ModelBakery)(Object)this, bakedCache); + // ensure missing model is a permanent override this.bakedTopLevelModels.put(MISSING_MODEL_LOCATION, this.bake(MISSING_MODEL_LOCATION, BlockModelRotation.X0_Y0)); + this.loadedModels.invalidateAll(); + this.loadedModels.put(MISSING_MODEL_LOCATION, this.missingModel); + this.topLevelModels.clear(); + this.topLevelModels.put(MISSING_MODEL_LOCATION, this.missingModel); + this.smallLoadingCache.clear(); cir.setReturnValue(atlasSet); } @@ -278,12 +237,12 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { } } - private , V extends T> BlockState setPropertyGeneric(BlockState state, Property prop, Object o) { - return state.setValue(prop, (V)o); - } @Redirect(method = "loadModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/StateDefinition;getPossibleStates()Lcom/google/common/collect/ImmutableList;")) private ImmutableList loadOnlyRelevantBlockState(StateDefinition stateDefinition, ResourceLocation location) { - return ModelBakeryHelpers.getBlockStatesForMRL(stateDefinition, (ModelResourceLocation)location); + if(this.inTextureGatheringPass) + return stateDefinition.getPossibleStates(); + else + return ModelBakeryHelpers.getBlockStatesForMRL(stateDefinition, (ModelResourceLocation)location); } @Override From f274dc1f5f2a38621d2a413a820d5f97cc9314e3 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Tue, 2 May 2023 14:44:48 -0400 Subject: [PATCH 2/5] Add resource caching to Fabric --- fabric/build.gradle | 1 + .../ModNioResourcePackMixin.java | 56 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java diff --git a/fabric/build.gradle b/fabric/build.gradle index 8cedb676..a7d20969 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -32,6 +32,7 @@ dependencies { modIncludeImplementation(fabricApi.module("fabric-api-base", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } modIncludeImplementation(fabricApi.module("fabric-lifecycle-events-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } modIncludeImplementation(fabricApi.module("fabric-screen-api-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } + modImplementation(fabricApi.module("fabric-resource-loader-v0", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } // Remove the next line if you don't want to depend on the API // modApi "me.shedaniel:architectury-fabric:${rootProject.architectury_version}" diff --git a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java new file mode 100644 index 00000000..f115f943 --- /dev/null +++ b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java @@ -0,0 +1,56 @@ +package org.embeddedt.modernfix.fabric.mixin.perf.fabric_resourcepacks; + +import net.fabricmc.fabric.impl.resource.loader.ModNioResourcePack; +import net.minecraft.server.packs.PackType; +import org.embeddedt.modernfix.annotation.RequiresMod; +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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.util.Set; + +@Mixin(ModNioResourcePack.class) +@RequiresMod("fabric-resource-loader-v0") +public abstract class ModNioResourcePackMixin { + @Shadow public abstract Set getNamespaces(PackType type); + + @Shadow @Final private Path basePath; + private PackResourcesCacheEngine cacheEngine; + + @Inject(method = "", at = @At("RETURN")) + private void cacheResources(CallbackInfo ci) { + this.cacheEngine = new PackResourcesCacheEngine(this::getNamespaces, (type, namespace) -> { + return basePath.resolve(type.getDirectory()).resolve(namespace); + }); + } + + // this check wastes CPU time, it is checked later anyway + @Redirect(method = "getPath", at = @At(value = "INVOKE", target = "Ljava/nio/file/Files;exists(Ljava/nio/file/Path;[Ljava/nio/file/LinkOption;)Z"), remap = false) + private boolean checkExists(Path p, LinkOption[] opts) { + return true; + } + + // TODO might be redundant + @Inject(method = "getNamespaces", at = @At("HEAD"), cancellable = true) + private void useCacheForNamespaces(PackType type, CallbackInfoReturnable> cir) { + if(cacheEngine != null) { + Set namespaces = cacheEngine.getNamespaces(type); + if(namespaces != null) + cir.setReturnValue(namespaces); + } + } + + @Inject(method = "hasResource", at = @At(value = "HEAD"), cancellable = true) + private void useCacheForExistence(String path, CallbackInfoReturnable cir) { + if(cacheEngine != null) + cir.setReturnValue(this.cacheEngine.hasResource(path)); + } +} From 3db4e9071f7dfd34778c0af31166be862c6ac017 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Tue, 2 May 2023 14:52:21 -0400 Subject: [PATCH 3/5] Update resource pack mixin --- .../ModNioResourcePackMixin.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java index f115f943..e0cd8827 100644 --- a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java +++ b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java @@ -1,7 +1,9 @@ package org.embeddedt.modernfix.fabric.mixin.perf.fabric_resourcepacks; import net.fabricmc.fabric.impl.resource.loader.ModNioResourcePack; +import net.fabricmc.loader.api.metadata.ModMetadata; import net.minecraft.server.packs.PackType; +import org.embeddedt.modernfix.ModernFix; import org.embeddedt.modernfix.annotation.RequiresMod; import org.embeddedt.modernfix.resources.PackResourcesCacheEngine; import org.spongepowered.asm.mixin.Final; @@ -13,8 +15,8 @@ import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import java.nio.file.LinkOption; import java.nio.file.Path; +import java.util.List; import java.util.Set; @Mixin(ModNioResourcePack.class) @@ -22,19 +24,24 @@ import java.util.Set; public abstract class ModNioResourcePackMixin { @Shadow public abstract Set getNamespaces(PackType type); - @Shadow @Final private Path basePath; + @Shadow @Final private List basePaths; + @Shadow @Final private ModMetadata modInfo; private PackResourcesCacheEngine cacheEngine; @Inject(method = "", at = @At("RETURN")) private void cacheResources(CallbackInfo ci) { - this.cacheEngine = new PackResourcesCacheEngine(this::getNamespaces, (type, namespace) -> { - return basePath.resolve(type.getDirectory()).resolve(namespace); - }); + if(this.basePaths.size() == 1) { + Path basePath = this.basePaths.get(0); + this.cacheEngine = new PackResourcesCacheEngine(this::getNamespaces, (type, namespace) -> { + return basePath.resolve(type.getDirectory()).resolve(namespace); + }); + } else + ModernFix.LOGGER.warn("Cannot cache resource pack for mod '{}' as it uses multiple base paths", modInfo.getId()); } // this check wastes CPU time, it is checked later anyway - @Redirect(method = "getPath", at = @At(value = "INVOKE", target = "Ljava/nio/file/Files;exists(Ljava/nio/file/Path;[Ljava/nio/file/LinkOption;)Z"), remap = false) - private boolean checkExists(Path p, LinkOption[] opts) { + @Redirect(method = "getPath", at = @At(value = "INVOKE", target = "Lnet/fabricmc/fabric/impl/resource/loader/ModNioResourcePack;exists(Ljava/nio/file/Path;)Z"), remap = false) + private boolean checkExists(Path path) { return true; } From fb2929215fb4c02cd2e94327a9328521907cb53d Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Tue, 2 May 2023 14:57:35 -0400 Subject: [PATCH 4/5] Update --- .../perf/fabric_resourcepacks/ModNioResourcePackMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java index f115f943..1ff34cd7 100644 --- a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java +++ b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java @@ -48,7 +48,7 @@ public abstract class ModNioResourcePackMixin { } } - @Inject(method = "hasResource", at = @At(value = "HEAD"), cancellable = true) + @Inject(method = "hasResource", at = @At(value = "INVOKE", target = "Lnet/fabricmc/fabric/impl/resource/loader/ModNioResourcePack;getPath(Ljava/lang/String;)Ljava/nio/file/Path;"), cancellable = true) private void useCacheForExistence(String path, CallbackInfoReturnable cir) { if(cacheEngine != null) cir.setReturnValue(this.cacheEngine.hasResource(path)); From 393a834891f0d088d6c9a7b39fcc5255eac58048 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Tue, 2 May 2023 15:02:42 -0400 Subject: [PATCH 5/5] Update resource pack mixin --- .../fabric_resourcepacks/ModNioResourcePackMixin.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java index 2819d00f..0edfe915 100644 --- a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java +++ b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/fabric_resourcepacks/ModNioResourcePackMixin.java @@ -2,6 +2,7 @@ package org.embeddedt.modernfix.fabric.mixin.perf.fabric_resourcepacks; import net.fabricmc.fabric.impl.resource.loader.ModNioResourcePack; import net.fabricmc.loader.api.metadata.ModMetadata; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.PackType; import org.embeddedt.modernfix.ModernFix; import org.embeddedt.modernfix.annotation.RequiresMod; @@ -14,6 +15,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; import java.nio.file.Path; import java.util.List; @@ -55,9 +57,9 @@ public abstract class ModNioResourcePackMixin { } } - @Inject(method = "hasResource", at = @At(value = "INVOKE", target = "Lnet/fabricmc/fabric/impl/resource/loader/ModNioResourcePack;getPath(Ljava/lang/String;)Ljava/nio/file/Path;"), cancellable = true) - private void useCacheForExistence(String path, CallbackInfoReturnable cir) { + @Inject(method = "hasResource", at = @At(value = "INVOKE", target = "Lnet/fabricmc/fabric/impl/resource/loader/ModNioResourcePack;getPath(Ljava/lang/String;)Ljava/nio/file/Path;"), cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD) + private void useCacheForExistence(PackType type, ResourceLocation id, CallbackInfoReturnable cir, String filename) { if(cacheEngine != null) - cir.setReturnValue(this.cacheEngine.hasResource(path)); + cir.setReturnValue(this.cacheEngine.hasResource(filename)); } }