diff --git a/common/src/main/java/org/embeddedt/modernfix/blockstate/FerriteCorePostProcess.java b/common/src/main/java/org/embeddedt/modernfix/blockstate/FerriteCorePostProcess.java new file mode 100644 index 00000000..372fb27d --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/blockstate/FerriteCorePostProcess.java @@ -0,0 +1,56 @@ +package org.embeddedt.modernfix.blockstate; + +import it.unimi.dsi.fastutil.objects.Object2IntArrayMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntMaps; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.StateHolder; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; + +public class FerriteCorePostProcess { + private static final boolean willPostProcess; + + private static final MethodHandle theTable, toKeyIndex; + + static { + boolean success = true; + MethodHandle table = null, keyIndex = null; + try { + Class fastMap = Class.forName("malte0811.ferritecore.fastmap.FastMap"); + Field field = fastMap.getDeclaredField("toKeyIndex"); + field.setAccessible(true); + keyIndex = MethodHandles.publicLookup().unreflectSetter(field); + field = StateHolder.class.getDeclaredField("ferritecore_globalTable"); + field.setAccessible(true); + table = MethodHandles.publicLookup().unreflectGetter(field); + } catch(ReflectiveOperationException | RuntimeException e) { + e.printStackTrace(); + success = false; + } + willPostProcess = success; + theTable = table; + toKeyIndex = keyIndex; + } + + private static final Object2IntMap EMPTY_MAP = Object2IntMaps.unmodifiable(new Object2IntArrayMap<>()); + + public static > void postProcess(StateDefinition state) { + if(!willPostProcess) + return; + try { + if(state.getProperties().size() == 0) { + for(S holder : state.getPossibleStates()) { + // deduplicate Object2IntMap objects from FerriteCore + // will probably be fixed upstream at some point, but likely not for older versions + Object table = theTable.invoke(holder); + toKeyIndex.invoke(table, EMPTY_MAP); + } + } + } catch(Throwable e) { + e.printStackTrace(); + } + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/mojang_registry_size/MappedRegistryMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/mojang_registry_size/MappedRegistryMixin.java new file mode 100644 index 00000000..d3574148 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/mojang_registry_size/MappedRegistryMixin.java @@ -0,0 +1,36 @@ +package org.embeddedt.modernfix.common.mixin.perf.mojang_registry_size; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import net.minecraft.core.MappedRegistry; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(MappedRegistry.class) +public class MappedRegistryMixin { + /** + * Avoid copying the ID list to a slightly larger one every time an entry is added to the registry. + * The original behavior causes O(n) time complexity for registration. + */ + @Redirect( + method = "registerMapping(ILnet/minecraft/resources/ResourceKey;Ljava/lang/Object;Lcom/mojang/serialization/Lifecycle;Z)Lnet/minecraft/core/Holder;", + at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/objects/ObjectList;size(I)V") + ) + private void setSizeSmart(ObjectList list, int size) { + if(list instanceof ObjectArrayList && size > list.size()) { + int requestedSize = size; + /* choose next power of two, or this value if it is a power of two */ + int p2Size = Integer.highestOneBit(size); + if(p2Size != size) + size = p2Size << 1; + // grow backing array to power-of-two size, this will return instantly in most cases + ((ObjectArrayList)list).ensureCapacity(size); + // write null entries to fill size, to match the behavior of list.size(int) + while(list.size() < requestedSize) + list.add(null); + } else { + list.size(size); + } + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/fast_registry_validation/ResourceKeyMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/mojang_registry_size/ResourceKeyMixin.java similarity index 85% rename from forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/fast_registry_validation/ResourceKeyMixin.java rename to common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/mojang_registry_size/ResourceKeyMixin.java index 0c62d3fe..7d6701db 100644 --- a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/fast_registry_validation/ResourceKeyMixin.java +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/mojang_registry_size/ResourceKeyMixin.java @@ -1,4 +1,4 @@ -package org.embeddedt.modernfix.forge.mixin.perf.fast_registry_validation; +package org.embeddedt.modernfix.common.mixin.perf.mojang_registry_size; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.resources.ResourceKey; @@ -12,7 +12,7 @@ import java.util.Map; @Mixin(ResourceKey.class) public class ResourceKeyMixin { - private static Map>> INTERNING_MAP = new Object2ObjectOpenHashMap<>(); + private static final Map>> INTERNING_MAP = new Object2ObjectOpenHashMap<>(); @Inject(method = "create(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/resources/ResourceKey;", at = @At("HEAD"), cancellable = true) private static void createEfficient(ResourceLocation parent, ResourceLocation location, CallbackInfoReturnable> cir) { synchronized (ResourceKey.class) { diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/state_definition_construct/StateDefinitionMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/state_definition_construct/StateDefinitionMixin.java index 1d32168d..70210f7b 100644 --- a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/state_definition_construct/StateDefinitionMixin.java +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/state_definition_construct/StateDefinitionMixin.java @@ -6,11 +6,15 @@ import net.minecraft.world.level.block.state.StateHolder; import net.minecraft.world.level.block.state.properties.Property; import org.embeddedt.modernfix.annotation.RequiresMod; import org.embeddedt.modernfix.blockstate.FakeStateMap; +import org.embeddedt.modernfix.blockstate.FerriteCorePostProcess; +import org.embeddedt.modernfix.platform.ModernFixPlatformHooks; 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.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.Map; @@ -27,4 +31,11 @@ public class StateDefinitionMixin> { } return new FakeStateMap<>(numStates); } + + @Inject(method = "", at = @At("TAIL")) + private void postProcess(CallbackInfo ci) { + // keep in dev only until upstream FC releases + if(ModernFixPlatformHooks.INSTANCE.isDevEnv()) + FerriteCorePostProcess.postProcess((StateDefinition)(Object)this); + } } diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/load/ModResourcePackPathFixer.java b/forge/src/main/java/org/embeddedt/modernfix/forge/load/ModResourcePackPathFixer.java new file mode 100644 index 00000000..d9e9f7c7 --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/load/ModResourcePackPathFixer.java @@ -0,0 +1,21 @@ +package org.embeddedt.modernfix.forge.load; + +import net.minecraftforge.fml.ModList; +import net.minecraftforge.forgespi.language.IModFileInfo; +import net.minecraftforge.forgespi.locating.IModFile; + +import java.nio.file.Path; +import java.util.IdentityHashMap; + +public class ModResourcePackPathFixer { + private static final IdentityHashMap modFileByPath = new IdentityHashMap<>(); + + public static synchronized IModFile getModFileByRootPath(Path path) { + if(modFileByPath.size() == 0) { + for(IModFileInfo info : ModList.get().getModFiles()) { + modFileByPath.put(info.getFile().getFilePath(), info.getFile()); + } + } + return modFileByPath.get(path); + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/resourcepacks/ForgePathPackResourcesMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/resourcepacks/ForgePathPackResourcesMixin.java index 51f0cd23..5f4c368f 100644 --- a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/resourcepacks/ForgePathPackResourcesMixin.java +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/resourcepacks/ForgePathPackResourcesMixin.java @@ -3,7 +3,10 @@ package org.embeddedt.modernfix.forge.mixin.perf.resourcepacks; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.PackResources; import net.minecraft.server.packs.PackType; +import net.minecraftforge.forgespi.locating.IModFile; import net.minecraftforge.resource.PathPackResources; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.forge.load.ModResourcePackPathFixer; import org.embeddedt.modernfix.resources.ICachingResourcePack; import org.embeddedt.modernfix.resources.NewResourcePackAdapter; import org.embeddedt.modernfix.resources.PackResourcesCacheEngine; @@ -36,12 +39,26 @@ public abstract class ForgePathPackResourcesMixin implements ICachingResourcePac private PackResourcesCacheEngine cacheEngine; + private IModFile mfix$resolveFileOverride; + @Inject(method = "", at = @At("TAIL")) - private void cacheResources(CallbackInfo ci) { + private void cacheResources(String packId, boolean isBuiltin, final Path source, CallbackInfo ci) { + // handle buggy mods instantiating at the root path, but only if they didn't override at all + // (otherwise they may have handled resolve() already) + if(((Object)this).getClass() == PathPackResources.class) + this.mfix$resolveFileOverride = ModResourcePackPathFixer.getModFileByRootPath(source); + if(this.mfix$resolveFileOverride != null) + ModernFix.LOGGER.warn("PathResourcePack base class instantiated with root path of mod file {}. This probably means a mod should be calling ResourcePackLoader.createPackForMod instead. Applying workaround.", mfix$resolveFileOverride.getFileName()); invalidateCache(); PackResourcesCacheEngine.track(this); } + @Inject(method = "resolve", at = @At("HEAD"), cancellable = true, remap = false) + private void resolveViaModFile(String[] paths, CallbackInfoReturnable cir) { + if(this.mfix$resolveFileOverride != null) + cir.setReturnValue(this.mfix$resolveFileOverride.findResource(paths)); + } + private PackResourcesCacheEngine generateResourceCache() { synchronized (this) { PackResourcesCacheEngine engine = this.cacheEngine;