Merged platform diet

- Per-chunk biome stage resolver, subterrain candidate gate, section-walk heightmaps, reused history compressors

# Conflicts:
#	core/src/main/java/art/arcane/iris/generation/runtime/Engine.java
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-22 11:29:26 -04:00
commit c62a31e16b
14 files changed
+554 -149

No files matched your search

@@ -347,6 +347,13 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
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 : SubterrainChunkCells.activePlanner(complex, getDimension());
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);
@@ -406,6 +413,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;
@@ -419,7 +427,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;
@@ -668,6 +668,15 @@ public class IrisEngine implements Engine {
return gate.admitted() ? complex.getSubterrainPlanner() : null; return gate.admitted() ? complex.getSubterrainPlanner() : null;
} }
@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) {
@@ -107,6 +107,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);
} }
} }
@@ -13,12 +13,13 @@ import art.arcane.volmlib.util.collection.KList;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.BooleanSupplier;
import java.util.function.IntConsumer; import java.util.function.IntConsumer;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -38,8 +39,8 @@ public final class NativeStructureVolumeIndex {
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 +181,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,27 +209,21 @@ 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
) { ) {
synchronized (cache) { KList<NativeStructureVolume> hit = volumes.get(key);
KList<NativeStructureVolume> hit = cache.get(key); if (hit != null) {
if (hit != null) { return hit;
return hit;
}
} }
CompletableFuture<KList<NativeStructureVolume>> future = new CompletableFuture<>(); CompletableFuture<KList<NativeStructureVolume>> future = new CompletableFuture<>();
@@ -241,22 +234,16 @@ public final class NativeStructureVolumeIndex {
// Close the check-then-claim window: a racer whose cache check missed before the // Close the check-then-claim window: a racer whose cache check missed before the
// previous builder cached could claim the build slot after it retired and rebuild. // previous builder cached could claim the build slot after it retired and rebuild.
synchronized (cache) { KList<NativeStructureVolume> published = volumes.get(key);
KList<NativeStructureVolume> published = cache.get(key); if (published != null) {
if (published != null) { future.complete(published);
future.complete(published); builds.remove(key, future);
builds.remove(key, future); return published;
return published;
}
} }
try { try {
KList<NativeStructureVolume> built = loader.get(); KList<NativeStructureVolume> built = loader.get();
synchronized (cache) { volumes.publish(key, built, () -> builds.get(key) == future);
if (builds.get(key) == future) {
cache.put(key, built);
}
}
future.complete(built); future.complete(built);
return built; return built;
} catch (RuntimeException | Error error) { } catch (RuntimeException | Error error) {
@@ -303,15 +290,45 @@ public final class NativeStructureVolumeIndex {
} }
} }
private static Map<RuntimeChunkKey, KList<NativeStructureVolume>> lru(int capacity) { private static final class VolumeCache {
return new LinkedHashMap<>(16, 0.75F, true) { private final int capacity;
@Override private final ConcurrentHashMap<RuntimeChunkKey, KList<NativeStructureVolume>> entries = new ConcurrentHashMap<>();
protected boolean removeEldestEntry( private final ConcurrentLinkedQueue<RuntimeChunkKey> insertionOrder = new ConcurrentLinkedQueue<>();
Map.Entry<RuntimeChunkKey, KList<NativeStructureVolume>> eldest
) { private VolumeCache(int capacity) {
return size() > capacity; this.capacity = capacity;
}
private KList<NativeStructureVolume> get(RuntimeChunkKey key) {
return entries.get(key);
}
private void publish(RuntimeChunkKey key, KList<NativeStructureVolume> volumes, BooleanSupplier current) {
boolean[] inserted = new boolean[1];
entries.compute(key, (ignored, previous) -> {
if (!current.getAsBoolean()) {
return previous;
}
inserted[0] = previous == null;
return volumes;
});
if (!inserted[0]) {
return;
} }
}; insertionOrder.add(key);
while (entries.size() > capacity) {
RuntimeChunkKey eldest = insertionOrder.poll();
if (eldest == null) {
return;
}
entries.remove(eldest);
}
}
private void evictRuntime(int runtimeId) {
entries.keySet().removeIf(key -> key.runtimeId() == runtimeId);
insertionOrder.removeIf(key -> key.runtimeId() == runtimeId);
}
} }
static int runtimeChunkHash(int runtimeId, long chunkKey) { static int runtimeChunkHash(int runtimeId, long chunkKey) {
@@ -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) {
@@ -6,11 +6,16 @@ import java.io.ByteArrayOutputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.OptionalInt; import java.util.OptionalInt;
import java.util.zip.CRC32;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
@@ -18,6 +23,8 @@ public final class NativeTerrainReceipt {
private static final int MAGIC = 0x4952544E; private static final int MAGIC = 0x4952544E;
private static final int VERSION = 1; private static final int VERSION = 1;
private static final int MAXIMUM_BYTES = 64 * 1024 * 1024; private static final int MAXIMUM_BYTES = 64 * 1024 * 1024;
private static final ThreadLocal<Deflater> DEFLATERS =
ThreadLocal.withInitial(() -> new Deflater(Deflater.DEFAULT_COMPRESSION, true));
private NativeTerrainReceipt() { private NativeTerrainReceipt() {
} }
@@ -25,7 +32,9 @@ public final class NativeTerrainReceipt {
public static byte[] encode(SavedTerrainChunk chunk, long activationId, String epochId) throws IOException { public static byte[] encode(SavedTerrainChunk chunk, long activationId, String epochId) throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ByteArrayOutputStream bytes = new ByteArrayOutputStream();
UtfCache strings = new UtfCache(); UtfCache strings = new UtfCache();
try (DataOutputStream output = new DataOutputStream(new BufferedOutputStream(new GZIPOutputStream(bytes)))) { Deflater deflater = DEFLATERS.get();
deflater.reset();
try (DataOutputStream output = new DataOutputStream(new BufferedOutputStream(new GzipStream(bytes, deflater)))) {
output.writeInt(MAGIC); output.writeInt(MAGIC);
output.writeInt(VERSION); output.writeInt(VERSION);
output.writeInt(chunk.chunkX()); output.writeInt(chunk.chunkX());
@@ -196,6 +205,60 @@ public final class NativeTerrainReceipt {
public record Decoded(SavedTerrainChunk terrain, long activationId, String epochId) { public record Decoded(SavedTerrainChunk terrain, long activationId, String epochId) {
} }
private static final class GzipStream extends DeflaterOutputStream {
private static final int BUFFER_BYTES = 512;
private static final int TRAILER_BYTES = 8;
private static final byte[] HEADER = gzipHeader();
private final CRC32 checksum = new CRC32();
private GzipStream(OutputStream out, Deflater deflater) throws IOException {
super(out, deflater, BUFFER_BYTES);
out.write(HEADER);
}
@Override
public void write(byte[] buffer, int offset, int length) throws IOException {
super.write(buffer, offset, length);
checksum.update(buffer, offset, length);
}
@Override
public void finish() throws IOException {
if (def.finished()) {
return;
}
def.finish();
while (!def.finished()) {
int length = def.deflate(buf, 0, buf.length);
if (length > 0) {
out.write(buf, 0, length);
}
}
byte[] trailer = new byte[TRAILER_BYTES];
writeLittleEndian(trailer, 0, (int) checksum.getValue());
writeLittleEndian(trailer, 4, (int) def.getBytesRead());
out.write(trailer);
}
private static void writeLittleEndian(byte[] target, int offset, int value) {
target[offset] = (byte) value;
target[offset + 1] = (byte) (value >>> 8);
target[offset + 2] = (byte) (value >>> 16);
target[offset + 3] = (byte) (value >>> 24);
}
private static byte[] gzipHeader() {
ByteArrayOutputStream empty = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(empty)) {
gzip.finish();
} catch (IOException failure) {
throw new IllegalStateException("Unable to resolve the platform gzip header", failure);
}
return Arrays.copyOf(empty.toByteArray(), 10);
}
}
private static final class UtfCache { private static final class UtfCache {
private static final int MAXIMUM_ENTRIES = 256; private static final int MAXIMUM_ENTRIES = 256;
private static final int MAXIMUM_ENCODED_BYTES = 64 * 1024; private static final int MAXIMUM_ENCODED_BYTES = 64 * 1024;
@@ -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));
}
}
@@ -60,6 +60,15 @@ public class NativeTerrainReceiptTest {
} }
} }
@Test
public void repeatedEncodingOnOneThreadKeepsLegacyBytes() throws Exception {
for (int round = 0; round < 6; round++) {
SavedTerrainChunk chunk = chunk(round % 2 == 0);
String epoch = "epoch-" + round;
assertArrayEquals(legacyEncode(chunk, round, epoch), NativeTerrainReceipt.encode(chunk, round, epoch));
}
}
@Test @Test
public void saturatedStringCachesPreserveModifiedUtfBytes() throws Exception { public void saturatedStringCachesPreserveModifiedUtfBytes() throws Exception {
for (int padding : new int[]{0, 300}) { for (int padding : new int[]{0, 300}) {
@@ -60,6 +60,16 @@ public class SavedBiomeStoreWriteTest {
assertArrayEquals(expectedRegion(), Files.readAllBytes(regionPath(root))); assertArrayEquals(expectedRegion(), Files.readAllBytes(regionPath(root)));
} }
@Test
public void consecutiveRecordsKeepIndependentlyCompressedBytes() throws Exception {
Path root = temporaryFolder.newFolder().toPath();
SavedBiomeStore store = SavedBiomeStore.open(root);
for (int x = 0; x < 4; x++) {
store.claimAndPersist(chunk(x, 3L));
}
assertArrayEquals(expectedRegion(4), Files.readAllBytes(regionPath(root)));
}
@Test @Test
public void relaxedDurabilityPreservesRecordsWithoutForcingEachAppend() throws Exception { public void relaxedDurabilityPreservesRecordsWithoutForcingEachAppend() throws Exception {
String previousMode = System.getProperty(Durability.MODE_PROPERTY); String previousMode = System.getProperty(Durability.MODE_PROPERTY);
@@ -379,6 +389,10 @@ public class SavedBiomeStoreWriteTest {
} }
private static byte[] expectedRegion() throws IOException { private static byte[] expectedRegion() throws IOException {
return expectedRegion(1);
}
private static byte[] expectedRegion(int records) throws IOException {
ByteArrayOutputStream raw = new ByteArrayOutputStream(); ByteArrayOutputStream raw = new ByteArrayOutputStream();
DataOutputStream data = new DataOutputStream(raw); DataOutputStream data = new DataOutputStream(raw);
data.writeLong(3L); data.writeLong(3L);
@@ -392,17 +406,6 @@ public class SavedBiomeStoreWriteTest {
data.writeInt(1); data.writeInt(1);
data.write(new byte[]{0, 0, 1, (byte) 0x80, 3, 0}); data.write(new byte[]{0, 0, 1, (byte) 0x80, 3, 0});
data.write(new byte[256]); data.write(new byte[256]);
ByteArrayOutputStream body = new ByteArrayOutputStream();
DataOutputStream coordinates = new DataOutputStream(body);
coordinates.writeInt(0);
coordinates.writeInt(0);
coordinates.writeInt(raw.size());
Deflater deflater = new Deflater(Deflater.BEST_SPEED);
try (DeflaterOutputStream compressed = new DeflaterOutputStream(coordinates, deflater)) {
raw.writeTo(compressed);
} finally {
deflater.end();
}
ByteArrayOutputStream region = new ByteArrayOutputStream(); ByteArrayOutputStream region = new ByteArrayOutputStream();
DataOutputStream output = new DataOutputStream(region); DataOutputStream output = new DataOutputStream(region);
output.writeInt(0x4942494F); output.writeInt(0x4942494F);
@@ -410,9 +413,22 @@ public class SavedBiomeStoreWriteTest {
output.writeInt(0); output.writeInt(0);
output.writeInt(0); output.writeInt(0);
output.writeInt(checksum(region.toByteArray())); output.writeInt(checksum(region.toByteArray()));
output.writeInt(body.size()); for (int x = 0; x < records; x++) {
output.write(body.toByteArray()); ByteArrayOutputStream body = new ByteArrayOutputStream();
output.writeInt(checksum(body.toByteArray())); DataOutputStream coordinates = new DataOutputStream(body);
coordinates.writeInt(x);
coordinates.writeInt(0);
coordinates.writeInt(raw.size());
Deflater deflater = new Deflater(Deflater.BEST_SPEED);
try (DeflaterOutputStream compressed = new DeflaterOutputStream(coordinates, deflater)) {
raw.writeTo(compressed);
} finally {
deflater.end();
}
output.writeInt(body.size());
output.write(body.toByteArray());
output.writeInt(checksum(body.toByteArray()));
}
return region.toByteArray(); return region.toByteArray();
} }