diff --git a/src/main/java/org/embeddedt/modernfix/ModernFix.java b/src/main/java/org/embeddedt/modernfix/ModernFix.java index c1601f8b..0c052d3e 100644 --- a/src/main/java/org/embeddedt/modernfix/ModernFix.java +++ b/src/main/java/org/embeddedt/modernfix/ModernFix.java @@ -18,7 +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 java.lang.management.ManagementFactory; import java.util.concurrent.CountDownLatch; @@ -91,6 +91,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/core/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 581db6af..03308b21 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -65,14 +65,17 @@ 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"); } 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); } } } 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/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/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/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java index 7af54884..3c610504 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 @@ -37,6 +37,7 @@ import net.minecraftforge.fml.ModLoader; import net.minecraftforge.registries.ForgeRegistries; import org.apache.commons.lang3.tuple.Triple; 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"); @@ -443,6 +444,11 @@ public abstract class ModelBakeryMixin { return ImmutableList.copyOf(finalList); } + @Override + public ImmutableList getBlockStatesForMRL(StateDefinition stateDefinition, ModelResourceLocation location) { + return loadOnlyRelevantBlockState(stateDefinition, location); + } + @Inject(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;Ljava/util/function/Function;)Lnet/minecraft/client/resources/model/BakedModel;", at = @At("HEAD"), cancellable = true, remap = false) 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 079575ef..4ecbe122 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; @@ -9,13 +10,17 @@ import net.minecraft.client.resources.model.ModelResourceLocation; import net.minecraft.client.resources.model.MultiPartBakedModel; import net.minecraft.client.resources.model.WeightedBakedModel; import net.minecraft.core.Holder; +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.client.ChunkRenderTypeSet; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.registries.ForgeRegistries; +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; @@ -38,8 +43,6 @@ public abstract class CTMPackReloadListenerMixin { @Shadow protected abstract ChunkRenderTypeSet 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); @@ -49,29 +52,31 @@ 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())); + Holder.Reference delegate = block != null ? ForgeRegistries.BLOCKS.getDelegateOrThrow(block) : null; + if(block == null || block == Blocks.AIR || blockRenderChecks.containsKey(delegate)) return; - Block block = state.getBlock(); - Holder.Reference delegate = ForgeRegistries.BLOCKS.getDelegateOrThrow(block); - if(blockRenderChecks.containsKey(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(delegate, this.getExistingRenderCheck(block)::contains); - ItemBlockRenderTypes.setRenderLayer(block, newPredicate); + } + for(BlockState state : allStates) { + Predicate newPredicate = this.getLayerCheck(state, event.getModel()); + if(newPredicate != null) { + blockRenderChecks.put(delegate, this.getExistingRenderCheck(block)::contains); + ItemBlockRenderTypes.setRenderLayer(block, newPredicate); + return; + } } } } diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathPackResourcesMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathPackResourcesMixin.java index 35b48835..7c23973d 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathPackResourcesMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathPackResourcesMixin.java @@ -5,6 +5,7 @@ import com.mojang.datafixers.util.Pair; import net.minecraft.server.packs.PackType; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.resource.PathPackResources; +import org.embeddedt.modernfix.util.CachedResourcePath; import org.embeddedt.modernfix.util.FileUtil; import org.jetbrains.annotations.NotNull; import org.spongepowered.asm.mixin.Final; @@ -40,9 +41,9 @@ public abstract class PathPackResourcesMixin { @Shadow @NotNull protected abstract Set getNamespacesFromDisk(PackType type); private EnumMap> namespacesByType; - private EnumMap>>> rootListingByNamespaceAndType; + private EnumMap>> rootListingByNamespaceAndType; private boolean hasGeneratedListings; - private Set containedPaths; + private Set containedPaths; private FileSystem resourcePackFS; @@ -59,24 +60,25 @@ public abstract class PathPackResourcesMixin { synchronized (this) { if(hasGeneratedListings) return; - EnumMap>>> rootListingByNamespaceAndType = new EnumMap<>(PackType.class); - HashSet containedPaths = new HashSet<>(); + EnumMap>> rootListingByNamespaceAndType = new EnumMap<>(PackType.class); + HashSet containedPaths = new HashSet<>(); for(PackType type : PackType.values()) { Set namespaces = this.getNamespacesFromDisk(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))); - String mergedPath = slashJoiner.join(type.getDirectory(), namespace, path); - containedPaths.add(mergedPath); + CachedResourcePath listing = new CachedResourcePath(path); + if(!listing.getFileName().endsWith(".mcmeta")) { + rootListingPaths.add(listing); + } + containedPaths.add(new CachedResourcePath(new String[] { type.getDirectory(), namespace }, listing)); }); rootListingPaths.trimToSize(); rootListingForNamespaces.put(namespace, rootListingPaths); @@ -94,6 +96,8 @@ public abstract class PathPackResourcesMixin { } private boolean isValidCachedResourcePath(Path path) { + if(path.getFileName() == null || path.getNameCount() == 0) + return false; String str = path.toString(); for(int i = 0; i < str.length(); i++) { if(!ResourceLocation.validPathChar(str.charAt(i))) { @@ -124,7 +128,7 @@ public abstract class PathPackResourcesMixin { @Inject(method = "hasResource(Ljava/lang/String;)Z", at = @At(value = "HEAD"), cancellable = true) private void useCacheForExistence(String path, CallbackInfoReturnable cir) { this.generateResourceCache(); - cir.setReturnValue(this.containedPaths.contains(FileUtil.normalize(path))); + cir.setReturnValue(this.containedPaths.contains(new CachedResourcePath(path))); } /** @@ -139,11 +143,11 @@ public abstract class PathPackResourcesMixin { pathIn = pathIn + "/"; final String testPath = pathIn; Collection cachedListing = this.rootListingByNamespaceAndType.get(type).getOrDefault(resourceNamespace, Collections.emptyList()).stream(). - filter(path -> path.getSecond().startsWith(testPath)). // Make sure the target path is inside this one + filter(path -> path.getFullPath().startsWith(testPath)). // Make sure the target path is inside this one // 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.getFullPath())). filter(filter::test). // Test the file name against the predicate collect(Collectors.toList()); cir.setReturnValue(cachedListing); 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..ca1eee0d --- /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.util.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"); + } + } +} 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..5b32b41f --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java @@ -0,0 +1,101 @@ +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; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class CachedResourcePath { + 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); + private static final String[] NO_PREFIX = new String[0]; + + public CachedResourcePath(Path path) { + this(NO_PREFIX, path, path.getNameCount()); + } + + public CachedResourcePath(String 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) { + 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); + i++; + } + 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 + public int hashCode() { + int result = hashCode; + if(result != 0) + return result; + hashCode = Arrays.hashCode(pathComponents); + 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 Arrays.equals(pathComponents, that.pathComponents); + } + + public String getFileName() { + return pathComponents[pathComponents.length - 1]; + } + + public int getNameCount() { + return pathComponents.length; + } + + public String getFullPath() { + String fPath = fullPathCache.get(); + if(fPath == null) { + fPath = SLASH_JOINER.join(pathComponents); + fullPathCache = new WeakReference<>(fPath); + } + return fPath; + } +} diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index 1997abd5..a2b20e27 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -60,6 +60,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", "perf.skip_first_datapack_reload.CreateWorldScreenMixin",