From 92259629831827143216aa5aaff893c40cdf714d Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 15 Apr 2023 21:26:49 -0400 Subject: [PATCH 01/13] Invalidate material cache if the texture map changes on a model --- .../modernfix/duck/ICachedMaterialsModel.java | 5 + .../BlockModelMixin.java | 108 ++++++++++++++++++ .../VanillaModelMixin.java | 8 +- src/main/resources/modernfix.mixins.json | 1 + 4 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/embeddedt/modernfix/duck/ICachedMaterialsModel.java create mode 100644 src/main/java/org/embeddedt/modernfix/mixin/perf/cache_model_materials/BlockModelMixin.java diff --git a/src/main/java/org/embeddedt/modernfix/duck/ICachedMaterialsModel.java b/src/main/java/org/embeddedt/modernfix/duck/ICachedMaterialsModel.java new file mode 100644 index 00000000..855b8c3b --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/duck/ICachedMaterialsModel.java @@ -0,0 +1,5 @@ +package org.embeddedt.modernfix.duck; + +public interface ICachedMaterialsModel { + public void clearMaterialsCache(); +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/cache_model_materials/BlockModelMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/cache_model_materials/BlockModelMixin.java new file mode 100644 index 00000000..691a28e5 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/cache_model_materials/BlockModelMixin.java @@ -0,0 +1,108 @@ +package org.embeddedt.modernfix.mixin.perf.cache_model_materials; + +import com.mojang.datafixers.util.Either; +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.Material; +import org.embeddedt.modernfix.duck.ICachedMaterialsModel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +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.Collection; +import java.util.Map; +import java.util.Set; + +@Mixin(BlockModel.class) +public class BlockModelMixin { + @Shadow @Final @Mutable public Map> textureMap; + + /** + * @author embeddedt + * @reason detect changes to the texture map, and clear the material cache as needed + */ + @Inject(method = "", at = @At("RETURN")) + private void useTrackingTextureMap(CallbackInfo ci) { + Map> backingMap = this.textureMap; + ICachedMaterialsModel cacheHolder = (ICachedMaterialsModel)this; + this.textureMap = new Map>() { + @Override + public int size() { + return backingMap.size(); + } + + @Override + public boolean isEmpty() { + return backingMap.isEmpty(); + } + + @Override + public boolean containsKey(Object o) { + return backingMap.containsKey(o); + } + + @Override + public boolean containsValue(Object o) { + return backingMap.containsValue(o); + } + + @Override + public Either get(Object o) { + return backingMap.get(o); + } + + @Nullable + @Override + public Either put(String s, Either materialStringEither) { + Either old = backingMap.put(s, materialStringEither); + cacheHolder.clearMaterialsCache(); + return old; + } + + @Override + public Either remove(Object o) { + Either e = backingMap.remove(o); + cacheHolder.clearMaterialsCache(); + return e; + } + + @Override + public void putAll(@NotNull Map> map) { + backingMap.putAll(map); + cacheHolder.clearMaterialsCache(); + } + + @Override + public void clear() { + backingMap.clear(); + cacheHolder.clearMaterialsCache(); + } + + @NotNull + @Override + public Set keySet() { + cacheHolder.clearMaterialsCache(); + return backingMap.keySet(); + } + + @NotNull + @Override + public Collection> values() { + cacheHolder.clearMaterialsCache(); + return backingMap.values(); + } + + @NotNull + @Override + public Set>> entrySet() { + cacheHolder.clearMaterialsCache(); + return backingMap.entrySet(); + } + }; + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/cache_model_materials/VanillaModelMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/cache_model_materials/VanillaModelMixin.java index 6d9a9a55..54413655 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/cache_model_materials/VanillaModelMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/cache_model_materials/VanillaModelMixin.java @@ -7,6 +7,7 @@ import net.minecraft.client.resources.model.Material; import net.minecraft.client.renderer.block.model.MultiVariant; import net.minecraft.client.renderer.block.model.multipart.MultiPart; import net.minecraft.resources.ResourceLocation; +import org.embeddedt.modernfix.duck.ICachedMaterialsModel; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -18,7 +19,7 @@ import java.util.Set; import java.util.function.Function; @Mixin(value = {MultiVariant.class, MultiPart.class, BlockModel.class}) -public class VanillaModelMixin { +public class VanillaModelMixin implements ICachedMaterialsModel { private Collection materialsCache = null; @Inject(method = "getMaterials", at = @At("HEAD"), cancellable = true) @@ -33,4 +34,9 @@ public class VanillaModelMixin { if(materialsCache == null) materialsCache = Collections.unmodifiableCollection(cir.getReturnValue()); } + + @Override + public void clearMaterialsCache() { + materialsCache = null; + } } diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index 58795e9f..6464cc72 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -101,6 +101,7 @@ "perf.blast_search_trees.MinecraftMixin", "perf.blast_search_trees.IngredientFilterInvoker", "perf.cache_model_materials.VanillaModelMixin", + "perf.cache_model_materials.BlockModelMixin", "perf.cache_model_materials.MultipartMixin", "perf.faster_texture_stitching.StitcherMixin", "bugfix.packet_leak.ClientPlayNetHandlerMixin", From 61af88a2abb37a182c00ae14689868eee569974f Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 16 Apr 2023 09:35:31 -0400 Subject: [PATCH 02/13] Improve memory usage of resource pack cache --- .../ModFileResourcePackMixin.java | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java index c5bc3f68..c58b3457 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java @@ -6,6 +6,7 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.ResourcePackFileNotFoundException; import net.minecraftforge.fml.loading.moddiscovery.ModFile; import net.minecraftforge.fml.packs.ModFileResourcePack; +import org.apache.commons.lang3.tuple.Triple; import org.embeddedt.modernfix.util.FileUtil; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -31,7 +32,7 @@ public abstract class ModFileResourcePackMixin { @Shadow(remap = false) @Final private ModFile modFile; private EnumMap> namespacesByType; - private EnumMap>> rootListingByNamespaceAndType; + private EnumMap>>> rootListingByNamespaceAndType; private Set containedPaths; private boolean useNamespaceCaches; private FileSystem resourcePackFS; @@ -50,18 +51,19 @@ public abstract class ModFileResourcePackMixin { this.containedPaths = new HashSet<>(); for(PackType type : PackType.values()) { Set namespaces = this.namespacesByType.get(type); - HashMap> rootListingForNamespaces = new HashMap<>(); + HashMap>> rootListingForNamespaces = new HashMap<>(); for(String namespace : namespaces) { try { Path root = modFile.getLocator().findPath(modFile, type.getDirectory(), namespace).toAbsolutePath(); try (Stream stream = Files.walk(root)) { - ArrayList rootListingPaths = new ArrayList<>(); + ArrayList> rootListingPaths = new ArrayList<>(); stream .map(path -> root.relativize(path.toAbsolutePath())) .filter(this::isValidCachedResourcePath) .forEach(path -> { - if(!path.toString().endsWith(".mcmeta")) - rootListingPaths.add(path); + if(!path.toString().endsWith(".mcmeta")) { + rootListingPaths.add(Triple.of(path.getNameCount(), path.getFileName().toString(), slashJoiner.join(path))); + } String mergedPath = slashJoiner.join(type.getDirectory(), namespace, path); this.containedPaths.add(mergedPath); }); @@ -77,6 +79,9 @@ public abstract class ModFileResourcePackMixin { } private boolean isValidCachedResourcePath(Path path) { + if(path.getFileName() == null) { + return false; + } String str = path.toString(); for(int i = 0; i < str.length(); i++) { if(!ResourceLocation.validPathChar(str.charAt(i))) { @@ -114,15 +119,17 @@ public abstract class ModFileResourcePackMixin { @Inject(method = "getResources", at = @At("HEAD"), cancellable = true) private void fastGetResources(PackType type, String resourceNamespace, String pathIn, int maxDepth, Predicate filter, CallbackInfoReturnable> cir) { - Path inputPath = this.resourcePackFS.getPath(pathIn); + if(!pathIn.endsWith("/")) + pathIn = pathIn + "/"; + final String testPath = pathIn; cir.setReturnValue(this.rootListingByNamespaceAndType.get(type).getOrDefault(resourceNamespace, Collections.emptyList()).stream(). - filter(path -> path.getNameCount() <= maxDepth). // Make sure the depth is within bounds - filter(path -> path.startsWith(inputPath)). // Make sure the target path is inside this one - filter(path -> filter.test(path.getFileName().toString())). // Test the file name against the predicate + filter(path -> path.getLeft() <= maxDepth). // Make sure the depth is within bounds + filter(path -> path.getRight().startsWith(testPath)). // Make sure the target path is inside this one + filter(path -> filter.test(path.getMiddle())). // Test the file name against the predicate // Finally we need to form the RL, so use the first name as the domain, and the rest as the path // It is VERY IMPORTANT that we do not rely on Path.toString as this is inconsistent between operating systems // Join the path names ourselves to force forward slashes - map(path -> new ResourceLocation(resourceNamespace, slashJoiner.join(path))). + map(path -> new ResourceLocation(resourceNamespace, path.getRight())). collect(Collectors.toList())); } } From 26c690595edef7fa1c4441ecde97ea7c14489459 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 16 Apr 2023 09:38:11 -0400 Subject: [PATCH 03/13] Improve memory usage of 1.18 resource pack cache --- .../PathResourcePackMixin.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathResourcePackMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathResourcePackMixin.java index c9a84dd6..53236ea6 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathResourcePackMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathResourcePackMixin.java @@ -6,6 +6,7 @@ import net.minecraft.server.packs.PackType; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.fml.loading.moddiscovery.ModFile; import net.minecraftforge.resource.PathResourcePack; +import org.apache.commons.lang3.tuple.Triple; import org.embeddedt.modernfix.ModernFix; import org.embeddedt.modernfix.util.FileUtil; import org.spongepowered.asm.mixin.Final; @@ -35,7 +36,7 @@ public abstract class PathResourcePackMixin { @Shadow @Final private String packName; @Shadow @Final private Path source; private EnumMap> namespacesByType; - private EnumMap>>> rootListingByNamespaceAndType; + private EnumMap>>> rootListingByNamespaceAndType; private boolean hasGeneratedListings; private Set containedPaths; @@ -54,22 +55,22 @@ public abstract class PathResourcePackMixin { synchronized (this) { if(hasGeneratedListings) return; - EnumMap>>> rootListingByNamespaceAndType = new EnumMap<>(PackType.class); + EnumMap>>> rootListingByNamespaceAndType = new EnumMap<>(PackType.class); HashSet containedPaths = new HashSet<>(); for(PackType type : PackType.values()) { Set namespaces = this.getNamespaces(type); - HashMap>> rootListingForNamespaces = new HashMap<>(); + HashMap>> rootListingForNamespaces = new HashMap<>(); for(String namespace : namespaces) { try { Path root = this.resolve(type.getDirectory(), namespace).toAbsolutePath(); try (Stream stream = Files.walk(root)) { - ArrayList> rootListingPaths = new ArrayList<>(); + ArrayList> rootListingPaths = new ArrayList<>(); stream .map(path -> root.relativize(path.toAbsolutePath())) .filter(this::isValidCachedResourcePath) .forEach(path -> { if(!path.toString().endsWith(".mcmeta")) - rootListingPaths.add(Pair.of(path, slashJoiner.join(path))); + rootListingPaths.add(Triple.of(path.getNameCount(), path.getFileName().toString(), slashJoiner.join(path))); String mergedPath = slashJoiner.join(type.getDirectory(), namespace, path); containedPaths.add(mergedPath); }); @@ -89,6 +90,8 @@ public abstract class PathResourcePackMixin { } private boolean isValidCachedResourcePath(Path path) { + if(path.getFileName() == null) + return false; String str = path.toString(); for(int i = 0; i < str.length(); i++) { if(!ResourceLocation.validPathChar(str.charAt(i))) { @@ -134,13 +137,13 @@ public abstract class PathResourcePackMixin { pathIn = pathIn + "/"; final String testPath = pathIn; Collection cachedListing = this.rootListingByNamespaceAndType.get(type).getOrDefault(resourceNamespace, Collections.emptyList()).stream(). - filter(path -> path.getFirst().getNameCount() <= maxDepth). // Make sure the depth is within bounds - filter(path -> path.getSecond().startsWith(testPath)). // Make sure the target path is inside this one - filter(path -> filter.test(path.getFirst().getFileName().toString())). // Test the file name against the predicate + filter(path -> path.getLeft() <= maxDepth). // Make sure the depth is within bounds + filter(path -> path.getRight().startsWith(testPath)). // Make sure the target path is inside this one + filter(path -> filter.test(path.getMiddle())). // Test the file name against the predicate // Finally we need to form the RL, so use the first name as the domain, and the rest as the path // It is VERY IMPORTANT that we do not rely on Path.toString as this is inconsistent between operating systems // Join the path names ourselves to force forward slashes - map(path -> new ResourceLocation(resourceNamespace, path.getSecond())). + map(path -> new ResourceLocation(resourceNamespace, path.getRight())). collect(Collectors.toList()); cir.setReturnValue(cachedListing); } From f36a8f4266355aea3d365ea867925713f6815f09 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 16 Apr 2023 11:39:54 -0400 Subject: [PATCH 04/13] Clear ObjectHolder Throwable fields after registry events fire --- .../org/embeddedt/modernfix/ModernFix.java | 3 +- .../registry/ObjectHolderClearer.java | 47 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/embeddedt/modernfix/registry/ObjectHolderClearer.java diff --git a/src/main/java/org/embeddedt/modernfix/ModernFix.java b/src/main/java/org/embeddedt/modernfix/ModernFix.java index bf73fc45..f1106e64 100644 --- a/src/main/java/org/embeddedt/modernfix/ModernFix.java +++ b/src/main/java/org/embeddedt/modernfix/ModernFix.java @@ -18,6 +18,7 @@ import org.apache.logging.log4j.Logger; import org.embeddedt.modernfix.core.config.ModernFixConfig; import org.embeddedt.modernfix.entity.EntityDataIDSyncHandler; import org.embeddedt.modernfix.packet.PacketHandler; +import org.embeddedt.modernfix.registry.ObjectHolderClearer; import org.embeddedt.modernfix.structure.AsyncLocator; import org.embeddedt.modernfix.util.KubeUtil; @@ -91,7 +92,7 @@ public class ModernFix { ModLoader.get().addWarning(new ModLoadingWarning(ModLoadingContext.get().getActiveContainer().getModInfo(), ModLoadingStage.COMMON_SETUP, "modernfix.no_lazydfu")); }); } - + ObjectHolderClearer.clearThrowables(); } @SubscribeEvent diff --git a/src/main/java/org/embeddedt/modernfix/registry/ObjectHolderClearer.java b/src/main/java/org/embeddedt/modernfix/registry/ObjectHolderClearer.java new file mode 100644 index 00000000..6ca0f948 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/registry/ObjectHolderClearer.java @@ -0,0 +1,47 @@ +package org.embeddedt.modernfix.registry; + +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import net.minecraftforge.registries.ObjectHolderRegistry; +import org.embeddedt.modernfix.ModernFix; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public class ObjectHolderClearer { + /** + * Many of the built-in Forge holders needlessly hold on to an exception. + */ + public static void clearThrowables() { + Set>> holders = ObfuscationReflectionHelper.getPrivateValue(ObjectHolderRegistry.class, null, "objectHolders"); + if(holders != null) { + int numCleared = 0; + HashMap, Field> throwableField = new HashMap<>(); + Throwable singletonThrowable = new Throwable("[This stacktrace was cleared to save memory]"); + try { + for(Consumer> holder : holders) { + Field target = throwableField.computeIfAbsent(holder.getClass(), clz -> { + Field[] clzFields = clz.getDeclaredFields(); + for(Field f : clzFields) { + if(Throwable.class.isAssignableFrom(f.getType())) { + f.setAccessible(true); + return f; + } + } + return null; + }); + if(target != null) { + target.set(holder, singletonThrowable); + numCleared++; + } + } + } catch(RuntimeException | ReflectiveOperationException | NoClassDefFoundError ignored) { + } + ModernFix.LOGGER.debug("Cleared " + numCleared + " object holder stacktrace references"); + } + } +} From 3922c3ec265732063e109da84b7ff8e970ed4d6c Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 16 Apr 2023 11:55:00 -0400 Subject: [PATCH 05/13] Reduce memory usage of dynamic CTMPackReloadListener --- .../modernfix/duck/IExtendedModelBakery.java | 9 +++-- .../dynamic_resources/ModelBakeryMixin.java | 8 +++- .../ctm/CTMPackReloadListenerMixin.java | 39 +++++++++++-------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/embeddedt/modernfix/duck/IExtendedModelBakery.java b/src/main/java/org/embeddedt/modernfix/duck/IExtendedModelBakery.java index 34ce3862..49435254 100644 --- a/src/main/java/org/embeddedt/modernfix/duck/IExtendedModelBakery.java +++ b/src/main/java/org/embeddedt/modernfix/duck/IExtendedModelBakery.java @@ -1,8 +1,11 @@ package org.embeddedt.modernfix.duck; - -import net.minecraft.client.renderer.texture.AtlasSet; +import com.google.common.collect.ImmutableList; +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; public interface IExtendedModelBakery { - AtlasSet getUnfinishedAtlasSet(); + ImmutableList getBlockStatesForMRL(StateDefinition stateDefinition, ModelResourceLocation location); } diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java index 7ff9e892..2eb96122 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java @@ -39,6 +39,7 @@ import net.minecraftforge.registries.ForgeRegistryEntry; import org.apache.commons.lang3.tuple.Triple; import org.apache.logging.log4j.Logger; import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.duck.IExtendedModelBakery; import org.embeddedt.modernfix.dynamicresources.DynamicBakedModelProvider; import org.embeddedt.modernfix.dynamicresources.DynamicModelBakeEvent; import org.embeddedt.modernfix.dynamicresources.ModelLocationCache; @@ -64,7 +65,7 @@ import java.util.stream.Stream; /* high priority so that our injectors are added before other mods' */ @Mixin(value = ModelBakery.class, priority = 600) -public abstract class ModelBakeryMixin { +public abstract class ModelBakeryMixin implements IExtendedModelBakery { private static final boolean debugDynamicModelLoading = Boolean.getBoolean("modernfix.debugDynamicModelLoading"); @@ -438,6 +439,11 @@ public abstract class ModelBakeryMixin { return ImmutableList.copyOf(finalList); } + @Override + public ImmutableList getBlockStatesForMRL(StateDefinition stateDefinition, ModelResourceLocation location) { + return loadOnlyRelevantBlockState(stateDefinition, location); + } + @Inject(method = "getBakedModel", at = @At("HEAD"), cancellable = true) public void getOrLoadBakedModelDynamic(ResourceLocation arg, ModelState arg2, Function textureGetter, CallbackInfoReturnable cir) { Triple triple = Triple.of(arg, arg2.getRotation(), arg2.isUvLocked()); diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/CTMPackReloadListenerMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/CTMPackReloadListenerMixin.java index 9799f23b..63c7404b 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/CTMPackReloadListenerMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/CTMPackReloadListenerMixin.java @@ -1,5 +1,6 @@ package org.embeddedt.modernfix.mixin.perf.dynamic_resources.ctm; +import com.google.common.collect.ImmutableList; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.RenderType; @@ -8,13 +9,17 @@ import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.ModelResourceLocation; import net.minecraft.client.resources.model.MultiPartBakedModel; import net.minecraft.client.resources.model.WeightedBakedModel; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.IRegistryDelegate; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.duck.IExtendedModelBakery; import org.embeddedt.modernfix.dynamicresources.DynamicModelBakeEvent; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -37,8 +42,6 @@ public abstract class CTMPackReloadListenerMixin { @Shadow protected abstract Predicate getExistingRenderCheck(Block block); - private Map locationToState = new Object2ObjectOpenHashMap<>(); - @Inject(method = "", at = @At("RETURN")) private void onInit(CallbackInfo ci) { MinecraftForge.EVENT_BUS.addListener(EventPriority.LOW, this::onModelBake); @@ -48,28 +51,30 @@ public abstract class CTMPackReloadListenerMixin { private void refreshLayerHacks() { blockRenderChecks.forEach((b, p) -> ItemBlockRenderTypes.setRenderLayer((Block) b.get(), p)); blockRenderChecks.clear(); - if(locationToState.isEmpty()) { - for(Block block : ForgeRegistries.BLOCKS.getValues()) { - for(BlockState state : block.getStateDefinition().getPossibleStates()) { - locationToState.put(BlockModelShaper.stateToModelLocation(state), state); - } - } - } } private void onModelBake(DynamicModelBakeEvent event) { if(!(event.getModel() instanceof AbstractCTMBakedModel || event.getModel() instanceof WeightedBakedModel || event.getModel() instanceof MultiPartBakedModel)) return; - BlockState state = locationToState.get(event.getLocation()); - if(state == null) + /* we construct a new ResourceLocation because an MRL is coming in */ + Block block = ForgeRegistries.BLOCKS.getValue(new ResourceLocation(event.getLocation().getNamespace(), event.getLocation().getPath())); + if(block == null || block == Blocks.AIR || blockRenderChecks.containsKey(block.delegate)) return; - Block block = state.getBlock(); - if(blockRenderChecks.containsKey(block.delegate)) + /* find all states that match this MRL */ + ImmutableList allStates; + try { + allStates = ((IExtendedModelBakery)(Object)event.getModelLoader()).getBlockStatesForMRL(block.getStateDefinition(), (ModelResourceLocation)event.getLocation()); + } catch(RuntimeException e) { + ModernFix.LOGGER.error("Couldn't get state for MRL " + event.getLocation(), e); return; - Predicate newPredicate = this.getLayerCheck(state, event.getModel()); - if(newPredicate != null) { - blockRenderChecks.put(block.delegate, this.getExistingRenderCheck(block)); - ItemBlockRenderTypes.setRenderLayer(block, newPredicate); + } + for(BlockState state : allStates) { + Predicate newPredicate = this.getLayerCheck(state, event.getModel()); + if(newPredicate != null) { + blockRenderChecks.put(block.delegate, this.getExistingRenderCheck(block)); + ItemBlockRenderTypes.setRenderLayer(block, newPredicate); + return; + } } } } From 4a626043b82873482572e489fb0943d740a6bff7 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 16 Apr 2023 12:02:50 -0400 Subject: [PATCH 06/13] Fix import --- .../org/embeddedt/modernfix/registry/ObjectHolderClearer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/embeddedt/modernfix/registry/ObjectHolderClearer.java b/src/main/java/org/embeddedt/modernfix/registry/ObjectHolderClearer.java index 6ca0f948..ca1eee0d 100644 --- a/src/main/java/org/embeddedt/modernfix/registry/ObjectHolderClearer.java +++ b/src/main/java/org/embeddedt/modernfix/registry/ObjectHolderClearer.java @@ -1,7 +1,7 @@ package org.embeddedt.modernfix.registry; import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import net.minecraftforge.fml.util.ObfuscationReflectionHelper; import net.minecraftforge.registries.ObjectHolderRegistry; import org.embeddedt.modernfix.ModernFix; From ea86bc68502e42da073b29c052640af9956682ff Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 16 Apr 2023 12:48:16 -0400 Subject: [PATCH 07/13] More aggressive interning of cached path components --- .../ModFileResourcePackMixin.java | 26 ++++--- .../modernfix/util/CachedResourcePath.java | 73 +++++++++++++++++++ 2 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java index c58b3457..84dcc9b7 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java @@ -1,12 +1,15 @@ package org.embeddedt.modernfix.mixin.perf.resourcepacks; import com.google.common.base.Joiner; +import com.google.common.collect.Interner; +import com.google.common.collect.Interners; import net.minecraft.server.packs.PackType; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.ResourcePackFileNotFoundException; import net.minecraftforge.fml.loading.moddiscovery.ModFile; import net.minecraftforge.fml.packs.ModFileResourcePack; import org.apache.commons.lang3.tuple.Triple; +import org.embeddedt.modernfix.util.CachedResourcePath; import org.embeddedt.modernfix.util.FileUtil; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -32,8 +35,8 @@ public abstract class ModFileResourcePackMixin { @Shadow(remap = false) @Final private ModFile modFile; private EnumMap> namespacesByType; - private EnumMap>>> rootListingByNamespaceAndType; - private Set containedPaths; + private EnumMap>> rootListingByNamespaceAndType; + private Set containedPaths; private boolean useNamespaceCaches; private FileSystem resourcePackFS; private static Joiner slashJoiner = Joiner.on('/'); @@ -51,21 +54,21 @@ public abstract class ModFileResourcePackMixin { this.containedPaths = new HashSet<>(); for(PackType type : PackType.values()) { Set namespaces = this.namespacesByType.get(type); - HashMap>> rootListingForNamespaces = new HashMap<>(); + HashMap> rootListingForNamespaces = new HashMap<>(); for(String namespace : namespaces) { try { Path root = modFile.getLocator().findPath(modFile, type.getDirectory(), namespace).toAbsolutePath(); try (Stream stream = Files.walk(root)) { - ArrayList> rootListingPaths = new ArrayList<>(); + ArrayList rootListingPaths = new ArrayList<>(); stream .map(path -> root.relativize(path.toAbsolutePath())) .filter(this::isValidCachedResourcePath) .forEach(path -> { if(!path.toString().endsWith(".mcmeta")) { - rootListingPaths.add(Triple.of(path.getNameCount(), path.getFileName().toString(), slashJoiner.join(path))); + rootListingPaths.add(new CachedResourcePath(path)); } String mergedPath = slashJoiner.join(type.getDirectory(), namespace, path); - this.containedPaths.add(mergedPath); + this.containedPaths.add(new CachedResourcePath(mergedPath)); }); rootListingPaths.trimToSize(); rootListingForNamespaces.put(namespace, rootListingPaths); @@ -100,7 +103,7 @@ public abstract class ModFileResourcePackMixin { @Inject(method = "hasResource(Ljava/lang/String;)Z", at = @At(value = "HEAD"), cancellable = true) private void useCacheForExistence(String path, CallbackInfoReturnable cir) { - cir.setReturnValue(this.containedPaths.contains(FileUtil.normalize(path))); + cir.setReturnValue(this.containedPaths.contains(new CachedResourcePath(path))); } @Inject(method = "getResource(Ljava/lang/String;)Ljava/io/InputStream;", at = @At(value = "INVOKE", target = "Ljava/nio/file/Files;exists(Ljava/nio/file/Path;[Ljava/nio/file/LinkOption;)Z"), cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD) @@ -123,13 +126,12 @@ public abstract class ModFileResourcePackMixin { pathIn = pathIn + "/"; final String testPath = pathIn; cir.setReturnValue(this.rootListingByNamespaceAndType.get(type).getOrDefault(resourceNamespace, Collections.emptyList()).stream(). - filter(path -> path.getLeft() <= maxDepth). // Make sure the depth is within bounds - filter(path -> path.getRight().startsWith(testPath)). // Make sure the target path is inside this one - filter(path -> filter.test(path.getMiddle())). // Test the file name against the predicate + filter(path -> path.getNameCount() <= maxDepth). // Make sure the depth is within bounds + filter(path -> path.getFullPath().startsWith(testPath)). // Make sure the target path is inside this one + filter(path -> filter.test(path.getFileName())). // Test the file name against the predicate // Finally we need to form the RL, so use the first name as the domain, and the rest as the path // It is VERY IMPORTANT that we do not rely on Path.toString as this is inconsistent between operating systems - // Join the path names ourselves to force forward slashes - map(path -> new ResourceLocation(resourceNamespace, path.getRight())). + map(path -> new ResourceLocation(resourceNamespace, path.getFullPath())). collect(Collectors.toList())); } } diff --git a/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java b/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java new file mode 100644 index 00000000..9c210f17 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java @@ -0,0 +1,73 @@ +package org.embeddedt.modernfix.util; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Interner; +import com.google.common.collect.Interners; +import com.google.common.collect.Streams; + +import java.lang.ref.WeakReference; +import java.nio.file.Path; + +public class CachedResourcePath { + private final ImmutableList pathComponents; + private int hashCode = 0; + + private static final Interner PATH_COMPONENT_INTERNER = Interners.newStrongInterner(); + private static final Splitter SLASH_SPLITTER = Splitter.on('/'); + private static final Joiner SLASH_JOINER = Joiner.on('/'); + private WeakReference fullPathCache = new WeakReference<>(null); + + public CachedResourcePath(Iterable components) { + ImmutableList.Builder b = ImmutableList.builder(); + for(String s : components) { + if(s == null || s.length() == 0) + continue; + b.add(PATH_COMPONENT_INTERNER.intern(s)); + } + pathComponents = b.build(); + } + + public CachedResourcePath(Path path) { + this(() -> Streams.stream(path.iterator()).map(Path::toString).iterator()); + } + + public CachedResourcePath(String s) { + this(SLASH_SPLITTER.split(s)); + } + + @Override + public int hashCode() { + int result = hashCode; + if(result != 0) + return result; + hashCode = pathComponents.hashCode(); + return hashCode; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CachedResourcePath that = (CachedResourcePath) o; + return pathComponents.equals(that.pathComponents); + } + + public String getFileName() { + return pathComponents.get(pathComponents.size() - 1); + } + + public int getNameCount() { + return pathComponents.size(); + } + + public String getFullPath() { + String fPath = fullPathCache.get(); + if(fPath == null) { + fPath = SLASH_JOINER.join(pathComponents); + fullPathCache = new WeakReference<>(fPath); + } + return fPath; + } +} From 6f176ba86d139b13060e3c68d33ec70cd05359ce Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 16 Apr 2023 12:55:01 -0400 Subject: [PATCH 08/13] Remove reference to nonexistent config option --- .../embeddedt/modernfix/core/config/ModernFixEarlyConfig.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 83080de9..71449b20 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -81,7 +81,6 @@ public class ModernFixEarlyConfig { disableIfModPresent("mixin.perf.async_jei", "modernui"); disableIfModPresent("mixin.perf.compress_biome_container", "chocolate", "betterendforge"); disableIfModPresent("mixin.bugfix.mc218112", "performant"); - disableIfModPresent("mixin.perf.faster_baking", "touhou_little_maid"); disableIfModPresent("mixin.perf.reuse_datapacks", "tac"); } From 0f3c701d2acc1e73185eaf3b36b66210b0009671 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 16 Apr 2023 12:55:52 -0400 Subject: [PATCH 09/13] Prevent missing options from crashing the game --- .../modernfix/core/config/ModernFixEarlyConfig.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 71449b20..5af30d73 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -87,7 +87,11 @@ public class ModernFixEarlyConfig { private void disableIfModPresent(String configName, String... ids) { for(String id : ids) { if(FMLLoader.getLoadingModList().getModFileById(id) != null) { - this.options.get(configName).addModOverride(false, id); + Option option = this.options.get(configName); + if(option != null) + option.addModOverride(false, id); + else + LOGGER.warn("Can't disable missing option {}", configName); } } } From f8c5c50ce4ae4157c0bb472bd5385f7700a73ee4 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 16 Apr 2023 13:15:13 -0400 Subject: [PATCH 10/13] Improve speed of cache building --- .../ModFileResourcePackMixin.java | 8 +-- .../modernfix/util/CachedResourcePath.java | 60 +++++++++++++------ 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java index 84dcc9b7..a4af0a11 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java @@ -64,11 +64,11 @@ public abstract class ModFileResourcePackMixin { .map(path -> root.relativize(path.toAbsolutePath())) .filter(this::isValidCachedResourcePath) .forEach(path -> { - if(!path.toString().endsWith(".mcmeta")) { - rootListingPaths.add(new CachedResourcePath(path)); + CachedResourcePath listing = new CachedResourcePath(path); + if(!listing.getFileName().endsWith(".mcmeta")) { + rootListingPaths.add(listing); } - String mergedPath = slashJoiner.join(type.getDirectory(), namespace, path); - this.containedPaths.add(new CachedResourcePath(mergedPath)); + this.containedPaths.add(new CachedResourcePath(new String[] { type.getDirectory(), namespace }, listing)); }); rootListingPaths.trimToSize(); rootListingForNamespaces.put(namespace, rootListingPaths); diff --git a/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java b/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java index 9c210f17..4f3d287d 100644 --- a/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java +++ b/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java @@ -9,32 +9,58 @@ import com.google.common.collect.Streams; import java.lang.ref.WeakReference; import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; public class CachedResourcePath { - private final ImmutableList pathComponents; + private final String[] pathComponents; private int hashCode = 0; private static final Interner PATH_COMPONENT_INTERNER = Interners.newStrongInterner(); private static final Splitter SLASH_SPLITTER = Splitter.on('/'); private static final Joiner SLASH_JOINER = Joiner.on('/'); private WeakReference fullPathCache = new WeakReference<>(null); - - public CachedResourcePath(Iterable components) { - ImmutableList.Builder b = ImmutableList.builder(); - for(String s : components) { - if(s == null || s.length() == 0) - continue; - b.add(PATH_COMPONENT_INTERNER.intern(s)); - } - pathComponents = b.build(); - } + private static final String[] NO_PREFIX = new String[0]; public CachedResourcePath(Path path) { - this(() -> Streams.stream(path.iterator()).map(Path::toString).iterator()); + this(NO_PREFIX, path, path.getNameCount()); } public CachedResourcePath(String s) { - this(SLASH_SPLITTER.split(s)); + this(NO_PREFIX, SLASH_SPLITTER.splitToList(s)); + } + + public CachedResourcePath(String[] prefixElements, Collection collection) { + this(prefixElements, collection, collection.size()); + } + + public CachedResourcePath(String[] prefixElements, Iterable path, int count) { + String[] components = new String[prefixElements.length + count]; + int i = 0; + while(i < prefixElements.length) { + components[i] = PATH_COMPONENT_INTERNER.intern(prefixElements[i]); + i++; + } + for(Object component : path) { + String s = component.toString(); + if(s.length() == 0) + continue; + components[i] = PATH_COMPONENT_INTERNER.intern(s); + } + pathComponents = components; + } + + public CachedResourcePath(String[] prefixElements, CachedResourcePath other) { + String[] components = new String[prefixElements.length + other.pathComponents.length]; + int i = 0; + while(i < prefixElements.length) { + components[i] = PATH_COMPONENT_INTERNER.intern(prefixElements[i]); + i++; + } + System.arraycopy(other.pathComponents, 0, components, i, other.pathComponents.length); + pathComponents = components; } @Override @@ -42,7 +68,7 @@ public class CachedResourcePath { int result = hashCode; if(result != 0) return result; - hashCode = pathComponents.hashCode(); + hashCode = Arrays.hashCode(pathComponents); return hashCode; } @@ -51,15 +77,15 @@ public class CachedResourcePath { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CachedResourcePath that = (CachedResourcePath) o; - return pathComponents.equals(that.pathComponents); + return Arrays.equals(pathComponents, that.pathComponents); } public String getFileName() { - return pathComponents.get(pathComponents.size() - 1); + return pathComponents[pathComponents.length - 1]; } public int getNameCount() { - return pathComponents.size(); + return pathComponents.length; } public String getFullPath() { From 3497adfa6db1fae5754874eda081fb7b93b91277 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 16 Apr 2023 13:21:10 -0400 Subject: [PATCH 11/13] Bugfixes --- .../mixin/perf/resourcepacks/ModFileResourcePackMixin.java | 2 +- .../java/org/embeddedt/modernfix/util/CachedResourcePath.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java index a4af0a11..147a1b4c 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java @@ -82,7 +82,7 @@ public abstract class ModFileResourcePackMixin { } private boolean isValidCachedResourcePath(Path path) { - if(path.getFileName() == null) { + if(path.getFileName() == null || path.getNameCount() == 0) { return false; } String str = path.toString(); diff --git a/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java b/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java index 4f3d287d..03a851c7 100644 --- a/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java +++ b/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java @@ -29,7 +29,8 @@ public class CachedResourcePath { } public CachedResourcePath(String s) { - this(NO_PREFIX, SLASH_SPLITTER.splitToList(s)); + // normalize so we can guarantee there are no empty sections + this(NO_PREFIX, SLASH_SPLITTER.splitToList(FileUtil.normalize(s))); } public CachedResourcePath(String[] prefixElements, Collection collection) { From dcd37391fef9e1d50866490558e401a40d0c7e75 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 16 Apr 2023 13:24:34 -0400 Subject: [PATCH 12/13] Fix incorrect merge --- .../mixin/perf/modern_resourcepacks/PathResourcePackMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathResourcePackMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathResourcePackMixin.java index 58818a89..24263287 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathResourcePackMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathResourcePackMixin.java @@ -74,7 +74,7 @@ public abstract class PathResourcePackMixin { if(!listing.getFileName().endsWith(".mcmeta")) { rootListingPaths.add(listing); } - this.containedPaths.add(new CachedResourcePath(new String[] { type.getDirectory(), namespace }, listing)); + containedPaths.add(new CachedResourcePath(new String[] { type.getDirectory(), namespace }, listing)); }); rootListingPaths.trimToSize(); rootListingForNamespaces.put(namespace, rootListingPaths); From c8523b3844c03e26a9cd90c3f834f5eade7e1639 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 16 Apr 2023 13:31:01 -0400 Subject: [PATCH 13/13] Incrementing the index is important --- .../java/org/embeddedt/modernfix/util/CachedResourcePath.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java b/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java index 03a851c7..5b32b41f 100644 --- a/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java +++ b/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java @@ -49,6 +49,7 @@ public class CachedResourcePath { if(s.length() == 0) continue; components[i] = PATH_COMPONENT_INTERNER.intern(s); + i++; } pathComponents = components; }