diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SequenceRuleSourceMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SequenceRuleSourceMixin.java index a599aa7b..428ed584 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SequenceRuleSourceMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SequenceRuleSourceMixin.java @@ -3,17 +3,25 @@ package org.embeddedt.modernfix.common.mixin.perf.optimize_surface_rules; import net.minecraft.world.level.levelgen.SurfaceRules; import org.embeddedt.modernfix.world.gen.SurfaceRuleOptimizer; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(targets = {"net/minecraft/world/level/levelgen/SurfaceRules$SequenceRuleSource"}) public class SequenceRuleSourceMixin { + @Unique + private transient SurfaceRules.RuleSource mfix$optimizedSource; + @Inject(method = "apply(Lnet/minecraft/world/level/levelgen/SurfaceRules$Context;)Lnet/minecraft/world/level/levelgen/SurfaceRules$SurfaceRule;", at = @At("HEAD"), cancellable = true) private void optimizeApply(SurfaceRules.Context context, CallbackInfoReturnable cir) { - var optimized = SurfaceRuleOptimizer.optimizeSequenceRule((SurfaceRules.SequenceRuleSource)(Object) this, context); - if (optimized != null) { - cir.setReturnValue(optimized); + var optimizedSource = mfix$optimizedSource; + if (optimizedSource == null) { + mfix$optimizedSource = optimizedSource = SurfaceRuleOptimizer.optimizeSequenceRuleSource((SurfaceRules.SequenceRuleSource)(Object) this); + } + // Must check for it not being ourselves, to avoid infinite reentrance + if (optimizedSource != (Object)this) { + cir.setReturnValue(optimizedSource.apply(context)); } } } diff --git a/src/main/java/org/embeddedt/modernfix/world/gen/SurfaceRuleOptimizer.java b/src/main/java/org/embeddedt/modernfix/world/gen/SurfaceRuleOptimizer.java index 42492fa1..40b13864 100644 --- a/src/main/java/org/embeddedt/modernfix/world/gen/SurfaceRuleOptimizer.java +++ b/src/main/java/org/embeddedt/modernfix/world/gen/SurfaceRuleOptimizer.java @@ -1,9 +1,11 @@ package org.embeddedt.modernfix.world.gen; +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; import it.unimi.dsi.fastutil.objects.Reference2ObjectMaps; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; import net.minecraft.core.Holder; import net.minecraft.resources.ResourceKey; +import net.minecraft.util.KeyDispatchDataCodec; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.levelgen.SurfaceRules; @@ -15,25 +17,33 @@ import java.util.Map; import java.util.Objects; public class SurfaceRuleOptimizer { - public static @Nullable SurfaceRules.SurfaceRule optimizeSequenceRule(SurfaceRules.SequenceRuleSource source, SurfaceRules.Context context) { + public static SurfaceRules.RuleSource optimizeSequenceRuleSource(SurfaceRules.SequenceRuleSource source) { // First pass: collect which biomes appear and count biome-gated branches - Reference2ObjectOpenHashMap, List> perBiomeSources = new Reference2ObjectOpenHashMap<>(); + Reference2ObjectOpenHashMap, List> perBiomeSources = null; int biomeGatedBranches = 0; - for (var innerSource : source.sequence()) { - if (innerSource instanceof SurfaceRules.TestRuleSource testRuleSource + var sequence = source.sequence(); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < sequence.size(); i++) { + if (sequence.get(i) instanceof SurfaceRules.TestRuleSource testRuleSource && testRuleSource.ifTrue() instanceof SurfaceRules.BiomeConditionSource biomeConditionSource) { biomeGatedBranches++; + if (perBiomeSources == null) { + perBiomeSources = new Reference2ObjectOpenHashMap<>(); + } for (var biome : biomeConditionSource.biomes) { perBiomeSources.putIfAbsent(biome, new ArrayList<>()); } } } if (biomeGatedBranches < 3) { - return null; + // Just use the source as-is, not worth optimizing + return source; } // Second pass: build per-biome source lists preserving original interleaving order List noMatchSources = new ArrayList<>(); - for (var innerSource : source.sequence()) { + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < sequence.size(); i++) { + var innerSource = sequence.get(i); if (innerSource instanceof SurfaceRules.TestRuleSource testRuleSource && testRuleSource.ifTrue() instanceof SurfaceRules.BiomeConditionSource biomeConditionSource) { // Add the inner rule (condition stripped) only to the matching biomes' lists @@ -48,23 +58,41 @@ public class SurfaceRuleOptimizer { noMatchSources.add(innerSource); } } - // Compile all source lists into rule lists - Reference2ObjectOpenHashMap, List> compiledBiomeMatch = new Reference2ObjectOpenHashMap<>(perBiomeSources.size()); - Reference2ObjectMaps.fastForEach(perBiomeSources, entry -> { - List compiled = new ArrayList<>(entry.getValue().size()); - for (var src : entry.getValue()) { - compiled.add(src.apply(context)); - } - compiledBiomeMatch.put(entry.getKey(), List.copyOf(compiled)); - }); - List compiledNoMatch = new ArrayList<>(noMatchSources.size()); - for (var src : noMatchSources) { - compiledNoMatch.add(src.apply(context)); - } - return new OptimizedBiomeLookupSequenceRule(compiledBiomeMatch, List.copyOf(compiledNoMatch), context); + return new OptimizedBiomeLookupSequenceRule(perBiomeSources, List.copyOf(noMatchSources)); } public record OptimizedBiomeLookupSequenceRule( + Reference2ObjectMap, List> sourcesForBiomeMatch, + List sourcesForNoBiomeMatch + ) implements SurfaceRules.RuleSource { + @Override + public SurfaceRules.SurfaceRule apply(SurfaceRules.Context context) { + var sourcesForBiomeMatch = this.sourcesForBiomeMatch; + Reference2ObjectOpenHashMap, List> compiledBiomeMatch = + new Reference2ObjectOpenHashMap<>(sourcesForBiomeMatch.size()); + Reference2ObjectMaps.fastForEach(sourcesForBiomeMatch, entry -> { + SurfaceRules.SurfaceRule[] compiled = new SurfaceRules.SurfaceRule[entry.getValue().size()]; + var uncompiled = entry.getValue(); + for (int i = 0; i < uncompiled.size(); i++) { + compiled[i] = uncompiled.get(i).apply(context); + } + compiledBiomeMatch.put(entry.getKey(), List.of(compiled)); + }); + var sourcesForNoBiomeMatch = this.sourcesForNoBiomeMatch; + SurfaceRules.SurfaceRule[] compiledNoMatch = new SurfaceRules.SurfaceRule[sourcesForNoBiomeMatch.size()]; + for (int i = 0; i < sourcesForNoBiomeMatch.size(); i++) { + compiledNoMatch[i] = sourcesForNoBiomeMatch.get(i).apply(context); + } + return new CompiledOptimizedBiomeLookupRule(compiledBiomeMatch, List.of(compiledNoMatch), context); + } + + @Override + public KeyDispatchDataCodec codec() { + throw new UnsupportedOperationException("Do not try to serialize OptimizedBiomeLookupSequenceRule"); + } + } + + private record CompiledOptimizedBiomeLookupRule( Map, List> rulesForBiomeMatch, List rulesForNoBiomeMatch, SurfaceRules.Context context @@ -88,7 +116,7 @@ public class SurfaceRuleOptimizer { @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; - OptimizedBiomeLookupSequenceRule that = (OptimizedBiomeLookupSequenceRule) o; + CompiledOptimizedBiomeLookupRule that = (CompiledOptimizedBiomeLookupRule) o; return rulesForBiomeMatch.equals(that.rulesForBiomeMatch) && rulesForNoBiomeMatch.equals(that.rulesForNoBiomeMatch); }