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
This commit is contained in:
parent
864c751aea
commit
a6c03e9928
|
|
@ -0,0 +1,7 @@
|
|||
package org.embeddedt.modernfix.chunk;
|
||||
|
||||
import net.minecraft.world.level.chunk.Palette;
|
||||
|
||||
public interface ExtendedPalettedContainer<T> {
|
||||
Palette<T> mfix$getPalette();
|
||||
}
|
||||
|
|
@ -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<T> implements ExtendedPalettedContainer<T> {
|
||||
@Shadow
|
||||
private volatile PalettedContainer.Data<T> data;
|
||||
|
||||
@Override
|
||||
public Palette<T> mfix$getPalette() {
|
||||
return this.data.palette();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ResourceKey<Biome>> 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<SurfaceRules.Condition> 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<ResourceKey<Biome>> 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<ResourceKey<Biome>> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ResourceKey<Biome>> chunkBiomes, LevelChunkSection section) {
|
||||
var palette = ((ExtendedPalettedContainer<Holder<Biome>>)section.getBiomes()).mfix$getPalette();
|
||||
for (int i = 0; i < palette.getSize(); i++) {
|
||||
chunkBiomes.add(palette.valueFor(i).unwrapKey().orElseThrow());
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<ResourceKey<Biome>> mfix$obtainBiomes(WorldGenRegion region, int chunkRadius) {
|
||||
Set<ResourceKey<Biome>> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SurfaceRules.SurfaceRule> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ResourceKey<Biome>> 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<ResourceKey<Biome>> mfix$getPossibleBiomes() {
|
||||
return this.mfix$possibleBiomes;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Biome> biomes, boolean p_224652_, WorldGenerationContext context, ChunkAccess chunk, NoiseChunk noiseChunk, SurfaceRules.RuleSource ruleSource, CallbackInfo ci) {
|
||||
MFIX_LOOKUP_CACHE.get().dispose();
|
||||
|
|
|
|||
|
|
@ -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<Set<ResourceKey<Biome>>> COMPUTED_POSSIBLE_BIOMES = new ThreadLocal<>();
|
||||
|
||||
@Nullable
|
||||
Set<ResourceKey<Biome>> mfix$getPossibleBiomes();
|
||||
|
||||
void mfix$applyPossibleBiomes();
|
||||
}
|
||||
|
|
@ -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<ResourceKey<Biome>, List<SurfaceRules.RuleSource>> 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<SurfaceRules.RuleSource> 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<Biome>[] 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<Biome>[] 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<ResourceKey<Biome>, List<SurfaceRules.SurfaceRule>> 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<SurfaceRules.SurfaceRule> 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<? extends SurfaceRules.RuleSource> codec() {
|
||||
throw new UnsupportedOperationException("Do not try to serialize OptimizedBiomeLookupSequenceRule");
|
||||
}
|
||||
}
|
||||
|
||||
private record CompiledOptimizedBiomeLookupRule(
|
||||
Map<ResourceKey<Biome>, List<SurfaceRules.SurfaceRule>> rulesForBiomeMatch,
|
||||
List<SurfaceRules.SurfaceRule> 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<Biome> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user