diff --git a/common/src/main/java/org/embeddedt/modernfix/blockstate/FakeStateMap.java b/common/src/main/java/org/embeddedt/modernfix/blockstate/FakeStateMap.java index ff7cc745..7cc8f22c 100644 --- a/common/src/main/java/org/embeddedt/modernfix/blockstate/FakeStateMap.java +++ b/common/src/main/java/org/embeddedt/modernfix/blockstate/FakeStateMap.java @@ -1,5 +1,6 @@ package org.embeddedt.modernfix.blockstate; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.world.level.block.state.properties.Property; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -14,6 +15,7 @@ import java.util.*; */ public class FakeStateMap implements Map, Comparable>, S> { private final Map, Comparable>[] keys; + private Map, Comparable>, S> fastLookup; private final Object[] values; private int usedSlots; public FakeStateMap(int numStates) { @@ -34,22 +36,39 @@ public class FakeStateMap implements Map, Comparable>, S> @Override public boolean containsKey(Object o) { - throw new UnsupportedOperationException(); + return getFastLookup().containsKey(o); } @Override public boolean containsValue(Object o) { - throw new UnsupportedOperationException(); + return getFastLookup().containsValue(o); + } + + @SuppressWarnings("unchecked") + private Map, Comparable>, S> getFastLookup() { + if(fastLookup == null) { + var map = new Object2ObjectOpenHashMap, Comparable>, S>(usedSlots); + Map, Comparable>[] keys = this.keys; + Object[] values = this.values; + for(int i = 0; i < usedSlots; i++) { + map.put(keys[i], (S)values[i]); + } + fastLookup = map; + } + return fastLookup; } @Override public S get(Object o) { - throw new UnsupportedOperationException(); + return getFastLookup().get(o); } @Nullable @Override public S put(Map, Comparable> propertyComparableMap, S s) { + if(fastLookup != null) { + throw new IllegalStateException("Cannot populate map after fast lookup is built"); + } keys[usedSlots] = propertyComparableMap; values[usedSlots] = s; usedSlots++; @@ -70,7 +89,7 @@ public class FakeStateMap implements Map, Comparable>, S> @Override public void clear() { - for(int i = 0; i < this.keys.length; i++) { + for(int i = 0; i < usedSlots; i++) { this.keys[i] = null; this.values[i] = null; } diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/chunk_meshing/RebuildTaskMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/chunk_meshing/RebuildTaskMixin.java new file mode 100644 index 00000000..48e4c92a --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/chunk_meshing/RebuildTaskMixin.java @@ -0,0 +1,22 @@ +package org.embeddedt.modernfix.common.mixin.perf.chunk_meshing; + +import net.minecraft.client.renderer.chunk.SectionCompiler; +import net.minecraft.core.BlockPos; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.embeddedt.modernfix.util.blockpos.SectionBlockPosIterator; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(value = SectionCompiler.class, priority = 2000) +@ClientOnlyMixin +public class RebuildTaskMixin { + /** + * @author embeddedt + * @reason Use a much faster iterator implementation than vanilla's Guava-based one. + */ + @Redirect(method = "compile", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;betweenClosed(Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/BlockPos;)Ljava/lang/Iterable;")) + private Iterable fastBetweenClosed(BlockPos firstPos, BlockPos secondPos) { + return () -> new SectionBlockPosIterator(firstPos); + } +} 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 70210f7b..b24ed70d 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,23 +6,25 @@ 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; +// This optimization requires FerriteCore to be worthwhile, otherwise the FakeStateMap degrades to hash internally @Mixin(StateDefinition.class) @RequiresMod("ferritecore") public class StateDefinitionMixin> { @Shadow @Final private ImmutableSortedMap> propertiesByName; + /** + * @author embeddedt + * @reason write states into a custom array map for fast iteration by FerriteCore, no need to waste time hashing + * and growing + */ @ModifyVariable(method = "", at = @At(value = "STORE", ordinal = 0), ordinal = 1, index = 8) private Map, Comparable>, S> useArrayMap(Map, Comparable>, S> in) { int numStates = 1; @@ -31,11 +33,4 @@ 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/common/src/main/java/org/embeddedt/modernfix/util/blockpos/SectionBlockPosIterator.java b/common/src/main/java/org/embeddedt/modernfix/util/blockpos/SectionBlockPosIterator.java new file mode 100644 index 00000000..3521680d --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/util/blockpos/SectionBlockPosIterator.java @@ -0,0 +1,39 @@ +package org.embeddedt.modernfix.util.blockpos; + +import net.minecraft.core.BlockPos; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class SectionBlockPosIterator implements Iterator { + private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); + private int index = 0; + private final int baseX, baseY, baseZ; + + public SectionBlockPosIterator(int baseX, int baseY, int baseZ) { + this.baseX = baseX; + this.baseY = baseY; + this.baseZ = baseZ; + } + + public SectionBlockPosIterator(BlockPos pos) { + this(pos.getX(), pos.getY(), pos.getZ()); + } + + @Override + public boolean hasNext() { + return index < 4096; + } + + @Override + public BlockPos next() { + int i = index; + if (i >= 4096) { + throw new NoSuchElementException(); + } + index = i + 1; + var pos = this.pos; + pos.set(this.baseX + (i & 15), this.baseY + ((i >> 8) & 15), this.baseZ + ((i >> 4) & 15)); + return pos; + } +} diff --git a/fabric/src/test/java/org/embeddedt/modernfix/testing/util/BootstrapMinecraftExtension.java b/fabric/src/test/java/org/embeddedt/modernfix/testing/util/BootstrapMinecraftExtension.java index fcda530b..cc53554b 100644 --- a/fabric/src/test/java/org/embeddedt/modernfix/testing/util/BootstrapMinecraftExtension.java +++ b/fabric/src/test/java/org/embeddedt/modernfix/testing/util/BootstrapMinecraftExtension.java @@ -1,12 +1,17 @@ package org.embeddedt.modernfix.testing.util; import net.minecraft.SharedConstants; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.server.Bootstrap; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; +import java.lang.reflect.Field; +import java.util.IdentityHashMap; + /** * Simple extension to run vanilla bootstrap, inspired by AE2. */ @@ -15,6 +20,15 @@ public class BootstrapMinecraftExtension implements Extension, BeforeAllCallback public void beforeAll(ExtensionContext context) throws Exception { SharedConstants.tryDetectVersion(); Bootstrap.bootStrap(); + // Allow blocks to be created in tests + Field field = MappedRegistry.class.getDeclaredField("unregisteredIntrusiveHolders"); + field.setAccessible(true); + if(field.get(BuiltInRegistries.BLOCK) == null) { + field.set(BuiltInRegistries.BLOCK, new IdentityHashMap<>()); + field = MappedRegistry.class.getDeclaredField("frozen"); + field.setAccessible(true); + field.setBoolean(BuiltInRegistries.BLOCK, false); + } } @Override diff --git a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/classloading/ModFileScanDataDeduplicator.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/classloading/ModFileScanDataDeduplicator.java deleted file mode 100644 index 4e607aa2..00000000 --- a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/classloading/ModFileScanDataDeduplicator.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.embeddedt.modernfix.neoforge.classloading; - -import com.google.common.collect.Interner; -import com.google.common.collect.Interners; -import net.neoforged.fml.ModList; -import net.neoforged.neoforgespi.language.ModFileScanData; -import net.neoforged.neoforgespi.locating.IModFile; -import org.objectweb.asm.Type; - -import java.lang.reflect.Field; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - -public class ModFileScanDataDeduplicator { - private final Interner typeInterner = Interners.newStrongInterner(); - - private final Function internerFn = type -> type != null ? typeInterner.intern(type) : null; - - private static Field classClazzField, parentField, interfacesField, annotationClazzField, annotationTypeField; - private static final boolean reflectionSuccessful; - - static { - boolean success = false; - try { - classClazzField = ModFileScanData.ClassData.class.getDeclaredField("clazz"); - classClazzField.setAccessible(true); - parentField = ModFileScanData.ClassData.class.getDeclaredField("parent"); - parentField.setAccessible(true); - interfacesField = ModFileScanData.ClassData.class.getDeclaredField("interfaces"); - interfacesField.setAccessible(true); - annotationClazzField = ModFileScanData.AnnotationData.class.getDeclaredField("clazz"); - annotationClazzField.setAccessible(true); - annotationTypeField = ModFileScanData.AnnotationData.class.getDeclaredField("annotationType"); - annotationTypeField.setAccessible(true); - success = true; - } catch(ReflectiveOperationException | RuntimeException e) { - } - reflectionSuccessful = success; - } - - ModFileScanDataDeduplicator() { - } - - private void runDeduplication() { - ModList.get().forEachModFile(this::deduplicateFile); - } - - private void deduplicateFile(IModFile file) { - ModFileScanData data = file.getScanResult(); - if(data != null) { - data.getClasses().forEach(this::deduplicateClass); - data.getAnnotations().forEach(this::deduplicateAnnotation); - } - } - - private void deduplicateClass(ModFileScanData.ClassData data) { - try { - Type type = (Type)classClazzField.get(data); - type = internerFn.apply(type); - classClazzField.set(data, type); - type = (Type)parentField.get(data); - type = internerFn.apply(type); - parentField.set(data, type); - Set types = (Set)interfacesField.get(data); - types = types.stream().map(internerFn).collect(Collectors.toSet()); - interfacesField.set(data, types); - } catch(ReflectiveOperationException e) { - } - } - - private void deduplicateAnnotation(ModFileScanData.AnnotationData data) { - try { - Type type = (Type)annotationClazzField.get(data); - type = internerFn.apply(type); - annotationClazzField.set(data, type); - type = (Type)annotationTypeField.get(data); - type = internerFn.apply(type); - annotationTypeField.set(data, type); - } catch(ReflectiveOperationException e) { - } - } - - public static void deduplicate() { - if(!reflectionSuccessful) - return; - new ModFileScanDataDeduplicator().runDeduplication(); - } -} diff --git a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/init/ModernFixForge.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/init/ModernFixForge.java index e74b95ae..2f552516 100644 --- a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/init/ModernFixForge.java +++ b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/init/ModernFixForge.java @@ -22,7 +22,6 @@ import org.apache.commons.lang3.tuple.Pair; import org.embeddedt.modernfix.ModernFix; import org.embeddedt.modernfix.core.ModernFixMixinPlugin; import org.embeddedt.modernfix.neoforge.ModernFixConfig; -import org.embeddedt.modernfix.neoforge.classloading.ModFileScanDataDeduplicator; import java.util.List; @@ -41,7 +40,6 @@ public class ModernFixForge { NeoForge.EVENT_BUS.register(new ModernFixClientForge(modContainer, modBus)); } modContainer.registerConfig(ModConfig.Type.COMMON, ModernFixConfig.COMMON_CONFIG); - ModFileScanDataDeduplicator.deduplicate(); } private void registerItems(RegisterEvent event) {