From a6c03e9928b1b9e27dc4632503b2725b790ed770 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 16 May 2026 12:23:46 -0400 Subject: [PATCH] Rewrite biome condition optimizer inspired by 26.2 changes Thanks to https://codeberg.org/ZenXArch for making me aware of the simpler vanilla approach to achieve the same thing --- .../chunk/ExtendedPalettedContainer.java | 7 + .../mixin/core/PalettedContainerMixin.java | 18 +++ .../BiomeConditionSourceMixin.java | 62 ++++++++ .../NoiseBasedChunkGeneratorMixin.java | 61 ++++++++ .../SequenceRuleSourceMixin.java | 27 ---- .../SurfaceRulesContextMixin.java | 27 ++++ .../SurfaceSystemMixin.java | 6 + .../world/gen/ExtendedSurfaceContext.java | 16 ++ .../world/gen/SurfaceRuleOptimizer.java | 144 ------------------ 9 files changed, 197 insertions(+), 171 deletions(-) create mode 100644 src/main/java/org/embeddedt/modernfix/chunk/ExtendedPalettedContainer.java create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/core/PalettedContainerMixin.java create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/BiomeConditionSourceMixin.java create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NoiseBasedChunkGeneratorMixin.java delete mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SequenceRuleSourceMixin.java create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SurfaceRulesContextMixin.java create mode 100644 src/main/java/org/embeddedt/modernfix/world/gen/ExtendedSurfaceContext.java delete mode 100644 src/main/java/org/embeddedt/modernfix/world/gen/SurfaceRuleOptimizer.java diff --git a/src/main/java/org/embeddedt/modernfix/chunk/ExtendedPalettedContainer.java b/src/main/java/org/embeddedt/modernfix/chunk/ExtendedPalettedContainer.java new file mode 100644 index 00000000..f1e2b9b0 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/chunk/ExtendedPalettedContainer.java @@ -0,0 +1,7 @@ +package org.embeddedt.modernfix.chunk; + +import net.minecraft.world.level.chunk.Palette; + +public interface ExtendedPalettedContainer { + Palette mfix$getPalette(); +} diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/core/PalettedContainerMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/core/PalettedContainerMixin.java new file mode 100644 index 00000000..6d20a263 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/core/PalettedContainerMixin.java @@ -0,0 +1,18 @@ +package org.embeddedt.modernfix.common.mixin.core; + +import net.minecraft.world.level.chunk.Palette; +import net.minecraft.world.level.chunk.PalettedContainer; +import org.embeddedt.modernfix.chunk.ExtendedPalettedContainer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(PalettedContainer.class) +public class PalettedContainerMixin implements ExtendedPalettedContainer { + @Shadow + private volatile PalettedContainer.Data data; + + @Override + public Palette mfix$getPalette() { + return this.data.palette(); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/BiomeConditionSourceMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/BiomeConditionSourceMixin.java new file mode 100644 index 00000000..e8b76142 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/BiomeConditionSourceMixin.java @@ -0,0 +1,62 @@ +package org.embeddedt.modernfix.common.mixin.perf.optimize_surface_rules; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.SurfaceRules; +import org.embeddedt.modernfix.world.gen.ExtendedSurfaceContext; +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.callback.CallbackInfoReturnable; + +import java.util.List; +import java.util.Set; + +@Mixin(SurfaceRules.BiomeConditionSource.class) +public class BiomeConditionSourceMixin { + @Shadow + @Final + public List> biomes; + + /** + * @author Mojang, embeddedt + * @reason Hoist evaluation of the biome conditions where possible, in cases where we know the chunk contains + * no matching biomes, or only matching biomes + */ + @Inject(method = "apply(Lnet/minecraft/world/level/levelgen/SurfaceRules$Context;)Lnet/minecraft/world/level/levelgen/SurfaceRules$Condition;", at = @At("HEAD"), cancellable = true) + private void mfix$optimizeCondition(SurfaceRules.Context p_context, CallbackInfoReturnable cir) { + var possibleBiomes = ((ExtendedSurfaceContext)(Object)p_context).mfix$getPossibleBiomes(); + if (possibleBiomes != null) { + if (mfix$guaranteedNoMatch(possibleBiomes)) { + cir.setReturnValue(() -> false); + } else if (mfix$alwaysMatches(possibleBiomes)) { + cir.setReturnValue(() -> true); + } + } + } + + private boolean mfix$guaranteedNoMatch(Set> possibleBiomesInChunk) { + var testBiomes = this.biomes; + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < testBiomes.size(); i++) { + if (possibleBiomesInChunk.contains(testBiomes.get(i))) { + return false; + } + } + return true; + } + + private boolean mfix$alwaysMatches(Set> possibleBiomesInChunk) { + var testBiomes = this.biomes; + // Check each of the biomes in the chunk and see if we'd always match it + for (var biome : possibleBiomesInChunk) { + if (!testBiomes.contains(biome)) { + // at least one biome would not match + return false; + } + } + return true; + } +} diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NoiseBasedChunkGeneratorMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NoiseBasedChunkGeneratorMixin.java new file mode 100644 index 00000000..4791d620 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NoiseBasedChunkGeneratorMixin.java @@ -0,0 +1,61 @@ +package org.embeddedt.modernfix.common.mixin.perf.optimize_surface_rules; + +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import net.minecraft.core.Holder; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.WorldGenRegion; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.StructureManager; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; +import net.minecraft.world.level.levelgen.RandomState; +import org.embeddedt.modernfix.chunk.ExtendedPalettedContainer; +import org.embeddedt.modernfix.world.gen.ExtendedSurfaceContext; +import org.spongepowered.asm.mixin.Mixin; +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.NoSuchElementException; +import java.util.Set; + +@Mixin(NoiseBasedChunkGenerator.class) +public class NoiseBasedChunkGeneratorMixin { + @SuppressWarnings("unchecked") + private static void mfix$accumulate(Set> chunkBiomes, LevelChunkSection section) { + var palette = ((ExtendedPalettedContainer>)section.getBiomes()).mfix$getPalette(); + for (int i = 0; i < palette.getSize(); i++) { + chunkBiomes.add(palette.valueFor(i).unwrapKey().orElseThrow()); + } + } + + private static Set> mfix$obtainBiomes(WorldGenRegion region, int chunkRadius) { + Set> chunkBiomes = new ReferenceOpenHashSet<>(); + ChunkPos center = region.getCenter(); + for (int z = center.z - chunkRadius; z <= center.z + chunkRadius; z++) { + for (int x = center.x - chunkRadius; x <= center.x + chunkRadius; x++) { + var chunk = region.getChunk(x, z); + for (var section : chunk.getSections()) { + mfix$accumulate(chunkBiomes, section); + } + } + } + return chunkBiomes; + } + + /** + * @author embeddedt + * @reason scan for all biomes in the chunk and its neighbors before building surface + */ + @Inject(method = "buildSurface(Lnet/minecraft/server/level/WorldGenRegion;Lnet/minecraft/world/level/StructureManager;Lnet/minecraft/world/level/levelgen/RandomState;Lnet/minecraft/world/level/chunk/ChunkAccess;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/NoiseBasedChunkGenerator;buildSurface(Lnet/minecraft/world/level/chunk/ChunkAccess;Lnet/minecraft/world/level/levelgen/WorldGenerationContext;Lnet/minecraft/world/level/levelgen/RandomState;Lnet/minecraft/world/level/StructureManager;Lnet/minecraft/world/level/biome/BiomeManager;Lnet/minecraft/core/Registry;Lnet/minecraft/world/level/levelgen/blending/Blender;)V")) + private void mfix$findNearbyBiomes(WorldGenRegion level, StructureManager structureManager, RandomState random, ChunkAccess chunk, CallbackInfo ci) { + try { + ExtendedSurfaceContext.COMPUTED_POSSIBLE_BIOMES.set(mfix$obtainBiomes(level, 1)); + } catch (NoSuchElementException ignored) { + // Catch in case a biome somehow does not have a key. In that case we just don't use the computed set + } + } +} 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 deleted file mode 100644 index 428ed584..00000000 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SequenceRuleSourceMixin.java +++ /dev/null @@ -1,27 +0,0 @@ -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 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/common/mixin/perf/optimize_surface_rules/SurfaceRulesContextMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SurfaceRulesContextMixin.java new file mode 100644 index 00000000..07e03efa --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SurfaceRulesContextMixin.java @@ -0,0 +1,27 @@ +package org.embeddedt.modernfix.common.mixin.perf.optimize_surface_rules; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.SurfaceRules; +import org.embeddedt.modernfix.world.gen.ExtendedSurfaceContext; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; + +import java.util.Set; + +@Mixin(SurfaceRules.Context.class) +public class SurfaceRulesContextMixin implements ExtendedSurfaceContext { + @Nullable Set> mfix$possibleBiomes; + + @Override + public void mfix$applyPossibleBiomes() { + // Copy into a field so we don't hit a thread local for each BiomeConditionSource instance + this.mfix$possibleBiomes = ExtendedSurfaceContext.COMPUTED_POSSIBLE_BIOMES.get(); + ExtendedSurfaceContext.COMPUTED_POSSIBLE_BIOMES.remove(); + } + + @Override + public @Nullable Set> mfix$getPossibleBiomes() { + return this.mfix$possibleBiomes; + } +} diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SurfaceSystemMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SurfaceSystemMixin.java index c80b375e..9c48fe9a 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SurfaceSystemMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SurfaceSystemMixin.java @@ -16,6 +16,7 @@ import net.minecraft.world.level.levelgen.SurfaceRules; import net.minecraft.world.level.levelgen.SurfaceSystem; import net.minecraft.world.level.levelgen.WorldGenerationContext; import org.embeddedt.modernfix.world.gen.ChunkBiomeLookup; +import org.embeddedt.modernfix.world.gen.ExtendedSurfaceContext; import org.embeddedt.modernfix.world.gen.PrefetchingBlockColumn; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -43,6 +44,11 @@ public class SurfaceSystemMixin { return lookup; } + @Inject(method = "buildSurface", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/SurfaceRules$RuleSource;apply(Ljava/lang/Object;)Ljava/lang/Object;", ordinal = 0)) + private void injectBiomesOnContext(CallbackInfo ci, @Local(ordinal = 0) SurfaceRules.Context surfacerules$context) { + ((ExtendedSurfaceContext)(Object) surfacerules$context).mfix$applyPossibleBiomes(); + } + @Inject(method = "buildSurface", at = @At("TAIL")) private void finishAndDisposeLookups(RandomState randomState, BiomeManager biomeManager, Registry biomes, boolean p_224652_, WorldGenerationContext context, ChunkAccess chunk, NoiseChunk noiseChunk, SurfaceRules.RuleSource ruleSource, CallbackInfo ci) { MFIX_LOOKUP_CACHE.get().dispose(); diff --git a/src/main/java/org/embeddedt/modernfix/world/gen/ExtendedSurfaceContext.java b/src/main/java/org/embeddedt/modernfix/world/gen/ExtendedSurfaceContext.java new file mode 100644 index 00000000..7312051a --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/world/gen/ExtendedSurfaceContext.java @@ -0,0 +1,16 @@ +package org.embeddedt.modernfix.world.gen; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.biome.Biome; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +public interface ExtendedSurfaceContext { + ThreadLocal>> COMPUTED_POSSIBLE_BIOMES = new ThreadLocal<>(); + + @Nullable + Set> mfix$getPossibleBiomes(); + + void mfix$applyPossibleBiomes(); +} diff --git a/src/main/java/org/embeddedt/modernfix/world/gen/SurfaceRuleOptimizer.java b/src/main/java/org/embeddedt/modernfix/world/gen/SurfaceRuleOptimizer.java deleted file mode 100644 index 37f9a889..00000000 --- a/src/main/java/org/embeddedt/modernfix/world/gen/SurfaceRuleOptimizer.java +++ /dev/null @@ -1,144 +0,0 @@ -package org.embeddedt.modernfix.world.gen; - -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; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -public class SurfaceRuleOptimizer { - public static SurfaceRules.RuleSource optimizeSequenceRuleSource(SurfaceRules.SequenceRuleSource source) { - // First pass: collect which biomes appear and count biome-gated branches - Reference2ObjectOpenHashMap, List> perBiomeSources = null; - int biomeGatedBranches = 0; - 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) { - // 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<>(); - //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 - for (var biome : biomeConditionSource.biomes) { - perBiomeSources.get(biome).add(testRuleSource.thenRun()); - } - } else { - // Non-biome-gated rule: add to every biome list and the no-match list - for (var list : perBiomeSources.values()) { - list.add(innerSource); - } - noMatchSources.add(innerSource); - } - } - @SuppressWarnings("unchecked") - ResourceKey[] biomeKeys = new ResourceKey[perBiomeSources.size()]; - SurfaceRules.RuleSource[][] sourcesPerBiome = new SurfaceRules.RuleSource[perBiomeSources.size()][]; - int i = 0; - for (var entry : Reference2ObjectMaps.fastIterable(perBiomeSources)) { - biomeKeys[i] = entry.getKey(); - sourcesPerBiome[i] = entry.getValue().toArray(new SurfaceRules.RuleSource[0]); - i++; - } - return new OptimizedBiomeLookupSequenceRule(biomeKeys, sourcesPerBiome, noMatchSources.toArray(new SurfaceRules.RuleSource[0])); - } - - public record OptimizedBiomeLookupSequenceRule( - ResourceKey[] biomeKeys, - SurfaceRules.RuleSource[][] sourcesPerBiome, - SurfaceRules.RuleSource[] sourcesForNoBiomeMatch - ) implements SurfaceRules.RuleSource { - @Override - public SurfaceRules.SurfaceRule apply(SurfaceRules.Context context) { - var biomeKeys = this.biomeKeys; - var sourcesPerBiome = this.sourcesPerBiome; - Reference2ObjectOpenHashMap, List> compiledBiomeMatch = - new Reference2ObjectOpenHashMap<>(biomeKeys.length); - for (int i = 0; i < biomeKeys.length; i++) { - var uncompiled = sourcesPerBiome[i]; - SurfaceRules.SurfaceRule[] compiled = new SurfaceRules.SurfaceRule[uncompiled.length]; - for (int j = 0; j < uncompiled.length; j++) { - compiled[j] = uncompiled[j].apply(context); - } - compiledBiomeMatch.put(biomeKeys[i], List.of(compiled)); - } - var sourcesForNoBiomeMatch = this.sourcesForNoBiomeMatch; - List compiledNoMatchList; - if (sourcesForNoBiomeMatch.length > 0) { - SurfaceRules.SurfaceRule[] compiledNoMatch = new SurfaceRules.SurfaceRule[sourcesForNoBiomeMatch.length]; - for (int i = 0; i < sourcesForNoBiomeMatch.length; i++) { - compiledNoMatch[i] = sourcesForNoBiomeMatch[i].apply(context); - } - compiledNoMatchList = List.of(compiledNoMatch); - } else { - compiledNoMatchList = List.of(); - } - return new CompiledOptimizedBiomeLookupRule(compiledBiomeMatch, compiledNoMatchList, 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 - ) implements SurfaceRules.SurfaceRule { - @Override - public @Nullable BlockState tryApply(int x, int y, int z) { - var biome = context.biome.get(); - var key = (biome instanceof Holder.Reference ref) ? ref.key() : biome.unwrapKey().orElseThrow(); - var ruleList = rulesForBiomeMatch.getOrDefault(key, rulesForNoBiomeMatch); - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < ruleList.size(); i++) { - var rule = ruleList.get(i); - var state = rule.tryApply(x, y, z); - if (state != null) { - return state; - } - } - return null; - } - - @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) return false; - CompiledOptimizedBiomeLookupRule that = (CompiledOptimizedBiomeLookupRule) o; - return rulesForBiomeMatch.equals(that.rulesForBiomeMatch) && rulesForNoBiomeMatch.equals(that.rulesForNoBiomeMatch); - } - - @Override - public int hashCode() { - return Objects.hash(rulesForBiomeMatch, rulesForNoBiomeMatch); - } - } -}