Batch history biome reads

- saved biome capture resolves each quart column through one engine call
- column-invariant height, region, surface and cave fallback resolved once
- cavern reads use one pinned mantle chunk per column
- saved biome cells interned per capture
- engine getters borrow the bound route runtime for other chunks instead of opening a stage
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-28 14:04:36 -04:00
1 parent c00dfbddce
commit adbcba3efc
8 files changed
+471 -65

No files matched your search

@@ -0,0 +1,180 @@
package art.arcane.iris.generation.runtime;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.block.B;
import art.arcane.iris.generation.mantle.EngineMantle;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.generation.terrain.IrisDimensionCarvingEntry;
import art.arcane.iris.generation.terrain.IrisRegion;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.pack.loading.ResourceLoader;
import art.arcane.iris.pack.value.IrisRange;
import art.arcane.iris.world.IrisWorld;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterCavern;
import art.arcane.volmlib.util.stream.ProceduralStream;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.util.List;
import java.util.Random;
import static org.junit.Assert.assertSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
public class EngineBiomeColumnTest {
private static final int SAMPLES = 96;
@BeforeClass
public static void initializeMantleBlockState() throws Exception {
NativeBlockState air = mock(NativeBlockState.class);
try (MockedStatic<B> blocks = mockStatic(B.class)) {
blocks.when(() -> B.getState("AIR")).thenReturn(air);
Class.forName(EngineMantle.class.getName());
}
}
@Test
public void columnMatchesPointLookupsAcrossCavesSurfacesAndMantleBiomes() {
Random random = new Random(1337L);
for (int trial = 0; trial < 48; trial++) {
Engine engine = randomEngine(random, trial);
for (int column = 0; column < 24; column++) {
int x = random.nextInt(4096) - 2048;
int z = random.nextInt(4096) - 2048;
IrisBiome[] biomes = new IrisBiome[SAMPLES];
IrisRegion[] regions = new IrisRegion[SAMPLES];
engine.getBiomeOrMantleColumn(x, z, 4, biomes, regions);
for (int index = 0; index < SAMPLES; index++) {
assertSame(engine.getBiomeOrMantle(x, index * 4, z), biomes[index]);
assertSame(engine.getRegion(x, index * 4, z), regions[index]);
}
}
}
}
@SuppressWarnings("unchecked")
private static Engine randomEngine(Random random, int trial) {
long salt = random.nextLong();
IrisBiome[] surfaces = {biome("iris:plains", 0), biome("iris:desert", 0), biome("iris:forest", 0)};
IrisBiome[] caves = {biome("iris:lush_caves", 0), biome("iris:deep_dark", 24), new IrisBiome(), null};
IrisRegion[] regions = {region("iris:temperate"), region("iris:arid")};
IrisBiome custom = biome("iris:custom_cave", 0);
IrisBiome configured = biome("iris:configured_cave", 0);
ProceduralStream<IrisBiome> surfaceStream = mock(ProceduralStream.class);
when(surfaceStream.get(anyDouble(), anyDouble())).thenAnswer(call ->
surfaces[pick(salt, call.getArgument(0), call.getArgument(1), 1, surfaces.length)]);
ProceduralStream<IrisBiome> caveStream = mock(ProceduralStream.class);
when(caveStream.get(anyDouble(), anyDouble())).thenAnswer(call ->
caves[pick(salt, call.getArgument(0), call.getArgument(1), 2, caves.length)]);
ProceduralStream<IrisRegion> regionStream = mock(ProceduralStream.class);
when(regionStream.get(anyDouble(), anyDouble())).thenAnswer(call ->
regions[pick(salt, call.getArgument(0), call.getArgument(1), 3, regions.length)]);
ProceduralStream<Double> heightStream = mock(ProceduralStream.class);
when(heightStream.get(anyDouble(), anyDouble())).thenAnswer(call ->
(double) (40 + pick(salt, call.getArgument(0), call.getArgument(1), 4, 300)));
IrisComplex complex = mock(IrisComplex.class);
when(complex.getTrueBiomeStream()).thenReturn(surfaceStream);
when(complex.getCaveBiomeStream()).thenReturn(caveStream);
when(complex.getRegionStream()).thenReturn(regionStream);
when(complex.getHeightStream()).thenReturn(heightStream);
when(complex.isTerrain3DSurface(anyInt(), anyInt(), anyInt())).thenAnswer(call ->
pick(salt, (int) call.getArgument(0) * 31 + (int) call.getArgument(1), (int) call.getArgument(2), 5, 9) == 0);
Mantle<Matter> mantle = mock(Mantle.class);
when(mantle.getWorldHeight()).thenReturn(384);
when(mantle.getLoadedRegions()).thenReturn(new KMap<>());
when(mantle.hasTectonicPlate(anyInt(), anyInt())).thenReturn(true);
when(mantle.get(anyInt(), anyInt(), anyInt(), eq(MatterCavern.class))).thenAnswer(call ->
cavern(salt, call.getArgument(0), call.getArgument(1), call.getArgument(2)));
when(mantle.getChunk(anyInt(), anyInt())).thenAnswer(call -> {
int chunkX = call.getArgument(0);
int chunkZ = call.getArgument(1);
MantleChunk<Matter> chunk = mock(MantleChunk.class);
when(chunk.use()).thenReturn(chunk);
when(chunk.get(anyInt(), anyInt(), anyInt(), eq(MatterCavern.class))).thenAnswer(read -> cavern(salt,
chunkX * 16 + (int) read.getArgument(0), read.getArgument(1), chunkZ * 16 + (int) read.getArgument(2)));
return chunk;
});
EngineMantle engineMantle = mock(EngineMantle.class);
when(engineMantle.getMantle()).thenReturn(mantle);
doAnswer(call -> 20 + pick(salt, (int) call.getArgument(0), (int) call.getArgument(1), 7, 330))
.when(engineMantle).getHighest(anyInt(), anyInt(), any(IrisData.class), anyBoolean());
IrisData data = mock(IrisData.class);
ResourceLoader<IrisBiome> loader = mock(ResourceLoader.class);
when(data.getBiomeLoader()).thenReturn(loader);
when(loader.load(anyString())).thenReturn(null);
when(loader.load("iris:custom_cave")).thenReturn(custom);
when(loader.load("iris:configured_cave")).thenReturn(configured);
IrisDimension dimension = new IrisDimension();
if (trial % 2 == 1) {
IrisDimensionCarvingEntry root = new IrisDimensionCarvingEntry();
root.setBiome("iris:configured_cave");
int bottom = -64 + random.nextInt(120);
root.setWorldYRange(new IrisRange(bottom, bottom + 8 + random.nextInt(80)));
root.setChildRecursionDepth(0);
dimension.setCarving(new KList<>(List.of(root)));
}
EnginePlatformHooks hooks = mock(EnginePlatformHooks.class);
Engine engine = mock(Engine.class, CALLS_REAL_METHODS);
doReturn(complex).when(engine).getComplex();
doReturn(engineMantle).when(engine).getMantle();
doReturn(hooks).when(engine).getPlatformHooks();
doReturn(data).when(engine).getData();
doReturn(dimension).when(engine).getDimension();
doReturn(IrisWorld.builder().minHeight(-64).maxHeight(320).build()).when(engine).getWorld();
return engine;
}
private static MatterCavern cavern(long salt, int x, int y, int z) {
return switch (pick(salt, x * 17 + y, z, 6, 12)) {
case 0 -> new MatterCavern(true, "iris:custom_cave", (byte) 0);
case 1 -> new MatterCavern(true, "iris:missing_cave", (byte) 1);
case 2 -> new MatterCavern(true, "", (byte) 0);
default -> null;
};
}
private static int pick(long salt, double x, double z, int channel, int bound) {
long mixed = salt ^ ((long) x * 0x9E3779B97F4A7C15L) ^ ((long) z * 0xC2B2AE3D27D4EB4FL) ^ channel * 0x165667B19E3779F9L;
mixed ^= mixed >>> 33;
mixed *= 0xFF51AFD7ED558CCDL;
mixed ^= mixed >>> 33;
return (int) Math.floorMod(mixed, (long) bound);
}
private static IrisBiome biome(String key, int caveMinDepth) {
IrisBiome biome = new IrisBiome();
biome.setLoadKey(key);
biome.setCaveMinDepthBelowSurface(caveMinDepth);
return biome;
}
private static IrisRegion region(String key) {
IrisRegion region = new IrisRegion();
region.setLoadKey(key);
return region;
}
}
@@ -204,10 +204,10 @@ public class IrisEngineGenerationRuntimeScopeTest {
assertSame(cave, engine.getCaveBiome(19, 20, -3, new IrisDimensionCarvingResolver.State()));
assertSame(surface, engine.getSurfaceBiome(19, -3));
assertSame(selected.complex, engine.getComplex());
verify(router, never()).openCoordinateScope(anyInt(), anyInt());
verify(router, never()).openReadScope(anyInt(), anyInt());
when(context.isNaturalTerrain()).thenReturn(false);
assertSame(cave, engine.getCaveBiome(19, -3));
verify(router, times(1)).openCoordinateScope(19, -3);
verify(router, times(1)).openReadScope(19, -3);
}
assertSame(active.complex, engine.getComplex());
assertNull(IrisContext.get());
@@ -242,7 +242,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
}
}
engine.getCaveBiome(19, -3);
verify(router, times(3)).openCoordinateScope(19, -3);
verify(router, times(3)).openReadScope(19, -3);
}
@Test
@@ -274,7 +274,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
when(caves.get(19D, -3D)).thenReturn(cave);
when(surfaces.get(19D, -3D)).thenReturn(surface);
when(regions.get(19D, -3D)).thenReturn(region);
when(router.openCoordinateScope(19, -3)).thenAnswer(invocation -> {
when(router.openReadScope(19, -3)).thenAnswer(invocation -> {
assertSame(active.complex, engine.getComplex());
IrisEngine.GenerationRuntimeScope runtimeScope = engine.openGenerationRuntimeScope(binding);
GenerationHistoryRuntimeRouter.CoordinateScope coordinateScope = mock(GenerationHistoryRuntimeRouter.CoordinateScope.class);
@@ -292,7 +292,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
assertSame(region, engine.getRegion(19, 20, -3));
assertSame(active.complex, engine.getComplex());
}
verify(router, times(4)).openCoordinateScope(19, -3);
verify(router, times(4)).openReadScope(19, -3);
verifyNoInteractions(saved);
assertNull(IrisContext.get());
}
@@ -318,7 +318,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
}
verify(saved).resolveCaveBase(19, -3);
verify(saved, times(2)).resolve(19, -64, -3, true);
verify(router, never()).openCoordinateScope(anyInt(), anyInt());
verify(router, never()).openReadScope(anyInt(), anyInt());
assertNull(IrisContext.get());
}
@@ -839,7 +839,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
assertSame(environment.biome(), engine.getSurfaceBiome(19, -3));
assertSame(environment.region(), engine.getRegion(19, -3));
assertSame(environment.region(), engine.getRegion(19, 20, -3));
verify(router, never()).openCoordinateScope(anyInt(), anyInt());
verify(router, never()).openReadScope(anyInt(), anyInt());
}
@Test
@@ -855,7 +855,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
assertSame(failure, assertThrows(SavedBiomeUnavailableException.class,
() -> engine.getBiome(19, 20, -3)));
verify(router, never()).openCoordinateScope(anyInt(), anyInt());
verify(router, never()).openReadScope(anyInt(), anyInt());
}
@Test
@@ -16,6 +16,7 @@ import org.mockito.MockedStatic;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -350,6 +351,11 @@ public final class GenerationHistoryRuntimeRouterRoutingTest extends GenerationH
when(engine.getBiomeOrMantle(anyInt(), anyInt(), anyInt())).thenReturn(biome);
when(engine.getRegion(anyInt(), anyInt())).thenReturn(region);
when(engine.getRegion(anyInt(), anyInt(), anyInt())).thenReturn(region);
doAnswer(invocation -> {
Arrays.fill((IrisBiome[]) invocation.getArgument(3), biome);
Arrays.fill((IrisRegion[]) invocation.getArgument(4), region);
return null;
}).when(engine).getBiomeOrMantleColumn(anyInt(), anyInt(), anyInt(), any(), any());
EngineMantle engineMantle = mock(EngineMantle.class);
@SuppressWarnings("unchecked")
Mantle<Matter> mantle = mock(Mantle.class);
@@ -429,4 +435,44 @@ public final class GenerationHistoryRuntimeRouterRoutingTest extends GenerationH
verify(engine, times(2)).openGenerationRuntimeScope(first);
router.close();
}
@Test
public void readScopeBorrowsTheBoundRouteRuntimeForOtherChunks() throws Exception {
Path world = temporaryFolder.newFolder("router-read-world").toPath();
Path pack = createPack("router-read-pack", "alpha");
GenerationHistory history = createHistory(world, pack);
IrisEngine engine = mock(IrisEngine.class);
FakeRuntimeFactory runtimes = new FakeRuntimeFactory();
IrisEngine.GenerationRuntimeBinding first = runtimes.binding(history, history.activeActivation());
when(engine.getActiveGenerationRuntimeBinding()).thenReturn(first);
AtomicReference<IrisEngine.GenerationRuntimeBinding> scoped = installScopeTracking(engine);
when(engine.captureGenerationRuntimeBinding()).thenAnswer(invocation -> scoped.get());
GenerationHistoryRuntimeRouter router = GenerationHistoryRuntimeRouter.attach(
engine,
history,
(ignored, blockX, blockZ) -> signature(blockX, blockZ),
runtimes
);
try (GenerationHistoryRuntimeRouter.RuntimeRoute route = router.openRoute(-1, -2);
GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope ignored = route.openRuntimeScope()) {
try (GenerationHistoryRuntimeRouter.CoordinateScope same = router.openReadScope(-1, -17)) {
assertSame(route.activation(), same.activation());
}
try (GenerationHistoryRuntimeRouter.CoordinateScope neighbour = router.openReadScope(16, 0)) {
assertEquals(1, neighbour.chunkX());
assertEquals(0, neighbour.chunkZ());
assertFalse(neighbour.claimGeneratedSemantics());
assertSame(first, scoped.get());
}
verify(engine, times(1)).openGenerationRuntimeScope(first);
scoped.set(runtimes.binding(history, history.activeActivation()));
try (GenerationHistoryRuntimeRouter.CoordinateScope rebound = router.openReadScope(16, 0)) {
assertSame(route.activation(), rebound.activation());
}
verify(engine, times(2)).openGenerationRuntimeScope(first);
}
router.close();
}
}
@@ -11,9 +11,11 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -92,6 +94,7 @@ public class SavedBiomeCaptureTest {
when(engine.getSurfaceBiome(anyInt(), anyInt())).thenReturn(host);
when(engine.getCaveBiome(anyInt(), anyInt())).thenReturn(host);
when(engine.getBiomeOrMantle(anyInt(), anyInt(), anyInt())).thenReturn(host);
columnsFromPointLookups(engine);
FloatingBiomeOverlay floating = new FloatingBiomeOverlay(16);
FloatingBiomeOverlay.Identity childIdentity = new FloatingBiomeOverlay.Identity(child.getLoadKey(), region.getLoadKey());
floating.record(0, 8, 0, childIdentity);
@@ -135,6 +138,7 @@ public class SavedBiomeCaptureTest {
when(engine.getSurfaceBiome(anyInt(), anyInt())).thenAnswer(call -> (int) call.getArgument(0) == -15 ? right : left);
when(engine.getCaveBiome(anyInt(), anyInt())).thenReturn(cave);
when(engine.getBiomeOrMantle(anyInt(), anyInt(), anyInt())).thenAnswer(call -> (int) call.getArgument(1) < 8 ? lower : upper);
columnsFromPointLookups(engine);
SavedBiomeChunk chunk = SavedBiomeCapture.capture(engine, stage, historical, null);
@@ -167,9 +171,25 @@ public class SavedBiomeCaptureTest {
doCallRealMethod().when(engine).getBiomeOrMantle(anyInt(), anyInt(), anyInt());
when(engine.getCaveOrMantleBiome(anyInt(), anyInt(), anyInt()))
.thenAnswer(call -> engine.getCaveBiome(call.getArgument(0), call.getArgument(2)));
columnsFromPointLookups(engine);
return engine;
}
private static void columnsFromPointLookups(Engine engine) {
doAnswer(call -> {
int x = call.getArgument(0);
int z = call.getArgument(1);
int step = call.getArgument(2);
IrisBiome[] biomes = call.getArgument(3);
IrisRegion[] regions = call.getArgument(4);
for (int index = 0; index < biomes.length; index++) {
biomes[index] = engine.getBiomeOrMantle(x, index * step, z);
regions[index] = engine.getRegion(x, index * step, z);
}
return null;
}).when(engine).getBiomeOrMantleColumn(anyInt(), anyInt(), anyInt(), any(), any());
}
private static GenerationHistory.GenerationStage stage() {
GenerationHistory.GenerationStage stage = mock(GenerationHistory.GenerationStage.class);
when(stage.chunkX()).thenReturn(-1);