mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +00:00
Chunk-scoped cave lookups
- Subterrain cells for carve and decoration resolve through a per-chunk sampler with a column height hull instead of per-block engine dispatch - IrisEngine subterrain lookups read the thread frame and runtime once and memoize the dimension gate, removing the per-call disabled-component list scan - Unrouted workers resolve cave biomes through the captured carving snapshot and column cache with subterrain biomes checked first - Captured cave biome lookups bypass the per-voxel hash cache - Internal carve walls come from word-level column masks instead of hashed neighbor puts, painted after cheap solidity and surface checks - Floor and ceiling layers are memoized per column when the biome layers do not depend on the zone height - Zone scanning walks carved runs, the decoration scan memoizes block solidity and reuses the previous block, and waterlogging normalization skips snapshots without hydrology - Boundary walls read own cells straight from the snapshot; the unused snapshot face builder and face accessors are removed
This commit is contained in:
1 parent
af90765a92
commit
0bdb022f56
16 files changed
+790
-197
No files matched your search
@@ -57,10 +57,20 @@ public class CaveTerrainSnapshotTest {
|
||||
for (TerrainMatterView.Face face : TerrainMatterView.Face.values()) {
|
||||
for (int height : new int[]{0, 1, 37, SECTIONS * 16, SECTIONS * 16 + 9}) {
|
||||
MatterCavern[] expected = TerrainMatterView.getComposedFace(chunk, face, height);
|
||||
MatterCavern[] actual = snapshot.composedFace(face, height);
|
||||
assertEquals(expected.length, actual.length);
|
||||
assertEquals(height * 16, expected.length);
|
||||
for (int index = 0; index < expected.length; index++) {
|
||||
assertSame(face + " " + index, expected[index], actual[index]);
|
||||
int offset = index & 15;
|
||||
int x = switch (face) {
|
||||
case WEST -> 0;
|
||||
case EAST -> 15;
|
||||
case NORTH, SOUTH -> offset;
|
||||
};
|
||||
int z = switch (face) {
|
||||
case NORTH -> 0;
|
||||
case SOUTH -> 15;
|
||||
case WEST, EAST -> offset;
|
||||
};
|
||||
assertSame(face + " " + index, expected[index], snapshot.composedCavern(x, index >> 4, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+36
@@ -9,6 +9,7 @@ import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.EnginePlatformHooks;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.generation.runtime.IrisEngine;
|
||||
import art.arcane.iris.generation.runtime.SubterrainChunkCells;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.generation.terrain.IrisDimensionCarvingEntry;
|
||||
import art.arcane.iris.generation.terrain.IrisDimensionCarvingResolver;
|
||||
@@ -148,6 +149,41 @@ public class IrisCarveModifierCaveInputsTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unroutedWorkerCapturesColumnsLikeTheEngineIncludingSubterrainBiomes() {
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
when(fixture.engine.hasGenerationRuntimeScope()).thenReturn(false);
|
||||
when(fixture.engine.hasGenerationHistoryRouting()).thenReturn(false);
|
||||
doReturn(SubterrainChunkCells.delegated(fixture.engine)).when(fixture.engine).subterrainChunkCells(anyInt(), anyInt());
|
||||
IrisBiome feature = biome("feature", 0);
|
||||
doReturn(feature).when(fixture.engine).getSubterrainBiome(-20, 33, 18);
|
||||
doReturn(feature).when(fixture.scalar).getSubterrainBiome(-20, 33, 18);
|
||||
IrisCarveModifier.CaveInputs inputs = IrisCarveModifier.CaveInputs.forContentChunk(fixture.engine, -2, 1);
|
||||
IrisDimensionCarvingResolver.State state = new IrisDimensionCarvingResolver.State();
|
||||
for (int x = -35; x <= -13; x++) {
|
||||
for (int z = 13; z <= 35; z += 2) {
|
||||
for (int y : new int[]{0, 30, 33, 34, 39, 40}) {
|
||||
assertSame(x + "," + y + "," + z, fixture.scalar.getCaveBiome(x, y, z, state), inputs.resolve(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
assertSame(feature, inputs.resolve(-20, 33, 18));
|
||||
assertTrue(fixture.samples.get() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void routedOrBoundUnscopedEnginesKeepCoordinateDispatch() {
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
when(fixture.engine.hasGenerationRuntimeScope()).thenReturn(false);
|
||||
when(fixture.engine.hasGenerationHistoryRouting()).thenReturn(true);
|
||||
IrisBiome historical = biome("historical", 0);
|
||||
when(fixture.engine.getCaveBiome(anyInt(), anyInt(), anyInt(), any(IrisDimensionCarvingResolver.State.class))).thenReturn(historical);
|
||||
assertSame(historical, IrisCarveModifier.CaveInputs.forTerrainChunk(fixture.engine, -2, 1).resolve(-20, 20, 18));
|
||||
assertEquals(0, fixture.samples.get());
|
||||
}
|
||||
}
|
||||
|
||||
private static IrisBiome biome(String key, int depth) {
|
||||
IrisBiome biome = new IrisBiome();
|
||||
biome.setLoadKey(key);
|
||||
|
||||
+4
-2
@@ -67,7 +67,8 @@ public class IrisCarveModifierInferenceIsolationTest {
|
||||
int.class,
|
||||
IrisCarveModifier.CaveInputs.class,
|
||||
Long2ObjectOpenHashMap.class,
|
||||
Map.class
|
||||
Map.class,
|
||||
CaveLayerMemo.class
|
||||
);
|
||||
paintBoundaryZone.setAccessible(true);
|
||||
paintBoundaryZone.invoke(
|
||||
@@ -83,7 +84,8 @@ public class IrisCarveModifierInferenceIsolationTest {
|
||||
1,
|
||||
new IrisCarveModifier.CaveInputs(engine),
|
||||
new Long2ObjectOpenHashMap<IrisBiome>(),
|
||||
customBiomes
|
||||
customBiomes,
|
||||
new CaveLayerMemo()
|
||||
);
|
||||
|
||||
IrisBiome fallback = new IrisBiome();
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package art.arcane.iris.generation.stage;
|
||||
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.biome.IrisBiomePaletteLayer;
|
||||
import art.arcane.iris.generation.biome.IrisSurfaceLayerFallback;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.generation.terrain.IrisSlopeClip;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.noise.CNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
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.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisCarveModifierLayerMemoTest {
|
||||
private static final Map<Integer, NativeBlockState> BLOCKS = new HashMap<>();
|
||||
|
||||
@Test
|
||||
public void heightFreeFloorLayersIgnoreTheZoneFloor() {
|
||||
IrisData data = mock(IrisData.class);
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
when(complex.hasTerrain3D()).thenReturn(true);
|
||||
when(complex.terrainSurfaceSlope(anyInt(), anyInt(), anyInt())).thenAnswer(call -> (double) (int) call.getArgument(1));
|
||||
RNG rng = new RNG(31L);
|
||||
IrisBiomePaletteLayer top = layer(data, 2, new IrisSlopeClip());
|
||||
IrisBiomePaletteLayer under = layer(data, 3, new IrisSlopeClip());
|
||||
IrisBiome biome = new IrisBiome().setLayers(new KList<IrisBiomePaletteLayer>().qadd(top).qadd(under));
|
||||
IrisDimension dimension = new IrisDimension().setSurfaceLayerFallback(IrisSurfaceLayerFallback.TOP_LAYER);
|
||||
|
||||
assertTrue(IrisCarveModifier.hasHeightFreeFloorLayers(biome));
|
||||
KList<NativeBlockState> reference = biome.generateLayers(dimension, 14D, -9D, rng, 3, 0, data, complex);
|
||||
for (int floor = -64; floor < 320; floor += 7) {
|
||||
assertEquals(reference, biome.generateLayers(dimension, 14D, -9D, rng, 3, floor, data, complex));
|
||||
}
|
||||
|
||||
biome.setLockLayers(true);
|
||||
assertFalse(IrisCarveModifier.hasHeightFreeFloorLayers(biome));
|
||||
biome.setLockLayers(false);
|
||||
biome.setLayers(new KList<IrisBiomePaletteLayer>().qadd(top).qadd(layer(data, 1, new IrisSlopeClip(0D, 2D))));
|
||||
assertFalse(IrisCarveModifier.hasHeightFreeFloorLayers(biome));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void memoAnswersOnlyTheBiomesOfTheCurrentColumn() {
|
||||
CaveLayerMemo memo = new CaveLayerMemo();
|
||||
IrisBiome first = new IrisBiome();
|
||||
IrisBiome second = new IrisBiome();
|
||||
KList<NativeBlockState> floor = new KList<>();
|
||||
KList<NativeBlockState> ceiling = new KList<>();
|
||||
memo.rememberFloor(first, floor);
|
||||
memo.rememberCeiling(second, ceiling);
|
||||
|
||||
assertSame(floor, memo.floor(first));
|
||||
assertNull(memo.floor(second));
|
||||
assertSame(ceiling, memo.ceiling(second));
|
||||
assertNull(memo.ceiling(first));
|
||||
memo.reset();
|
||||
assertNull(memo.floor(first));
|
||||
assertNull(memo.ceiling(second));
|
||||
}
|
||||
|
||||
private static IrisBiomePaletteLayer layer(IrisData data, int thickness, IrisSlopeClip slope) {
|
||||
IrisBiomePaletteLayer layer = mock(IrisBiomePaletteLayer.class);
|
||||
CNG height = mock(CNG.class);
|
||||
when(layer.getZoom()).thenReturn(1D);
|
||||
when(layer.getMinHeight()).thenReturn(thickness);
|
||||
when(layer.getMaxHeight()).thenReturn(thickness);
|
||||
when(layer.getSlopeCondition()).thenReturn(slope);
|
||||
when(layer.getHeightGenerator(any(RNG.class), same(data))).thenReturn(height);
|
||||
when(height.fit(anyInt(), anyInt(), anyDouble(), anyDouble())).thenReturn(thickness);
|
||||
when(layer.get(any(RNG.class), anyInt(), anyDouble(), anyDouble(), anyDouble(), same(data)))
|
||||
.thenAnswer(call -> block((int) call.getArgument(1)));
|
||||
return layer;
|
||||
}
|
||||
|
||||
private static NativeBlockState block(int signature) {
|
||||
return BLOCKS.computeIfAbsent(signature, key -> mock(NativeBlockState.class));
|
||||
}
|
||||
}
|
||||
+7
-31
@@ -112,39 +112,15 @@ public class IrisCarveModifierZoneParityTest {
|
||||
|
||||
private List<String> bitsetZones(CarveColumnMask columnMask, int maxHeight) {
|
||||
List<String> zones = new ArrayList<>();
|
||||
int firstHeight = columnMask.nextSetBit(0);
|
||||
if (firstHeight < 0) {
|
||||
return zones;
|
||||
}
|
||||
|
||||
int floor = firstHeight;
|
||||
int ceiling = -1;
|
||||
int buf = firstHeight - 1;
|
||||
int y = firstHeight;
|
||||
while (y >= 0) {
|
||||
if (y >= 0 && y <= maxHeight) {
|
||||
if (y == buf + 1) {
|
||||
buf = y;
|
||||
ceiling = buf;
|
||||
} else if (isValidZone(floor, ceiling, maxHeight)) {
|
||||
zones.add(zoneKey(floor, ceiling));
|
||||
floor = y;
|
||||
ceiling = -1;
|
||||
buf = y;
|
||||
} else {
|
||||
floor = y;
|
||||
ceiling = -1;
|
||||
buf = y;
|
||||
}
|
||||
int floor = columnMask.nextSetBit(0);
|
||||
while (floor >= 0 && floor <= maxHeight) {
|
||||
int end = columnMask.nextClearBit(floor) - 1;
|
||||
int ceiling = Math.min(end, maxHeight);
|
||||
if (isValidZone(floor, ceiling, maxHeight)) {
|
||||
zones.add(zoneKey(floor, ceiling));
|
||||
}
|
||||
|
||||
y = columnMask.nextSetBit(y + 1);
|
||||
floor = columnMask.nextSetBit(end + 1);
|
||||
}
|
||||
|
||||
if (isValidZone(floor, ceiling, maxHeight)) {
|
||||
zones.add(zoneKey(floor, ceiling));
|
||||
}
|
||||
|
||||
return zones;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,10 @@ import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -65,6 +68,54 @@ public class IrisCarveScratchTest {
|
||||
assertEquals(0, wallCount[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maskWallsMatchTheNeighborScanOfCarvedCells() {
|
||||
Random random = new Random(52_117L);
|
||||
for (int round = 0; round < 40; round++) {
|
||||
CarveColumnMask[] columns = new CarveColumnMask[256];
|
||||
CarveColumnMask[] walls = new CarveColumnMask[256];
|
||||
for (int index = 0; index < 256; index++) {
|
||||
columns[index] = new CarveColumnMask();
|
||||
walls[index] = new CarveColumnMask();
|
||||
walls[index].add(random.nextInt(700));
|
||||
}
|
||||
int cells = random.nextInt(4000);
|
||||
int height = 64 + random.nextInt(700);
|
||||
for (int cell = 0; cell < cells; cell++) {
|
||||
columns[random.nextInt(256)].add(1 + random.nextInt(height - 1));
|
||||
}
|
||||
Set<String> expected = new HashSet<>();
|
||||
for (int index = 0; index < 256; index++) {
|
||||
int rx = index >> 4;
|
||||
int rz = index & 15;
|
||||
for (int y = columns[index].nextSetBit(0); y >= 0; y = columns[index].nextSetBit(y + 1)) {
|
||||
if (rz < 15 && !columns[index + 1].contains(y)) {
|
||||
expected.add(key(rx, y, rz + 1));
|
||||
}
|
||||
if (rx < 15 && !columns[index + 16].contains(y)) {
|
||||
expected.add(key(rx + 1, y, rz));
|
||||
}
|
||||
if (rz > 0 && !columns[index - 1].contains(y)) {
|
||||
expected.add(key(rx, y, rz - 1));
|
||||
}
|
||||
if (rx > 0 && !columns[index - 16].contains(y)) {
|
||||
expected.add(key(rx - 1, y, rz));
|
||||
}
|
||||
}
|
||||
}
|
||||
IrisCarveModifier.collectInternalWalls(walls, columns);
|
||||
Set<String> actual = new HashSet<>();
|
||||
for (int index = 0; index < 256; index++) {
|
||||
for (int y = walls[index].nextSetBit(0); y >= 0; y = walls[index].nextSetBit(y + 1)) {
|
||||
actual.add(key(index >> 4, y, index & 15));
|
||||
assertFalse(columns[index].contains(y));
|
||||
}
|
||||
assertEquals(walls[index].nextSetBit(0) < 0, walls[index].isEmpty());
|
||||
}
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
private static String key(int x, int y, int z) {
|
||||
return x + ":" + y + ":" + z;
|
||||
}
|
||||
|
||||
@@ -376,6 +376,7 @@ public class IrisNaturalCaveScanTest {
|
||||
chunk = mock(MantleChunk.class);
|
||||
doReturn(engine).when(modifier).getEngine();
|
||||
doReturn(complex).when(modifier).getComplex();
|
||||
doReturn(complex).when(engine).getComplex();
|
||||
doReturn(32).when(engine).getHeight();
|
||||
doReturn(new SeedManager(1337L)).when(engine).getSeedManager();
|
||||
doReturn(engineMantle).when(engine).getMantle();
|
||||
|
||||
@@ -638,6 +638,48 @@ public class SubterrainPlannerTest {
|
||||
assertTrue(original.sample(anchor.x(), anchor.y(), anchor.z()).occupied());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunkSamplersMatchCoordinateSamplingInsideAndAroundTheirChunk() {
|
||||
for (IrisSubterrainFamily family : IrisSubterrainFamily.values()) {
|
||||
int length = family == IrisSubterrainFamily.TECTONIC_FAULT ? 208 : 64;
|
||||
SubterrainPlanner planner = planner(compactFeature(family).setLength(length).setProbability(0.6));
|
||||
SubterrainPlanner reference = planner(compactFeature(family).setLength(length).setProbability(0.6));
|
||||
SubterrainPlan plan = first(planner);
|
||||
int centerChunkX = plan.centerX() >> 4;
|
||||
int centerChunkZ = plan.centerZ() >> 4;
|
||||
int occupied = 0;
|
||||
int emptyChunks = 0;
|
||||
for (int chunkX = centerChunkX - 3; chunkX <= centerChunkX + 3; chunkX += 2) {
|
||||
for (int chunkZ = centerChunkZ - 3; chunkZ <= centerChunkZ + 3; chunkZ += 2) {
|
||||
SubterrainPlanner.ChunkSampler sampler = planner.chunkSampler(chunkX, chunkZ);
|
||||
List<SubterrainPlan> chunkPlans = reference.plansForBounds((chunkX << 4) - 1, (chunkZ << 4) - 1,
|
||||
(chunkX << 4) + 16, (chunkZ << 4) + 16);
|
||||
if (sampler.isEmpty()) {
|
||||
emptyChunks++;
|
||||
}
|
||||
for (int x = (chunkX << 4) - 3; x < (chunkX << 4) + 19; x++) {
|
||||
for (int z = (chunkZ << 4) - 3; z < (chunkZ << 4) + 19; z++) {
|
||||
for (int y = -66; y <= 99; y++) {
|
||||
SubterrainCell expected = reference.sample(x, y, z);
|
||||
assertEquals(family + " " + x + "," + y + "," + z, expected, sampler.sample(x, y, z));
|
||||
assertEquals(family + " candidates " + x + "," + y + "," + z,
|
||||
reference.sample(chunkPlans, x, y, z), sampler.sampleCandidates(x, y, z));
|
||||
if (expected.occupied()) {
|
||||
occupied++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assertTrue(family + " occupied", occupied > 100);
|
||||
assertTrue(family + " empty chunks", emptyChunks < 16);
|
||||
}
|
||||
SubterrainPlanner empty = new SubterrainPlanner(new SubterrainPlanner.Options(List.of(), 7123, -64, 97, (x, z) -> "test-region"));
|
||||
assertTrue(empty.chunkSampler(3, -5).isEmpty());
|
||||
assertEquals(SubterrainCell.OUTSIDE, empty.chunkSampler(3, -5).sample(50, 10, -70));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void duplicateIdentifiersAreRejected() {
|
||||
IrisSubterrainFeature feature = feature(IrisSubterrainFamily.CENOTE);
|
||||
|
||||
Reference in new issue
Block a user