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);
}
@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
default IrisBiome getSubterrainBiome(int x, int y, int z) {
SubterrainCell cell = getSubterrainCell(x, y, z);
@@ -406,6 +413,7 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
return;
}
IrisComplex complex = getComplex();
boolean subterrain = !subterrainAbsentInChunk(x >> 4, z >> 4);
int caveTop = getHeight(x, z) - 2;
IrisRegion region = getRegion(x, 0, z);
IrisBiome surface = null;
@@ -419,7 +427,7 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
for (int index = 0; index < biomes.length; index++) {
int y = index * step;
regions[index] = region;
IrisBiome featureBiome = getSubterrainBiome(x, y, z);
IrisBiome featureBiome = subterrain ? getSubterrainBiome(x, y, z) : null;
if (featureBiome != null) {
biomes[index] = featureBiome;
continue;
@@ -668,6 +668,15 @@ public class IrisEngine implements Engine {
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
@Override
public IrisBiome getSubterrainBiome(int x, int y, int z) {
@@ -107,6 +107,10 @@ public final class SubterrainPlanner {
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) {
if (isEmpty() || y < minY || y >= maxYExclusive) {
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.DimensionStackLayout;
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.Engine;
import art.arcane.iris.generation.stream.ProvisionalSampling;
@@ -27,6 +28,7 @@ import org.bukkit.block.Biome;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
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_QUART_STEP = 4;
private final long seed;
private final RNG biomeRng;
private final Engine engine;
private final BukkitChunkGenerator platformGenerator;
private final NativeBiomeRegistry<H, V> biomeCustomRegistry;
@@ -64,7 +66,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
this.engine = engine;
this.platformGenerator = options.platformGenerator();
this.possibleBiomeRegistry = options.possibleBiomeRegistry();
this.seed = options.seed();
this.biomeRng = new RNG(options.seed());
this.biomeCustomRegistry = registry;
this.biomeRegistry = biomeCustomRegistry;
this.fallbackBiome = resolveFallbackBiome(this.biomeRegistry, this.biomeCustomRegistry);
@@ -826,7 +828,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
int z,
IrisDimensionCarvingResolver.State resolverState
) {
return resolveVisibleBiomeHolder(x, y, z, resolverState, null);
return resolveVisibleBiomeHolder(x, y, z, resolverState, null, null);
}
private H resolveVisibleBiomeHolder(
@@ -835,32 +837,44 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
int z,
IrisDimensionCarvingResolver.State resolverState,
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 blockY = y << 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);
if (historicalKey.isPresent()) {
return resolvePhysicalBiomeHolder(historicalKey.get());
}
BiomeResolution resolution = resolveBiomeResolution(
x, y, z, resolverState, true, stackLayout);
x, y, z, resolverState, true, stackLayout, chunk);
if (resolution == null) {
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()) {
return resolveCustomHolder(resolution);
return resolveCustomHolder(resolution, chunk);
}
Biome vanillaBiome = resolution.underground
? resolution.irisBiome.getGroundBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ)
: resolution.irisBiome.getSkyBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
? resolution.irisBiome.getGroundBiome(biomeRng, engine, resolution.blockX, resolution.blockY, resolution.blockZ)
: resolution.irisBiome.getSkyBiome(biomeRng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
H holder = biomeRegistry.lookup(vanillaBiome);
if (holder != null) {
return holder;
@@ -869,11 +883,12 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
return getFallbackBiome();
}
private H resolveCustomHolder(BiomeResolution resolution) {
IrisBiomeCustom customBiome = resolution.irisBiome.getCustomBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
private H resolveCustomHolder(BiomeResolution resolution, ChunkResolution chunk) {
IrisBiomeCustom customBiome = resolution.irisBiome.getCustomBiome(biomeRng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
if (customBiome != null) {
H holder = runtimeBiomeState().customBiomes().get(customBiomeKey(
resolution.dimension, customBiome.getId()));
H holder = chunk == null
? runtimeBiomeState().customBiomes().get(customBiomeKey(resolution.dimension, customBiome.getId()))
: chunk.customHolder(resolution.dimension, customBiome);
if (holder != null) {
return holder;
}
@@ -883,7 +898,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
}
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) {
@@ -898,65 +913,50 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
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(
int x,
int y,
int z,
IrisDimensionCarvingResolver.State resolverState,
boolean includeDimensionStack,
DimensionStackLayout stackLayout
DimensionStackLayout stackLayout,
ChunkResolution chunk
) {
if (engine == null || engine.isClosed()) {
return null;
}
if (engine.getComplex() == null) {
if ((chunk == null ? engine.getComplex() : chunk.complex) == null) {
return null;
}
int blockX = x << 2;
int blockZ = z << 2;
int blockY = y << 2;
int worldMinHeight = engine.getWorld().minHeight();
int worldMinHeight = chunk == null ? engine.getWorld().minHeight() : chunk.worldMinHeight;
int internalY = blockY - worldMinHeight;
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) {
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
? stackLayout == null
? resolveDimensionStackLayer(engine, blockX, internalY, blockZ)
? resolveDimensionStackLayer(
chunk == null ? engine.getDimensionStackContext() : chunk.stackContext,
blockX, internalY, blockZ)
: stackLayout.layerAt(internalY)
: null;
IrisDimension owningDimension = stackLayer == null
? engine.getDimension()
? chunk == null ? engine.getDimension() : chunk.dimension
: stackLayer.terrainContext().getDimension();
if (stackLayer != null && !stackLayer.terrainContext().isSelfReferencing()) {
IrisBiome stackedBiome = stackLayer.biome();
return stackedBiome == null
? null
: createBiomeResolution(
: new BiomeResolution(
stackedBiome, false, owningDimension, blockX, blockY, blockZ);
}
boolean deepUnderground = internalY <= caveSwitchInternalY;
@@ -976,41 +976,31 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
)
: stackLayer.biome();
} 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;
irisBiome = underground
? engine.getCaveBiome(blockX, internalY, blockZ, resolverState)
: engine.getHostSurfaceBiome(blockX, blockZ);
: hostSurfaceBiome(chunk, blockX, blockZ);
} else {
irisBiome = engine.getHostSurfaceBiome(blockX, blockZ);
irisBiome = hostSurfaceBiome(chunk, blockX, blockZ);
}
if (irisBiome == null && underground) {
irisBiome = stackLayer == null
? engine.getHostSurfaceBiome(blockX, blockZ)
? hostSurfaceBiome(chunk, blockX, blockZ)
: stackLayer.biome();
}
if (irisBiome == null) {
return null;
}
return createBiomeResolution(
return new BiomeResolution(
irisBiome, underground, owningDimension, blockX, blockY, blockZ);
}
private BiomeResolution createBiomeResolution(
IrisBiome irisBiome,
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 IrisBiome hostSurfaceBiome(ChunkResolution chunk, int blockX, int blockZ) {
return chunk == null ? engine.getHostSurfaceBiome(blockX, blockZ) : chunk.hostSurfaceBiome(blockX, blockZ);
}
private H getFallbackBiome() {
@@ -1094,12 +1084,11 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
}
private static DimensionStackLayout.Layer resolveDimensionStackLayer(
Engine engine,
DimensionStackContext stackContext,
int blockX,
int internalY,
int blockZ
) {
DimensionStackContext stackContext = engine.getDimensionStackContext();
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 blockY;
private final int blockZ;
private final RNG rng;
private BiomeResolution(
IrisBiome irisBiome,
@@ -1144,8 +1132,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
IrisDimension dimension,
int blockX,
int blockY,
int blockZ,
RNG rng
int blockZ
) {
this.irisBiome = irisBiome;
this.underground = underground;
@@ -1153,7 +1140,100 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
this.blockX = blockX;
this.blockY = blockY;
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
public VisibleResolver<H> visibleResolver() {
IrisDimensionCarvingResolver.State state = new IrisDimensionCarvingResolver.State();
return (x, y, z) -> getVisibleNoiseBiomeWithActiveGenerationLease(x, y, z, state);
return new ChunkVisibleResolver();
}
}
@@ -13,12 +13,13 @@ import art.arcane.volmlib.util.collection.KList;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.BooleanSupplier;
import java.util.function.IntConsumer;
import java.util.function.Supplier;
@@ -38,8 +39,8 @@ public final class NativeStructureVolumeIndex {
Collections.synchronizedMap(new WeakHashMap<>());
private final OriginResolver origins;
private final Map<RuntimeChunkKey, KList<NativeStructureVolume>> originCache = lru(MAX_CACHED_ORIGIN_CHUNKS);
private final Map<RuntimeChunkKey, KList<NativeStructureVolume>> queryCache = lru(MAX_CACHED_QUERY_CHUNKS);
private final VolumeCache originCache = new VolumeCache(MAX_CACHED_ORIGIN_CHUNKS);
private final VolumeCache queryCache = new VolumeCache(MAX_CACHED_QUERY_CHUNKS);
private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> originBuilds =
new ConcurrentHashMap<>();
private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> queryBuilds =
@@ -180,12 +181,10 @@ public final class NativeStructureVolumeIndex {
Engine engine, int chunkX, int chunkZ, Integer pinnedRuntimeId
) {
if (pinnedRuntimeId != null) {
synchronized (originCache) {
KList<NativeStructureVolume> cached = originCache.get(
new RuntimeChunkKey(pinnedRuntimeId, chunkKey(chunkX, chunkZ)));
if (cached != null) {
return cached;
}
KList<NativeStructureVolume> cached = originCache.get(
new RuntimeChunkKey(pinnedRuntimeId, chunkKey(chunkX, chunkZ)));
if (cached != null) {
return cached;
}
}
return originVolumes(engine, chunkX, chunkZ);
@@ -210,27 +209,21 @@ public final class NativeStructureVolumeIndex {
}
void evictRuntime(int runtimeId) {
synchronized (originCache) {
originBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
originCache.keySet().removeIf(key -> key.runtimeId() == runtimeId);
}
synchronized (queryCache) {
queryBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
queryCache.keySet().removeIf(key -> key.runtimeId() == runtimeId);
}
originBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
originCache.evictRuntime(runtimeId);
queryBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
queryCache.evictRuntime(runtimeId);
}
private KList<NativeStructureVolume> cached(
Map<RuntimeChunkKey, KList<NativeStructureVolume>> cache,
VolumeCache volumes,
ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> builds,
RuntimeChunkKey key,
Supplier<KList<NativeStructureVolume>> loader
) {
synchronized (cache) {
KList<NativeStructureVolume> hit = cache.get(key);
if (hit != null) {
return hit;
}
KList<NativeStructureVolume> hit = volumes.get(key);
if (hit != null) {
return hit;
}
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
// previous builder cached could claim the build slot after it retired and rebuild.
synchronized (cache) {
KList<NativeStructureVolume> published = cache.get(key);
if (published != null) {
future.complete(published);
builds.remove(key, future);
return published;
}
KList<NativeStructureVolume> published = volumes.get(key);
if (published != null) {
future.complete(published);
builds.remove(key, future);
return published;
}
try {
KList<NativeStructureVolume> built = loader.get();
synchronized (cache) {
if (builds.get(key) == future) {
cache.put(key, built);
}
}
volumes.publish(key, built, () -> builds.get(key) == future);
future.complete(built);
return built;
} catch (RuntimeException | Error error) {
@@ -303,15 +290,45 @@ public final class NativeStructureVolumeIndex {
}
}
private static Map<RuntimeChunkKey, KList<NativeStructureVolume>> lru(int capacity) {
return new LinkedHashMap<>(16, 0.75F, true) {
@Override
protected boolean removeEldestEntry(
Map.Entry<RuntimeChunkKey, KList<NativeStructureVolume>> eldest
) {
return size() > capacity;
private static final class VolumeCache {
private final int capacity;
private final ConcurrentHashMap<RuntimeChunkKey, KList<NativeStructureVolume>> entries = new ConcurrentHashMap<>();
private final ConcurrentLinkedQueue<RuntimeChunkKey> insertionOrder = new ConcurrentLinkedQueue<>();
private VolumeCache(int 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) {
@@ -99,6 +99,7 @@ public final class GenerationSemanticIndex {
private volatile IOException journalFailure;
private long appendingRegionKey;
private RegionShard appendingBase;
private boolean journalStorageReady;
private GenerationSemanticIndex(
Path dimensionRoot,
@@ -264,12 +265,13 @@ public final class GenerationSemanticIndex {
if (updates.isEmpty()) {
return;
}
ensureStorageDirectory();
ensureJournalStorage();
boolean existingJournal = journalEntries.containsKey(regionKey);
appendingRegionKey = regionKey;
appendingBase = baseRegion;
publicationLock.unlock();
try {
SemanticJournal.append(directory, regionX, regionZ, updates, unpublished, durability);
SemanticJournal.append(directory, regionX, regionZ, updates, unpublished, durability, existingJournal);
} catch (JournalAppendFailure failure) {
journalFailure = failure;
throw failure;
@@ -1193,6 +1195,14 @@ public final class GenerationSemanticIndex {
return true;
}
private void ensureJournalStorage() throws IOException {
if (journalStorageReady) {
return;
}
ensureStorageDirectory();
journalStorageReady = true;
}
private void ensureStorageDirectory() throws IOException {
validateDimensionRoot();
Path irisDirectory = ensureChildDirectory(dimensionRoot, "iris");
@@ -1958,18 +1968,17 @@ public final class GenerationSemanticIndex {
int regionZ,
List<ChunkGenerationSemantics> claims,
boolean unpublished,
DeferredDurability durability
DeferredDurability durability,
boolean existing
) throws IOException {
Path file = directory.resolve(journalFileName(regionX, regionZ));
boolean created = !Files.exists(file, LinkOption.NOFOLLOW_LINKS);
if (!created && !Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS)) {
boolean created = !existing && !Files.exists(file, LinkOption.NOFOLLOW_LINKS);
if (!existing && !created && !Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS)) {
throw invalid(file, "path is not a regular file");
}
FileChannel opened = FileChannel.open(
file,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE
);
FileChannel opened = existing
? FileChannel.open(file, StandardOpenOption.WRITE)
: FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
boolean uncertain = created;
boolean restored = false;
try (FileChannel channel = opened) {
@@ -6,11 +6,16 @@ import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.GZIPOutputStream;
@@ -18,6 +23,8 @@ public final class NativeTerrainReceipt {
private static final int MAGIC = 0x4952544E;
private static final int VERSION = 1;
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() {
}
@@ -25,7 +32,9 @@ public final class NativeTerrainReceipt {
public static byte[] encode(SavedTerrainChunk chunk, long activationId, String epochId) throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
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(VERSION);
output.writeInt(chunk.chunkX());
@@ -196,6 +205,60 @@ public final class NativeTerrainReceipt {
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 int MAXIMUM_ENTRIES = 256;
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();
Map<Integer, List<SavedBiomeChunk.Span>> vertical = new HashMap<>(exactVolume ? 256 : 16);
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 localZ = 0; localZ < 16; localZ++) {
int blockX = startX + localX;
@@ -82,7 +83,8 @@ public final class SavedBiomeCapture {
for (int index = 0; index < column.biomes().length; index++) {
int worldY = minimumY + index * column.step();
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));
}
if (transition != null) {
@@ -139,9 +141,10 @@ public final class SavedBiomeCapture {
Objects.requireNonNull(region, "region").getLoadKey());
}
private record Column(Engine engine, int minimumY, int height, int step, IrisBiome[] biomes, IrisRegion[] regions) {
private Column(Engine engine, int minimumY, int height, int step) {
this(engine, minimumY, height, step, new IrisBiome[(height + step - 1) / step],
private record Column(Engine engine, int minimumY, int height, int step, boolean subterrain, IrisBiome[] biomes,
IrisRegion[] regions) {
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]);
}
}
@@ -49,6 +49,7 @@ public final class SavedBiomeStore {
private static final int MAXIMUM_CACHED_CHUNKS = 128;
private static final long MAXIMUM_CACHED_BYTES = 64L * 1024L * 1024L;
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 directory;
@@ -464,12 +465,16 @@ public final class SavedBiomeStore {
}
private void ensureRegionFile(RegionIndex region) throws IOException {
if (region.fileReady) {
return;
}
requireSafeAncestors();
Files.createDirectories(directory);
if (Files.exists(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);
}
region.fileReady = true;
return;
}
ByteBuffer header = ByteBuffer.allocate(HEADER_BYTES);
@@ -486,6 +491,7 @@ public final class SavedBiomeStore {
}
Files.move(temporary, region.path, StandardCopyOption.ATOMIC_MOVE);
forceDirectory(directory);
region.fileReady = true;
} finally {
Files.deleteIfExists(temporary);
}
@@ -674,11 +680,10 @@ public final class SavedBiomeStore {
output.writeInt(chunk.chunkX());
output.writeInt(chunk.chunkZ());
output.writeInt(raw.size());
Deflater deflater = new Deflater(Deflater.BEST_SPEED);
Deflater deflater = DEFLATERS.get();
deflater.reset();
try (DeflaterOutputStream compressed = new DeflaterOutputStream(output, deflater)) {
raw.writeTo(compressed);
} finally {
deflater.end();
}
if (encoded.size() > MAXIMUM_RECORD_BYTES) {
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 long[] offsets = new long[1024];
private final int[] lengths = new int[1024];
private boolean fileReady;
private RegionIndex(Path path, int regionX, int regionZ) {
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.set(policy, engine);
DimensionStackLayout stack = mock(DimensionStackLayout.class);
Class<?> chunkResolution = Class.forName(BukkitBiomePolicy.class.getName() + "$ChunkResolution");
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);
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(host, field(resolved, "dimension"));
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
public void saturatedStringCachesPreserveModifiedUtfBytes() throws Exception {
for (int padding : new int[]{0, 300}) {
@@ -60,6 +60,16 @@ public class SavedBiomeStoreWriteTest {
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
public void relaxedDurabilityPreservesRecordsWithoutForcingEachAppend() throws Exception {
String previousMode = System.getProperty(Durability.MODE_PROPERTY);
@@ -379,6 +389,10 @@ public class SavedBiomeStoreWriteTest {
}
private static byte[] expectedRegion() throws IOException {
return expectedRegion(1);
}
private static byte[] expectedRegion(int records) throws IOException {
ByteArrayOutputStream raw = new ByteArrayOutputStream();
DataOutputStream data = new DataOutputStream(raw);
data.writeLong(3L);
@@ -392,17 +406,6 @@ public class SavedBiomeStoreWriteTest {
data.writeInt(1);
data.write(new byte[]{0, 0, 1, (byte) 0x80, 3, 0});
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();
DataOutputStream output = new DataOutputStream(region);
output.writeInt(0x4942494F);
@@ -410,9 +413,22 @@ public class SavedBiomeStoreWriteTest {
output.writeInt(0);
output.writeInt(0);
output.writeInt(checksum(region.toByteArray()));
output.writeInt(body.size());
output.write(body.toByteArray());
output.writeInt(checksum(body.toByteArray()));
for (int x = 0; x < records; x++) {
ByteArrayOutputStream body = new ByteArrayOutputStream();
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();
}