Platform glue diet

- Chunk-scoped visible biome resolver without global cache, per-quart RNG or repeated runtime lookups
- Subterrain probe skips per-sample subterrain lookups in chunks without candidates
- Striped structure volume caches
- Saved biome store reuses deflaters and remembers ready region files
- Semantic journal skips repeated storage and journal stat checks
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-22 06:40:12 -04:00
1 parent af90765a92
commit 0fe2363feb
12 files changed
+450 -115

No files matched your search

@@ -342,15 +342,24 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
@BlockCoordinates @BlockCoordinates
default SubterrainCell getSubterrainCell(int x, int y, int z) { default SubterrainCell getSubterrainCell(int x, int y, int z) {
if (getComplex() == null || getDimension() == null IrisComplex complex = getComplex();
|| !getDimension().allowsSubterrainFeatures() IrisDimension dimension = getDimension();
|| getDimension().getSubterrainFeatures() == null || getDimension().getSubterrainFeatures().isEmpty()) { if (complex == null || dimension == null
|| !dimension.allowsSubterrainFeatures()
|| dimension.getSubterrainFeatures() == null || dimension.getSubterrainFeatures().isEmpty()) {
return SubterrainCell.OUTSIDE; return SubterrainCell.OUTSIDE;
} }
SubterrainPlanner planner = getComplex().getSubterrainPlanner(); SubterrainPlanner planner = complex.getSubterrainPlanner();
return planner == null ? SubterrainCell.OUTSIDE : planner.sample(x, y + getWorld().minHeight(), z); return planner == null ? SubterrainCell.OUTSIDE : planner.sample(x, y + getWorld().minHeight(), z);
} }
@ChunkCoordinates
default boolean subterrainAbsentInChunk(int chunkX, int chunkZ) {
IrisComplex complex = getComplex();
SubterrainPlanner planner = complex == null ? null : complex.getSubterrainPlanner();
return planner == null || !planner.hasCandidates(chunkX, chunkZ);
}
@BlockCoordinates @BlockCoordinates
default IrisBiome getSubterrainBiome(int x, int y, int z) { default IrisBiome getSubterrainBiome(int x, int y, int z) {
SubterrainCell cell = getSubterrainCell(x, y, z); SubterrainCell cell = getSubterrainCell(x, y, z);
@@ -410,6 +419,7 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
return; return;
} }
IrisComplex complex = getComplex(); IrisComplex complex = getComplex();
boolean subterrain = !subterrainAbsentInChunk(x >> 4, z >> 4);
int caveTop = getHeight(x, z) - 2; int caveTop = getHeight(x, z) - 2;
IrisRegion region = getRegion(x, 0, z); IrisRegion region = getRegion(x, 0, z);
IrisBiome surface = null; IrisBiome surface = null;
@@ -423,7 +433,7 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
for (int index = 0; index < biomes.length; index++) { for (int index = 0; index < biomes.length; index++) {
int y = index * step; int y = index * step;
regions[index] = region; regions[index] = region;
IrisBiome featureBiome = getSubterrainBiome(x, y, z); IrisBiome featureBiome = subterrain ? getSubterrainBiome(x, y, z) : null;
if (featureBiome != null) { if (featureBiome != null) {
biomes[index] = featureBiome; biomes[index] = featureBiome;
continue; continue;
@@ -623,6 +623,15 @@ public class IrisEngine implements Engine {
return Engine.super.getSubterrainCell(x, y, z); return Engine.super.getSubterrainCell(x, y, z);
} }
@ChunkCoordinates
@Override
public boolean subterrainAbsentInChunk(int chunkX, int chunkZ) {
if (!hasGenerationRuntimeScope() && generationHistoryRuntimeRouter != null) {
return false;
}
return Engine.super.subterrainAbsentInChunk(chunkX, chunkZ);
}
@BlockCoordinates @BlockCoordinates
@Override @Override
public IrisBiome getSubterrainBiome(int x, int y, int z) { public IrisBiome getSubterrainBiome(int x, int y, int z) {
@@ -106,6 +106,10 @@ public final class SubterrainPlanner {
return List.copyOf(result); return List.copyOf(result);
} }
public boolean hasCandidates(int chunkX, int chunkZ) {
return !isEmpty() && !candidates(chunkX, chunkZ).isEmpty();
}
public SubterrainCell sample(int x, int y, int z) { public SubterrainCell sample(int x, int y, int z) {
if (isEmpty() || y < minY || y >= maxYExclusive) { if (isEmpty() || y < minY || y >= maxYExclusive) {
return SubterrainCell.OUTSIDE; return SubterrainCell.OUTSIDE;
@@ -8,6 +8,7 @@ import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.generation.runtime.DimensionStackContext; import art.arcane.iris.generation.runtime.DimensionStackContext;
import art.arcane.iris.generation.runtime.DimensionStackLayout; import art.arcane.iris.generation.runtime.DimensionStackLayout;
import art.arcane.iris.generation.runtime.DimensionTerrainContext; import art.arcane.iris.generation.runtime.DimensionTerrainContext;
import art.arcane.iris.generation.runtime.IrisComplex;
import art.arcane.iris.generation.runtime.IrisEngine; import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.generation.runtime.Engine; import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.stream.ProvisionalSampling; import art.arcane.iris.generation.stream.ProvisionalSampling;
@@ -27,6 +28,7 @@ import org.bukkit.block.Biome;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
@@ -46,7 +48,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
private static final int STRONGHOLD_RING_SEARCH_RADIUS = 112; private static final int STRONGHOLD_RING_SEARCH_RADIUS = 112;
private static final int STRONGHOLD_RING_SEARCH_QUART_STEP = 4; private static final int STRONGHOLD_RING_SEARCH_QUART_STEP = 4;
private final long seed; private final RNG biomeRng;
private final Engine engine; private final Engine engine;
private final BukkitChunkGenerator platformGenerator; private final BukkitChunkGenerator platformGenerator;
private final NativeBiomeRegistry<H, V> biomeCustomRegistry; private final NativeBiomeRegistry<H, V> biomeCustomRegistry;
@@ -64,7 +66,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
this.engine = engine; this.engine = engine;
this.platformGenerator = options.platformGenerator(); this.platformGenerator = options.platformGenerator();
this.possibleBiomeRegistry = options.possibleBiomeRegistry(); this.possibleBiomeRegistry = options.possibleBiomeRegistry();
this.seed = options.seed(); this.biomeRng = new RNG(options.seed());
this.biomeCustomRegistry = registry; this.biomeCustomRegistry = registry;
this.biomeRegistry = biomeCustomRegistry; this.biomeRegistry = biomeCustomRegistry;
this.fallbackBiome = resolveFallbackBiome(this.biomeRegistry, this.biomeCustomRegistry); this.fallbackBiome = resolveFallbackBiome(this.biomeRegistry, this.biomeCustomRegistry);
@@ -826,7 +828,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
int z, int z,
IrisDimensionCarvingResolver.State resolverState IrisDimensionCarvingResolver.State resolverState
) { ) {
return resolveVisibleBiomeHolder(x, y, z, resolverState, null); return resolveVisibleBiomeHolder(x, y, z, resolverState, null, null);
} }
private H resolveVisibleBiomeHolder( private H resolveVisibleBiomeHolder(
@@ -835,32 +837,44 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
int z, int z,
IrisDimensionCarvingResolver.State resolverState, IrisDimensionCarvingResolver.State resolverState,
DimensionStackLayout stackLayout DimensionStackLayout stackLayout
) {
return resolveVisibleBiomeHolder(x, y, z, resolverState, stackLayout, null);
}
private H resolveVisibleBiomeHolder(
int x,
int y,
int z,
IrisDimensionCarvingResolver.State resolverState,
DimensionStackLayout stackLayout,
ChunkResolution chunk
) { ) {
int blockX = x << 2; int blockX = x << 2;
int blockY = y << 2; int blockY = y << 2;
int blockZ = z << 2; int blockZ = z << 2;
Optional<String> historicalKey = engine.getComplex().historicalPhysicalBiomeKeyAt( IrisComplex complex = chunk == null ? engine.getComplex() : chunk.complex;
Optional<String> historicalKey = complex.historicalPhysicalBiomeKeyAt(
blockX, blockY, blockZ); blockX, blockY, blockZ);
if (historicalKey.isPresent()) { if (historicalKey.isPresent()) {
return resolvePhysicalBiomeHolder(historicalKey.get()); return resolvePhysicalBiomeHolder(historicalKey.get());
} }
BiomeResolution resolution = resolveBiomeResolution( BiomeResolution resolution = resolveBiomeResolution(
x, y, z, resolverState, true, stackLayout); x, y, z, resolverState, true, stackLayout, chunk);
if (resolution == null) { if (resolution == null) {
return getFallbackBiome(); return getFallbackBiome();
} }
return resolveVisibleBiomeHolder(resolution); return resolveVisibleBiomeHolder(resolution, chunk);
} }
private H resolveVisibleBiomeHolder(BiomeResolution resolution) { private H resolveVisibleBiomeHolder(BiomeResolution resolution, ChunkResolution chunk) {
if (resolution.irisBiome.isCustom()) { if (resolution.irisBiome.isCustom()) {
return resolveCustomHolder(resolution); return resolveCustomHolder(resolution, chunk);
} }
Biome vanillaBiome = resolution.underground Biome vanillaBiome = resolution.underground
? resolution.irisBiome.getGroundBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ) ? resolution.irisBiome.getGroundBiome(biomeRng, engine, resolution.blockX, resolution.blockY, resolution.blockZ)
: resolution.irisBiome.getSkyBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ); : resolution.irisBiome.getSkyBiome(biomeRng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
H holder = biomeRegistry.lookup(vanillaBiome); H holder = biomeRegistry.lookup(vanillaBiome);
if (holder != null) { if (holder != null) {
return holder; return holder;
@@ -869,11 +883,12 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
return getFallbackBiome(); return getFallbackBiome();
} }
private H resolveCustomHolder(BiomeResolution resolution) { private H resolveCustomHolder(BiomeResolution resolution, ChunkResolution chunk) {
IrisBiomeCustom customBiome = resolution.irisBiome.getCustomBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ); IrisBiomeCustom customBiome = resolution.irisBiome.getCustomBiome(biomeRng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
if (customBiome != null) { if (customBiome != null) {
H holder = runtimeBiomeState().customBiomes().get(customBiomeKey( H holder = chunk == null
resolution.dimension, customBiome.getId())); ? runtimeBiomeState().customBiomes().get(customBiomeKey(resolution.dimension, customBiome.getId()))
: chunk.customHolder(resolution.dimension, customBiome);
if (holder != null) { if (holder != null) {
return holder; return holder;
} }
@@ -883,7 +898,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
} }
private BiomeResolution resolveStructureBiomeResolution(int x, int y, int z) { private BiomeResolution resolveStructureBiomeResolution(int x, int y, int z) {
return resolveBiomeResolution(x, y, z, null, false); return resolveBiomeResolution(x, y, z, null, false, null, null);
} }
private H resolvePhysicalBiomeHolder(String physicalKey) { private H resolvePhysicalBiomeHolder(String physicalKey) {
@@ -898,65 +913,50 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
return holder; return holder;
} }
private BiomeResolution resolveBiomeResolution(
int x,
int y,
int z,
IrisDimensionCarvingResolver.State resolverState
) {
return resolveBiomeResolution(x, y, z, resolverState, true);
}
private BiomeResolution resolveBiomeResolution(
int x,
int y,
int z,
IrisDimensionCarvingResolver.State resolverState,
boolean includeDimensionStack
) {
return resolveBiomeResolution(
x, y, z, resolverState, includeDimensionStack, null);
}
private BiomeResolution resolveBiomeResolution( private BiomeResolution resolveBiomeResolution(
int x, int x,
int y, int y,
int z, int z,
IrisDimensionCarvingResolver.State resolverState, IrisDimensionCarvingResolver.State resolverState,
boolean includeDimensionStack, boolean includeDimensionStack,
DimensionStackLayout stackLayout DimensionStackLayout stackLayout,
ChunkResolution chunk
) { ) {
if (engine == null || engine.isClosed()) { if (engine == null || engine.isClosed()) {
return null; return null;
} }
if (engine.getComplex() == null) { if ((chunk == null ? engine.getComplex() : chunk.complex) == null) {
return null; return null;
} }
int blockX = x << 2; int blockX = x << 2;
int blockZ = z << 2; int blockZ = z << 2;
int blockY = y << 2; int blockY = y << 2;
int worldMinHeight = engine.getWorld().minHeight(); int worldMinHeight = chunk == null ? engine.getWorld().minHeight() : chunk.worldMinHeight;
int internalY = blockY - worldMinHeight; int internalY = blockY - worldMinHeight;
int caveSwitchInternalY = Math.max(-8 - worldMinHeight, 40); int caveSwitchInternalY = Math.max(-8 - worldMinHeight, 40);
IrisBiome featureBiome = engine.getSubterrainBiome(blockX, internalY, blockZ); IrisBiome featureBiome = chunk != null && !chunk.subterrain
? null : engine.getSubterrainBiome(blockX, internalY, blockZ);
if (featureBiome != null) { if (featureBiome != null) {
return createBiomeResolution(featureBiome, true, engine.getDimension(), blockX, blockY, blockZ); return new BiomeResolution(featureBiome, true, chunk == null ? engine.getDimension() : chunk.dimension,
blockX, blockY, blockZ);
} }
DimensionStackLayout.Layer stackLayer = includeDimensionStack DimensionStackLayout.Layer stackLayer = includeDimensionStack
? stackLayout == null ? stackLayout == null
? resolveDimensionStackLayer(engine, blockX, internalY, blockZ) ? resolveDimensionStackLayer(
chunk == null ? engine.getDimensionStackContext() : chunk.stackContext,
blockX, internalY, blockZ)
: stackLayout.layerAt(internalY) : stackLayout.layerAt(internalY)
: null; : null;
IrisDimension owningDimension = stackLayer == null IrisDimension owningDimension = stackLayer == null
? engine.getDimension() ? chunk == null ? engine.getDimension() : chunk.dimension
: stackLayer.terrainContext().getDimension(); : stackLayer.terrainContext().getDimension();
if (stackLayer != null && !stackLayer.terrainContext().isSelfReferencing()) { if (stackLayer != null && !stackLayer.terrainContext().isSelfReferencing()) {
IrisBiome stackedBiome = stackLayer.biome(); IrisBiome stackedBiome = stackLayer.biome();
return stackedBiome == null return stackedBiome == null
? null ? null
: createBiomeResolution( : new BiomeResolution(
stackedBiome, false, owningDimension, blockX, blockY, blockZ); stackedBiome, false, owningDimension, blockX, blockY, blockZ);
} }
boolean deepUnderground = internalY <= caveSwitchInternalY; boolean deepUnderground = internalY <= caveSwitchInternalY;
@@ -976,41 +976,31 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
) )
: stackLayer.biome(); : stackLayer.biome();
} else if (deepUnderground) { } else if (deepUnderground) {
int surfaceInternalY = Engine.hostHeight(engine, blockX, blockZ, true); int surfaceInternalY = chunk == null
? Engine.hostHeight(engine, blockX, blockZ, true)
: chunk.hostHeight(blockX, blockZ);
underground = internalY <= surfaceInternalY - 8; underground = internalY <= surfaceInternalY - 8;
irisBiome = underground irisBiome = underground
? engine.getCaveBiome(blockX, internalY, blockZ, resolverState) ? engine.getCaveBiome(blockX, internalY, blockZ, resolverState)
: engine.getHostSurfaceBiome(blockX, blockZ); : hostSurfaceBiome(chunk, blockX, blockZ);
} else { } else {
irisBiome = engine.getHostSurfaceBiome(blockX, blockZ); irisBiome = hostSurfaceBiome(chunk, blockX, blockZ);
} }
if (irisBiome == null && underground) { if (irisBiome == null && underground) {
irisBiome = stackLayer == null irisBiome = stackLayer == null
? engine.getHostSurfaceBiome(blockX, blockZ) ? hostSurfaceBiome(chunk, blockX, blockZ)
: stackLayer.biome(); : stackLayer.biome();
} }
if (irisBiome == null) { if (irisBiome == null) {
return null; return null;
} }
return createBiomeResolution( return new BiomeResolution(
irisBiome, underground, owningDimension, blockX, blockY, blockZ); irisBiome, underground, owningDimension, blockX, blockY, blockZ);
} }
private BiomeResolution createBiomeResolution( private IrisBiome hostSurfaceBiome(ChunkResolution chunk, int blockX, int blockZ) {
IrisBiome irisBiome, return chunk == null ? engine.getHostSurfaceBiome(blockX, blockZ) : chunk.hostSurfaceBiome(blockX, blockZ);
boolean underground,
IrisDimension dimension,
int blockX,
int blockY,
int blockZ
) {
RNG noiseRng = new RNG(seed
^ (((long) blockX) * 341873128712L)
^ (((long) blockY) * 132897987541L)
^ (((long) blockZ) * 42317861L));
return new BiomeResolution(
irisBiome, underground, dimension, blockX, blockY, blockZ, noiseRng);
} }
private H getFallbackBiome() { private H getFallbackBiome() {
@@ -1094,12 +1084,11 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
} }
private static DimensionStackLayout.Layer resolveDimensionStackLayer( private static DimensionStackLayout.Layer resolveDimensionStackLayer(
Engine engine, DimensionStackContext stackContext,
int blockX, int blockX,
int internalY, int internalY,
int blockZ int blockZ
) { ) {
DimensionStackContext stackContext = engine.getDimensionStackContext();
return stackContext == null ? null : stackContext.getLayerAt(blockX, internalY, blockZ); return stackContext == null ? null : stackContext.getLayerAt(blockX, internalY, blockZ);
} }
@@ -1136,7 +1125,6 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
private final int blockX; private final int blockX;
private final int blockY; private final int blockY;
private final int blockZ; private final int blockZ;
private final RNG rng;
private BiomeResolution( private BiomeResolution(
IrisBiome irisBiome, IrisBiome irisBiome,
@@ -1144,8 +1132,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
IrisDimension dimension, IrisDimension dimension,
int blockX, int blockX,
int blockY, int blockY,
int blockZ, int blockZ
RNG rng
) { ) {
this.irisBiome = irisBiome; this.irisBiome = irisBiome;
this.underground = underground; this.underground = underground;
@@ -1153,7 +1140,100 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
this.blockX = blockX; this.blockX = blockX;
this.blockY = blockY; this.blockY = blockY;
this.blockZ = blockZ; this.blockZ = blockZ;
this.rng = rng; }
}
private final class ChunkVisibleResolver implements VisibleResolver<H> {
private final IrisDimensionCarvingResolver.State resolverState = new IrisDimensionCarvingResolver.State();
private final boolean flat = platformGenerator != null && platformGenerator.usesFlatStudioTerrain();
private ChunkResolution chunk;
@Override
public H biome(int quartX, int quartY, int quartZ) {
if (flat) {
return fallbackBiome;
}
int chunkX = quartX >> 2;
int chunkZ = quartZ >> 2;
ChunkResolution current = chunk;
if (current == null || current.chunkX != chunkX || current.chunkZ != chunkZ) {
current = new ChunkResolution(chunkX, chunkZ);
chunk = current;
}
return resolveVisibleBiomeHolder(quartX, quartY, quartZ, resolverState, null, current);
}
}
private final class ChunkResolution {
private static final int COLUMNS = 16;
private final int chunkX;
private final int chunkZ;
private final IrisComplex complex;
private final IrisDimension dimension;
private final DimensionStackContext stackContext;
private final int worldMinHeight;
private final boolean subterrain;
private final RuntimeBiomeState<H, V> runtimeState;
private final int[] hostHeights = new int[COLUMNS];
private final boolean[] hostHeightResolved = new boolean[COLUMNS];
private final IrisBiome[] hostSurfaces = new IrisBiome[COLUMNS];
private final boolean[] hostSurfaceResolved = new boolean[COLUMNS];
private IrisDimension[] customDimensions = new IrisDimension[4];
private IrisBiomeCustom[] customBiomes = new IrisBiomeCustom[4];
private Object[] customHolders = new Object[4];
private int customCount;
private ChunkResolution(int chunkX, int chunkZ) {
this.chunkX = chunkX;
this.chunkZ = chunkZ;
this.complex = engine.getComplex();
this.dimension = engine.getDimension();
this.stackContext = engine.getDimensionStackContext();
this.worldMinHeight = engine.getWorld().minHeight();
this.subterrain = !engine.subterrainAbsentInChunk(chunkX, chunkZ);
this.runtimeState = runtimeBiomeState();
}
private int hostHeight(int blockX, int blockZ) {
int column = column(blockX, blockZ);
if (!hostHeightResolved[column]) {
hostHeights[column] = Engine.hostHeight(engine, blockX, blockZ, true);
hostHeightResolved[column] = true;
}
return hostHeights[column];
}
private IrisBiome hostSurfaceBiome(int blockX, int blockZ) {
int column = column(blockX, blockZ);
if (!hostSurfaceResolved[column]) {
hostSurfaces[column] = engine.getHostSurfaceBiome(blockX, blockZ);
hostSurfaceResolved[column] = true;
}
return hostSurfaces[column];
}
@SuppressWarnings("unchecked")
private H customHolder(IrisDimension owner, IrisBiomeCustom custom) {
for (int index = 0; index < customCount; index++) {
if (customBiomes[index] == custom && customDimensions[index] == owner) {
return (H) customHolders[index];
}
}
H holder = runtimeState.customBiomes().get(customBiomeKey(owner, custom.getId()));
if (customCount == customBiomes.length) {
customDimensions = Arrays.copyOf(customDimensions, customCount * 2);
customBiomes = Arrays.copyOf(customBiomes, customCount * 2);
customHolders = Arrays.copyOf(customHolders, customCount * 2);
}
customDimensions[customCount] = owner;
customBiomes[customCount] = custom;
customHolders[customCount++] = holder;
return holder;
}
private static int column(int blockX, int blockZ) {
return ((blockX & 15) >> 2) << 2 | ((blockZ & 15) >> 2);
} }
} }
@@ -1181,8 +1261,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
} }
@Override @Override
public VisibleResolver<H> visibleResolver() { public VisibleResolver<H> visibleResolver() {
IrisDimensionCarvingResolver.State state = new IrisDimensionCarvingResolver.State(); return new ChunkVisibleResolver();
return (x, y, z) -> getVisibleNoiseBiomeWithActiveGenerationLease(x, y, z, state);
} }
} }
@@ -12,8 +12,10 @@ import art.arcane.volmlib.util.cache.CacheKey;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.WeakHashMap; import java.util.WeakHashMap;
@@ -34,12 +36,13 @@ public final class NativeStructureVolumeIndex {
private static final int ORIGIN_REACH_CHUNKS = StructureOwnershipRecordView.MAX_REFERENCE_DISTANCE_CHUNKS; private static final int ORIGIN_REACH_CHUNKS = StructureOwnershipRecordView.MAX_REFERENCE_DISTANCE_CHUNKS;
private static final int MAX_CACHED_ORIGIN_CHUNKS = 16_384; private static final int MAX_CACHED_ORIGIN_CHUNKS = 16_384;
private static final int MAX_CACHED_QUERY_CHUNKS = 4_096; private static final int MAX_CACHED_QUERY_CHUNKS = 4_096;
private static final int CACHE_STRIPES = 32;
private static final Map<Engine, NativeStructureVolumeIndex> INDEXES = private static final Map<Engine, NativeStructureVolumeIndex> INDEXES =
Collections.synchronizedMap(new WeakHashMap<>()); Collections.synchronizedMap(new WeakHashMap<>());
private final OriginResolver origins; private final OriginResolver origins;
private final Map<RuntimeChunkKey, KList<NativeStructureVolume>> originCache = lru(MAX_CACHED_ORIGIN_CHUNKS); private final VolumeCache originCache = new VolumeCache(MAX_CACHED_ORIGIN_CHUNKS);
private final Map<RuntimeChunkKey, KList<NativeStructureVolume>> queryCache = lru(MAX_CACHED_QUERY_CHUNKS); private final VolumeCache queryCache = new VolumeCache(MAX_CACHED_QUERY_CHUNKS);
private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> originBuilds = private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> originBuilds =
new ConcurrentHashMap<>(); new ConcurrentHashMap<>();
private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> queryBuilds = private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> queryBuilds =
@@ -180,12 +183,10 @@ public final class NativeStructureVolumeIndex {
Engine engine, int chunkX, int chunkZ, Integer pinnedRuntimeId Engine engine, int chunkX, int chunkZ, Integer pinnedRuntimeId
) { ) {
if (pinnedRuntimeId != null) { if (pinnedRuntimeId != null) {
synchronized (originCache) { KList<NativeStructureVolume> cached = originCache.get(
KList<NativeStructureVolume> cached = originCache.get( new RuntimeChunkKey(pinnedRuntimeId, chunkKey(chunkX, chunkZ)));
new RuntimeChunkKey(pinnedRuntimeId, chunkKey(chunkX, chunkZ))); if (cached != null) {
if (cached != null) { return cached;
return cached;
}
} }
} }
return originVolumes(engine, chunkX, chunkZ); return originVolumes(engine, chunkX, chunkZ);
@@ -210,22 +211,19 @@ public final class NativeStructureVolumeIndex {
} }
void evictRuntime(int runtimeId) { void evictRuntime(int runtimeId) {
synchronized (originCache) { originBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
originBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId); originCache.evictRuntime(runtimeId);
originCache.keySet().removeIf(key -> key.runtimeId() == runtimeId); queryBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
} queryCache.evictRuntime(runtimeId);
synchronized (queryCache) {
queryBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
queryCache.keySet().removeIf(key -> key.runtimeId() == runtimeId);
}
} }
private KList<NativeStructureVolume> cached( private KList<NativeStructureVolume> cached(
Map<RuntimeChunkKey, KList<NativeStructureVolume>> cache, VolumeCache volumes,
ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> builds, ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> builds,
RuntimeChunkKey key, RuntimeChunkKey key,
Supplier<KList<NativeStructureVolume>> loader Supplier<KList<NativeStructureVolume>> loader
) { ) {
Map<RuntimeChunkKey, KList<NativeStructureVolume>> cache = volumes.stripe(key);
synchronized (cache) { synchronized (cache) {
KList<NativeStructureVolume> hit = cache.get(key); KList<NativeStructureVolume> hit = cache.get(key);
if (hit != null) { if (hit != null) {
@@ -314,6 +312,39 @@ public final class NativeStructureVolumeIndex {
}; };
} }
private static final class VolumeCache {
private final List<Map<RuntimeChunkKey, KList<NativeStructureVolume>>> stripes;
private VolumeCache(int capacity) {
int stripeCapacity = Math.max(1, capacity / CACHE_STRIPES);
List<Map<RuntimeChunkKey, KList<NativeStructureVolume>>> created = new ArrayList<>(CACHE_STRIPES);
for (int stripe = 0; stripe < CACHE_STRIPES; stripe++) {
created.add(lru(stripeCapacity));
}
stripes = List.copyOf(created);
}
private Map<RuntimeChunkKey, KList<NativeStructureVolume>> stripe(RuntimeChunkKey key) {
int hash = key.hashCode();
return stripes.get((hash ^ (hash >>> 16)) & (CACHE_STRIPES - 1));
}
private KList<NativeStructureVolume> get(RuntimeChunkKey key) {
Map<RuntimeChunkKey, KList<NativeStructureVolume>> cache = stripe(key);
synchronized (cache) {
return cache.get(key);
}
}
private void evictRuntime(int runtimeId) {
for (Map<RuntimeChunkKey, KList<NativeStructureVolume>> cache : stripes) {
synchronized (cache) {
cache.keySet().removeIf(key -> key.runtimeId() == runtimeId);
}
}
}
}
static int runtimeChunkHash(int runtimeId, long chunkKey) { static int runtimeChunkHash(int runtimeId, long chunkKey) {
return Long.hashCode(CacheKey.mix(chunkKey)) * 31 + runtimeId; return Long.hashCode(CacheKey.mix(chunkKey)) * 31 + runtimeId;
} }
@@ -155,7 +155,7 @@ public final class GenerationSemanticCapture {
int minimumX = Math.multiplyExact(chunkX, CHUNK_SIZE); int minimumX = Math.multiplyExact(chunkX, CHUNK_SIZE);
int minimumZ = Math.multiplyExact(chunkZ, CHUNK_SIZE); int minimumZ = Math.multiplyExact(chunkZ, CHUNK_SIZE);
BitSet resolvedCaves = new BitSet(); BitSet resolvedCaves = new BitSet();
CaveBiomes caveBiomes = new CaveBiomes(engine, scoped); CaveBiomes caveBiomes = new CaveBiomes(engine, scoped, chunkX, chunkZ);
mantle.iterateChunk(chunkX, chunkZ, MatterCavern.class, (localX, y, localZ, cavern) -> { mantle.iterateChunk(chunkX, chunkZ, MatterCavern.class, (localX, y, localZ, cavern) -> {
if (!caveSpace.test(localX, y, localZ)) { if (!caveSpace.test(localX, y, localZ)) {
return; return;
@@ -276,15 +276,17 @@ public final class GenerationSemanticCapture {
private static final class CaveBiomes { private static final class CaveBiomes {
private final Engine engine; private final Engine engine;
private final boolean scoped; private final boolean scoped;
private final boolean subterrain;
private final CaveFallback fallback; private final CaveFallback fallback;
private final IrisDimensionCarvingResolver.State fallbackState = new IrisDimensionCarvingResolver.State(); private final IrisDimensionCarvingResolver.State fallbackState = new IrisDimensionCarvingResolver.State();
private IrisDimensionCarvingResolver.Snapshot snapshot; private IrisDimensionCarvingResolver.Snapshot snapshot;
private int minimumY; private int minimumY;
private String lastKey; private String lastKey;
private CaveBiomes(Engine engine, boolean scoped) { private CaveBiomes(Engine engine, boolean scoped, int chunkX, int chunkZ) {
this.engine = engine; this.engine = engine;
this.scoped = scoped; this.scoped = scoped;
this.subterrain = scoped && !engine.subterrainAbsentInChunk(chunkX, chunkZ);
this.fallback = scoped && engine instanceof IrisEngine irisEngine this.fallback = scoped && engine instanceof IrisEngine irisEngine
&& irisEngine.hasGenerationRuntimeScope() && !engine.getPlatformHooks().isMainThread() && irisEngine.hasGenerationRuntimeScope() && !engine.getPlatformHooks().isMainThread()
? new CaveFallback(engine.getComplex(), engine.getDimensionStackContext()) ? new CaveFallback(engine.getComplex(), engine.getDimensionStackContext())
@@ -306,7 +308,7 @@ public final class GenerationSemanticCapture {
snapshot = IrisDimensionCarvingResolver.snapshot(engine); snapshot = IrisDimensionCarvingResolver.snapshot(engine);
minimumY = engine.getWorld().minHeight(); minimumY = engine.getWorld().minHeight();
} }
IrisBiome featureBiome = engine.getSubterrainBiome(x, y, z); IrisBiome featureBiome = subterrain ? engine.getSubterrainBiome(x, y, z) : null;
if (featureBiome != null) { if (featureBiome != null) {
return featureBiome; return featureBiome;
} }
@@ -99,6 +99,7 @@ public final class GenerationSemanticIndex {
private volatile IOException journalFailure; private volatile IOException journalFailure;
private long appendingRegionKey; private long appendingRegionKey;
private RegionShard appendingBase; private RegionShard appendingBase;
private boolean journalStorageReady;
private GenerationSemanticIndex( private GenerationSemanticIndex(
Path dimensionRoot, Path dimensionRoot,
@@ -264,12 +265,13 @@ public final class GenerationSemanticIndex {
if (updates.isEmpty()) { if (updates.isEmpty()) {
return; return;
} }
ensureStorageDirectory(); ensureJournalStorage();
boolean existingJournal = journalEntries.containsKey(regionKey);
appendingRegionKey = regionKey; appendingRegionKey = regionKey;
appendingBase = baseRegion; appendingBase = baseRegion;
publicationLock.unlock(); publicationLock.unlock();
try { try {
SemanticJournal.append(directory, regionX, regionZ, updates, unpublished, durability); SemanticJournal.append(directory, regionX, regionZ, updates, unpublished, durability, existingJournal);
} catch (JournalAppendFailure failure) { } catch (JournalAppendFailure failure) {
journalFailure = failure; journalFailure = failure;
throw failure; throw failure;
@@ -1193,6 +1195,14 @@ public final class GenerationSemanticIndex {
return true; return true;
} }
private void ensureJournalStorage() throws IOException {
if (journalStorageReady) {
return;
}
ensureStorageDirectory();
journalStorageReady = true;
}
private void ensureStorageDirectory() throws IOException { private void ensureStorageDirectory() throws IOException {
validateDimensionRoot(); validateDimensionRoot();
Path irisDirectory = ensureChildDirectory(dimensionRoot, "iris"); Path irisDirectory = ensureChildDirectory(dimensionRoot, "iris");
@@ -1958,18 +1968,17 @@ public final class GenerationSemanticIndex {
int regionZ, int regionZ,
List<ChunkGenerationSemantics> claims, List<ChunkGenerationSemantics> claims,
boolean unpublished, boolean unpublished,
DeferredDurability durability DeferredDurability durability,
boolean existing
) throws IOException { ) throws IOException {
Path file = directory.resolve(journalFileName(regionX, regionZ)); Path file = directory.resolve(journalFileName(regionX, regionZ));
boolean created = !Files.exists(file, LinkOption.NOFOLLOW_LINKS); boolean created = !existing && !Files.exists(file, LinkOption.NOFOLLOW_LINKS);
if (!created && !Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS)) { if (!existing && !created && !Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS)) {
throw invalid(file, "path is not a regular file"); throw invalid(file, "path is not a regular file");
} }
FileChannel opened = FileChannel.open( FileChannel opened = existing
file, ? FileChannel.open(file, StandardOpenOption.WRITE)
StandardOpenOption.CREATE, : FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
StandardOpenOption.WRITE
);
boolean uncertain = created; boolean uncertain = created;
boolean restored = false; boolean restored = false;
try (FileChannel channel = opened) { try (FileChannel channel = opened) {
@@ -36,7 +36,8 @@ public final class SavedBiomeCapture {
Math.addExact(startX, 15), Math.addExact(startZ, 15)).isEmpty(); Math.addExact(startX, 15), Math.addExact(startZ, 15)).isEmpty();
Map<Integer, List<SavedBiomeChunk.Span>> vertical = new HashMap<>(exactVolume ? 256 : 16); Map<Integer, List<SavedBiomeChunk.Span>> vertical = new HashMap<>(exactVolume ? 256 : 16);
Cells cells = new Cells(activationId); Cells cells = new Cells(activationId);
Column column = new Column(engine, minimumY, height, exactVolume ? 1 : 4); Column column = new Column(engine, minimumY, height, exactVolume ? 1 : 4,
!engine.subterrainAbsentInChunk(stage.chunkX(), stage.chunkZ()));
for (int localX = 0; localX < 16; localX++) { for (int localX = 0; localX < 16; localX++) {
for (int localZ = 0; localZ < 16; localZ++) { for (int localZ = 0; localZ < 16; localZ++) {
int blockX = startX + localX; int blockX = startX + localX;
@@ -82,7 +83,8 @@ public final class SavedBiomeCapture {
for (int index = 0; index < column.biomes().length; index++) { for (int index = 0; index < column.biomes().length; index++) {
int worldY = minimumY + index * column.step(); int worldY = minimumY + index * column.step();
SavedBiomeChunk.Cell current = cells.cell(column.biomes()[index], column.regions()[index]); SavedBiomeChunk.Cell current = cells.cell(column.biomes()[index], column.regions()[index]);
if (floating != null && !column.engine().getSubterrainCell(blockX, index * column.step(), blockZ).occupied()) { if (floating != null && (!column.subterrain()
|| !column.engine().getSubterrainCell(blockX, index * column.step(), blockZ).occupied())) {
current = overlay(current, floating.volumeAt(blockX & 15, worldY - minimumY, blockZ & 15)); current = overlay(current, floating.volumeAt(blockX & 15, worldY - minimumY, blockZ & 15));
} }
if (transition != null) { if (transition != null) {
@@ -139,9 +141,10 @@ public final class SavedBiomeCapture {
Objects.requireNonNull(region, "region").getLoadKey()); Objects.requireNonNull(region, "region").getLoadKey());
} }
private record Column(Engine engine, int minimumY, int height, int step, IrisBiome[] biomes, IrisRegion[] regions) { private record Column(Engine engine, int minimumY, int height, int step, boolean subterrain, IrisBiome[] biomes,
private Column(Engine engine, int minimumY, int height, int step) { IrisRegion[] regions) {
this(engine, minimumY, height, step, new IrisBiome[(height + step - 1) / step], private Column(Engine engine, int minimumY, int height, int step, boolean subterrain) {
this(engine, minimumY, height, step, subterrain, new IrisBiome[(height + step - 1) / step],
new IrisRegion[(height + step - 1) / step]); new IrisRegion[(height + step - 1) / step]);
} }
} }
@@ -49,6 +49,7 @@ public final class SavedBiomeStore {
private static final int MAXIMUM_CACHED_CHUNKS = 128; private static final int MAXIMUM_CACHED_CHUNKS = 128;
private static final long MAXIMUM_CACHED_BYTES = 64L * 1024L * 1024L; private static final long MAXIMUM_CACHED_BYTES = 64L * 1024L * 1024L;
private static final int MAXIMUM_WRITE_BATCH = 64; private static final int MAXIMUM_WRITE_BATCH = 64;
private static final ThreadLocal<Deflater> DEFLATERS = ThreadLocal.withInitial(() -> new Deflater(Deflater.BEST_SPEED));
private final Path dimensionRoot; private final Path dimensionRoot;
private final Path directory; private final Path directory;
@@ -464,12 +465,16 @@ public final class SavedBiomeStore {
} }
private void ensureRegionFile(RegionIndex region) throws IOException { private void ensureRegionFile(RegionIndex region) throws IOException {
if (region.fileReady) {
return;
}
requireSafeAncestors(); requireSafeAncestors();
Files.createDirectories(directory); Files.createDirectories(directory);
if (Files.exists(region.path, LinkOption.NOFOLLOW_LINKS)) { if (Files.exists(region.path, LinkOption.NOFOLLOW_LINKS)) {
if (!Files.isRegularFile(region.path, LinkOption.NOFOLLOW_LINKS)) { if (!Files.isRegularFile(region.path, LinkOption.NOFOLLOW_LINKS)) {
throw new IOException("Saved biome region is not a regular file: " + region.path); throw new IOException("Saved biome region is not a regular file: " + region.path);
} }
region.fileReady = true;
return; return;
} }
ByteBuffer header = ByteBuffer.allocate(HEADER_BYTES); ByteBuffer header = ByteBuffer.allocate(HEADER_BYTES);
@@ -486,6 +491,7 @@ public final class SavedBiomeStore {
} }
Files.move(temporary, region.path, StandardCopyOption.ATOMIC_MOVE); Files.move(temporary, region.path, StandardCopyOption.ATOMIC_MOVE);
forceDirectory(directory); forceDirectory(directory);
region.fileReady = true;
} finally { } finally {
Files.deleteIfExists(temporary); Files.deleteIfExists(temporary);
} }
@@ -674,11 +680,10 @@ public final class SavedBiomeStore {
output.writeInt(chunk.chunkX()); output.writeInt(chunk.chunkX());
output.writeInt(chunk.chunkZ()); output.writeInt(chunk.chunkZ());
output.writeInt(raw.size()); output.writeInt(raw.size());
Deflater deflater = new Deflater(Deflater.BEST_SPEED); Deflater deflater = DEFLATERS.get();
deflater.reset();
try (DeflaterOutputStream compressed = new DeflaterOutputStream(output, deflater)) { try (DeflaterOutputStream compressed = new DeflaterOutputStream(output, deflater)) {
raw.writeTo(compressed); raw.writeTo(compressed);
} finally {
deflater.end();
} }
if (encoded.size() > MAXIMUM_RECORD_BYTES) { if (encoded.size() > MAXIMUM_RECORD_BYTES) {
throw new IOException("Saved biome chunk exceeds its encoded size limit"); throw new IOException("Saved biome chunk exceeds its encoded size limit");
@@ -847,6 +852,7 @@ public final class SavedBiomeStore {
private final int regionZ; private final int regionZ;
private final long[] offsets = new long[1024]; private final long[] offsets = new long[1024];
private final int[] lengths = new int[1024]; private final int[] lengths = new int[1024];
private boolean fileReady;
private RegionIndex(Path path, int regionX, int regionZ) { private RegionIndex(Path path, int regionX, int regionZ) {
this.path = path; this.path = path;
@@ -0,0 +1,111 @@
package art.arcane.iris.platform.bukkit.nms;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.biome.IrisBiomeCustom;
import art.arcane.iris.generation.mantle.EngineMantle;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.GenerationSessionLease;
import art.arcane.iris.generation.runtime.IrisComplex;
import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.generation.terrain.IrisDimensionCarvingResolver;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.world.IrisWorld;
import art.arcane.volmlib.nativelib.terrain.NativeBiomeRegistry;
import art.arcane.volmlib.nativelib.terrain.NativeBiomeSourcePolicy;
import art.arcane.volmlib.util.collection.KList;
import org.junit.Test;
import java.lang.reflect.Field;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import static org.junit.Assert.assertEquals;
import static org.mockito.Answers.CALLS_REAL_METHODS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class BukkitBiomeVisibleResolverTest {
@Test
public void chunkResolverMatchesThePerQuartResolution() throws Exception {
IrisBiome surface = customBiome("surface", "meadow");
IrisBiome cave = customBiome("cave", "grotto");
IrisBiome custom = customBiome("custom", "mirage");
IrisBiome feature = customBiome("feature", "bloom");
IrisDimension dimension = mock(IrisDimension.class);
when(dimension.getReachableBiomes(any())).thenReturn(new KList<>(surface, cave, custom, feature));
when(dimension.getCustomBiomeKey(anyString())).thenAnswer(invocation -> "pack:" + invocation.getArgument(0));
IrisData data = mock(IrisData.class);
when(data.customBiomeResourceKey(any(IrisDimension.class), anyString()))
.thenAnswer(invocation -> "pack:" + invocation.getArgument(1));
IrisComplex complex = mock(IrisComplex.class);
when(complex.historicalPhysicalBiomeKeyAt(anyInt(), anyInt(), anyInt())).thenReturn(Optional.empty());
EngineMantle mantle = mock(EngineMantle.class);
when(mantle.trueHeight(anyInt(), anyInt())).thenAnswer(invocation ->
120 + Math.floorMod(invocation.<Integer>getArgument(0) * 7 + invocation.<Integer>getArgument(1) * 3, 160));
IrisEngine engine = mock(IrisEngine.class);
when(engine.getDimension()).thenReturn(dimension);
when(engine.getData()).thenReturn(data);
when(engine.getComplex()).thenReturn(complex);
when(engine.getMantle()).thenReturn(mantle);
when(engine.getWorld()).thenReturn(IrisWorld.builder().minHeight(-64).maxHeight(320).build());
when(engine.getMinHeight()).thenReturn(-64);
when(engine.getCacheID()).thenReturn(1);
when(engine.acquireGenerationLease(anyString())).thenReturn(GenerationSessionLease.noop());
when(engine.answersFromNaturalTerrain(anyInt(), anyInt())).thenReturn(false);
when(engine.getHostSurfaceBiome(anyInt(), anyInt())).thenAnswer(invocation ->
Math.floorMod(invocation.<Integer>getArgument(0) + invocation.<Integer>getArgument(1), 8) == 0 ? custom : surface);
when(engine.getCaveBiome(anyInt(), anyInt(), anyInt(), any(IrisDimensionCarvingResolver.State.class)))
.thenAnswer(invocation -> invocation.<Integer>getArgument(1) % 3 == 0 ? custom : cave);
when(engine.getSubterrainBiome(anyInt(), anyInt(), anyInt())).thenAnswer(invocation ->
invocation.<Integer>getArgument(0) == 4 && invocation.<Integer>getArgument(1) == 40 ? feature : null);
when(engine.subterrainAbsentInChunk(1, 0)).thenReturn(true);
@SuppressWarnings("unchecked")
NativeBiomeRegistry<String, String> registry = mock(NativeBiomeRegistry.class);
when(registry.lookupCustom(anyString())).thenAnswer(invocation -> invocation.getArgument(0));
BukkitBiomePolicy<String, String> policy = policy(engine, registry, "fallback");
for (int chunkX = 0; chunkX <= 1; chunkX++) {
NativeBiomeSourcePolicy.VisibleResolver<String> resolver = policy.visibleResolver();
for (int quartY = -16; quartY < 80; quartY++) {
for (int localX = 0; localX < 4; localX++) {
for (int localZ = 0; localZ < 4; localZ++) {
int quartX = (chunkX << 2) + localX;
String expected = policy.getVisibleNoiseBiomeWithActiveGenerationLease(
quartX, quartY, localZ, new IrisDimensionCarvingResolver.State());
assertEquals("quart " + quartX + "," + quartY + "," + localZ, expected,
resolver.biome(quartX, quartY, localZ));
}
}
}
}
}
private static IrisBiome customBiome(String key, String customId) {
IrisBiome biome = new IrisBiome().setCustomDerivitives(new KList<>(new IrisBiomeCustom().setId(customId)));
biome.setLoadKey(key);
return biome;
}
@SuppressWarnings("unchecked")
private static BukkitBiomePolicy<String, String> policy(Engine engine, NativeBiomeRegistry<String, String> registry,
String fallback) throws Exception {
BukkitBiomePolicy<String, String> policy = mock(BukkitBiomePolicy.class, CALLS_REAL_METHODS);
set(policy, "engine", engine);
set(policy, "biomeCustomRegistry", registry);
set(policy, "biomeRegistry", registry);
set(policy, "fallbackBiome", fallback);
set(policy, "runtimeBiomeStates", new ConcurrentHashMap<>());
set(policy, "noiseBiomeCache", new ConcurrentHashMap<>());
return policy;
}
private static void set(Object target, String name, Object value) throws Exception {
Field field = BukkitBiomePolicy.class.getDeclaredField(name);
field.setAccessible(true);
field.set(target, value);
}
}
@@ -36,10 +36,12 @@ public class BukkitSubterrainBiomePrecedenceTest {
owner.setAccessible(true); owner.setAccessible(true);
owner.set(policy, engine); owner.set(policy, engine);
DimensionStackLayout stack = mock(DimensionStackLayout.class); DimensionStackLayout stack = mock(DimensionStackLayout.class);
Class<?> chunkResolution = Class.forName(BukkitBiomePolicy.class.getName() + "$ChunkResolution");
Method resolve = BukkitBiomePolicy.class.getDeclaredMethod("resolveBiomeResolution", int.class, int.class, Method resolve = BukkitBiomePolicy.class.getDeclaredMethod("resolveBiomeResolution", int.class, int.class,
int.class, IrisDimensionCarvingResolver.State.class, boolean.class, DimensionStackLayout.class); int.class, IrisDimensionCarvingResolver.State.class, boolean.class, DimensionStackLayout.class,
chunkResolution);
resolve.setAccessible(true); resolve.setAccessible(true);
Object resolved = resolve.invoke(policy, 3, 40, -5, null, true, stack); Object resolved = resolve.invoke(policy, 3, 40, -5, null, true, stack, null);
assertSame(feature, field(resolved, "irisBiome")); assertSame(feature, field(resolved, "irisBiome"));
assertSame(host, field(resolved, "dimension")); assertSame(host, field(resolved, "dimension"));
assertEquals(true, field(resolved, "underground")); assertEquals(true, field(resolved, "underground"));
@@ -0,0 +1,69 @@
package art.arcane.iris.structure.nativegen;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
import art.arcane.volmlib.util.collection.KList;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class NativeStructureVolumeIndexCacheTest {
@Test
public void concurrentQueriesBuildEachOriginOnceAndAnswerIdentically() throws Exception {
Map<Long, AtomicInteger> builds = new ConcurrentHashMap<>();
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting((engine, chunkX, chunkZ) -> {
builds.computeIfAbsent(((long) chunkX << 32) ^ (chunkZ & 0xffffffffL), key -> new AtomicInteger()).incrementAndGet();
return volumesAt(chunkX, chunkZ);
});
Engine engine = mock(Engine.class);
when(engine.getCacheID()).thenReturn(7);
KList<NativeStructureVolume> expected = NativeStructureVolumeIndex.forTesting(
(ignored, chunkX, chunkZ) -> volumesAt(chunkX, chunkZ)).resolve(engine, 0, 0, 47, 47);
ExecutorService pool = Executors.newFixedThreadPool(8);
try {
List<Future<KList<NativeStructureVolume>>> results = new ArrayList<>();
for (int task = 0; task < 32; task++) {
results.add(pool.submit(() -> index.resolve(engine, 0, 0, 47, 47)));
}
for (Future<KList<NativeStructureVolume>> result : results) {
assertEquals(expected, result.get(30, TimeUnit.SECONDS));
}
} finally {
pool.shutdown();
assertTrue(pool.awaitTermination(30, TimeUnit.SECONDS));
}
for (AtomicInteger count : builds.values()) {
assertEquals(1, count.get());
}
int distinct = builds.size();
index.evictRuntime(7);
assertEquals(expected, index.resolve(engine, 0, 0, 47, 47));
assertEquals(distinct, builds.size());
for (AtomicInteger count : builds.values()) {
assertEquals(2, count.get());
}
}
private static KList<NativeStructureVolume> volumesAt(int chunkX, int chunkZ) {
if (Math.floorMod(chunkX * 31 + chunkZ * 17, 5) != 0) {
return NativeStructureVolume.NONE;
}
int minX = chunkX << 4;
int minZ = chunkZ << 4;
return new KList<>(new NativeStructureVolume("test:" + chunkX + "_" + chunkZ,
minX - 20, -10, minZ - 20, minX + 36, 40, minZ + 36, null));
}
}