mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
Snapshot cave terrain per chunk
- sCave copies the chunk's cavern and hydrology terrain view once (journal originals win) instead of per-voxel monitor, journal and slice lookups - hydrology lookups answer null without work in chunks with no cave cells - decorateNaturalCaves reuses the sCave snapshot through the chunk context
This commit is contained in:
1 parent
08353e3bdd
commit
184a04d97f
10 files changed
+542
-137
No files matched your search
@@ -0,0 +1,175 @@
|
||||
package art.arcane.iris.generation.mantle;
|
||||
|
||||
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveAction;
|
||||
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveCell;
|
||||
import art.arcane.iris.world.storage.matter.IrisMatterSupport;
|
||||
import art.arcane.iris.world.storage.matter.PreObjectMatterCell;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.matter.IrisMatter;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
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.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class CaveTerrainSnapshotTest {
|
||||
private static final int SECTIONS = 5;
|
||||
|
||||
@BeforeClass
|
||||
public static void registerMatter() {
|
||||
IrisMatterSupport.ensureRegistered();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void snapshotMatchesTerrainViewForEveryCellFaceAndResolverInput() {
|
||||
for (long seed = 1; seed <= 2; seed++) {
|
||||
MantleChunk<Matter> chunk = randomChunk(new Random(seed), seed % 2 == 0);
|
||||
CaveTerrainSnapshot snapshot = CaveTerrainSnapshot.capture(chunk, 3, -4);
|
||||
assertTrue(snapshot.covers(3, -4));
|
||||
assertFalse(snapshot.covers(3, 4));
|
||||
boolean anyHydrology = false;
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int y = -2; y < SECTIONS * 16 + 3; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
String cell = "seed=" + seed + " " + x + "," + y + "," + z;
|
||||
assertSame(cell, TerrainMatterView.get(chunk, x, y, z, MatterCavern.class), snapshot.cavern(x, y, z));
|
||||
HydrologyCaveCell hydrology = TerrainMatterView.get(chunk, x, y, z, HydrologyCaveCell.class);
|
||||
anyHydrology |= hydrology != null;
|
||||
assertSame(cell, hydrology, snapshot.hydrology(x, y, z));
|
||||
assertSame(cell, TerrainMatterView.getComposedCavern(chunk, x, y, z), snapshot.composedCavern(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(anyHydrology, snapshot.hasHydrology());
|
||||
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);
|
||||
for (int index = 0; index < expected.length; index++) {
|
||||
assertSame(face + " " + index, expected[index], actual[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(legacyResolverInputs(chunk), snapshotResolverInputs(snapshot));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunksWithoutHydrologyAnswerNullWithoutCells() {
|
||||
Matter[] sections = new Matter[SECTIONS];
|
||||
sections[1] = new IrisMatter(16, 16, 16);
|
||||
MatterCavern cavern = new MatterCavern(true, "", (byte) 0);
|
||||
sections[1].slice(MatterCavern.class).set(4, 5, 6, cavern);
|
||||
sections[1].slice(PreObjectMatterCell.class).set(4, 6, 6, PreObjectMatterCell.hydrology(null));
|
||||
CaveTerrainSnapshot snapshot = CaveTerrainSnapshot.capture(chunk(sections), 0, 0);
|
||||
assertFalse(snapshot.hasHydrology());
|
||||
assertNull(snapshot.hydrology(4, 22, 6));
|
||||
assertSame(cavern, snapshot.composedCavern(4, 21, 6));
|
||||
assertNull(snapshot.cavern(4, 22, 6));
|
||||
}
|
||||
|
||||
private static Map<String, String> legacyResolverInputs(MantleChunk<Matter> chunk) {
|
||||
Map<String, String> inputs = new HashMap<>();
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int y = 0; y < SECTIONS * 16; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
MatterCavern cavern = TerrainMatterView.get(chunk, x, y, z, MatterCavern.class);
|
||||
HydrologyCaveCell hydrology = TerrainMatterView.get(chunk, x, y, z, HydrologyCaveCell.class);
|
||||
if (cavern != null || hydrology != null) {
|
||||
inputs.put(x + ":" + y + ":" + z, identity(cavern) + "/" + identity(hydrology));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return inputs;
|
||||
}
|
||||
|
||||
private static Map<String, String> snapshotResolverInputs(CaveTerrainSnapshot snapshot) {
|
||||
Map<String, String> inputs = new HashMap<>();
|
||||
snapshot.forEachCell((x, y, z, cavern, hydrology) -> assertNull(inputs.put(x + ":" + y + ":" + z,
|
||||
identity(cavern) + "/" + identity(hydrology))));
|
||||
return inputs;
|
||||
}
|
||||
|
||||
private static String identity(Object value) {
|
||||
return value == null ? "null" : Integer.toHexString(System.identityHashCode(value));
|
||||
}
|
||||
|
||||
private static MantleChunk<Matter> randomChunk(Random random, boolean manyCaverns) {
|
||||
MatterCavern[] caverns = new MatterCavern[manyCaverns ? 40 : 4];
|
||||
for (int index = 0; index < caverns.length; index++) {
|
||||
caverns[index] = new MatterCavern(index % 3 != 0, "biome-" + index, (byte) (index % 4));
|
||||
}
|
||||
HydrologyCaveCell[] hydrology = new HydrologyCaveCell[HydrologyCaveAction.values().length];
|
||||
for (int index = 0; index < hydrology.length; index++) {
|
||||
hydrology[index] = new HydrologyCaveCell(HydrologyCaveAction.values()[index], "river", "flooded-" + index);
|
||||
}
|
||||
Matter[] sections = new Matter[SECTIONS];
|
||||
for (int section = 0; section < SECTIONS; section++) {
|
||||
if (section == 2) {
|
||||
continue;
|
||||
}
|
||||
Matter matter = new IrisMatter(16, 16, 16);
|
||||
sections[section] = matter;
|
||||
int mode = section % 4;
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
boolean aligned = ((z << 8 | y << 4 | x) & 0x333) == 0;
|
||||
int roll = random.nextInt(100);
|
||||
if (mode == 1 ? aligned && roll < 50 : roll < 40) {
|
||||
matter.slice(MatterCavern.class).set(x, y, z, caverns[random.nextInt(caverns.length)]);
|
||||
}
|
||||
if (mode != 3 && random.nextInt(100) < 6) {
|
||||
matter.slice(HydrologyCaveCell.class).set(x, y, z, hydrology[random.nextInt(hydrology.length)]);
|
||||
}
|
||||
int journal = random.nextInt(100);
|
||||
if (journal < 3) {
|
||||
matter.slice(PreObjectMatterCell.class).set(x, y, z, PreObjectMatterCell.cavern(null));
|
||||
} else if (journal < 6) {
|
||||
matter.slice(PreObjectMatterCell.class).set(x, y, z,
|
||||
PreObjectMatterCell.cavern(caverns[random.nextInt(caverns.length)]));
|
||||
} else if (journal < 8) {
|
||||
matter.slice(PreObjectMatterCell.class).set(x, y, z, PreObjectMatterCell.hydrology(null));
|
||||
} else if (journal < 10) {
|
||||
matter.slice(PreObjectMatterCell.class).set(x, y, z,
|
||||
PreObjectMatterCell.block(null).captureHydrology(hydrology[random.nextInt(hydrology.length)]));
|
||||
} else if (journal < 13) {
|
||||
matter.slice(PreObjectMatterCell.class).set(x, y, z, PreObjectMatterCell.string("marker"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return chunk(sections);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static MantleChunk<Matter> chunk(Matter[] sections) {
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
when(chunk.sectionCount()).thenReturn(sections.length);
|
||||
when(chunk.exists(anyInt())).thenAnswer(call -> {
|
||||
int section = call.getArgument(0);
|
||||
return section >= 0 && section < sections.length && sections[section] != null;
|
||||
});
|
||||
when(chunk.get(anyInt())).thenAnswer(call -> {
|
||||
assertTrue(Thread.holdsLock(chunk));
|
||||
int section = call.getArgument(0);
|
||||
return section >= 0 && section < sections.length ? sections[section] : null;
|
||||
});
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package art.arcane.iris.generation.stage;
|
||||
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.mantle.CaveTerrainSnapshot;
|
||||
import art.arcane.iris.generation.mantle.TerrainMatterView;
|
||||
import art.arcane.iris.world.IrisWorld;
|
||||
import art.arcane.iris.world.storage.matter.IrisMatterSupport;
|
||||
@@ -56,6 +57,7 @@ public class IrisCarveBoundaryFacesTest {
|
||||
return true;
|
||||
});
|
||||
when(chunks[side].get(anyInt())).thenAnswer(call -> matter[selected][(int) call.getArgument(0)]);
|
||||
when(chunks[side].sectionCount()).thenReturn(2);
|
||||
if (side == 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -87,10 +89,10 @@ public class IrisCarveBoundaryFacesTest {
|
||||
int[] surfaces = new int[256];
|
||||
Arrays.fill(surfaces, 30);
|
||||
Method method = IrisCarveModifier.class.getDeclaredMethod("addCrossChunkBoundaryWalls",
|
||||
Mantle.class, MantleChunk.class, CarveWallBuffer.class, CarveColumnMask[].class,
|
||||
Mantle.class, CaveTerrainSnapshot.class, CarveWallBuffer.class, CarveColumnMask[].class,
|
||||
int.class, int.class, int[].class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(modifier, mantle, chunks[0], walls, masks, 0, 0, surfaces);
|
||||
method.invoke(modifier, mantle, CaveTerrainSnapshot.capture(chunks[0], 0, 0), walls, masks, 0, 0, surfaces);
|
||||
CarveWallBuffer expectedWalls = new CarveWallBuffer(64);
|
||||
for (int y = 1; y < 32; y++) {
|
||||
for (int offset = 0; offset < 16; offset++) {
|
||||
|
||||
+12
-13
@@ -2,6 +2,7 @@ package art.arcane.iris.generation.stage;
|
||||
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.pack.loading.ResourceLoader;
|
||||
import art.arcane.iris.generation.mantle.CaveTerrainSnapshot;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
@@ -12,9 +13,10 @@ import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.volmlib.util.hunk.Hunk;
|
||||
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.iris.world.storage.matter.IrisMatterSupport;
|
||||
import art.arcane.volmlib.util.matter.IrisMatter;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import art.arcane.volmlib.util.matter.MatterSlice;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import org.junit.Test;
|
||||
@@ -55,30 +57,27 @@ public class IrisCarveModifierBoundarySupportTest {
|
||||
rng.setAccessible(true);
|
||||
rng.set(modifier, new RNG(71L));
|
||||
|
||||
IrisMatterSupport.ensureRegistered();
|
||||
Matter floorMatter = new IrisMatter(16, 16, 16);
|
||||
Matter ceilingMatter = new IrisMatter(16, 16, 16);
|
||||
floorMatter.slice(MatterCavern.class).set(1, 6, 2, new MatterCavern(true, "custom/floor", (byte) 0));
|
||||
ceilingMatter.slice(MatterCavern.class).set(1, 11, 2, new MatterCavern(true, "custom/other", (byte) 0));
|
||||
MantleChunk<Matter> mantleChunk = mock(MantleChunk.class);
|
||||
Matter floorMatter = mock(Matter.class);
|
||||
Matter ceilingMatter = mock(Matter.class);
|
||||
MatterSlice<MatterCavern> floorSlice = mock(MatterSlice.class);
|
||||
MatterSlice<MatterCavern> ceilingSlice = mock(MatterSlice.class);
|
||||
doReturn(3).when(mantleChunk).sectionCount();
|
||||
doReturn(true).when(mantleChunk).exists(0);
|
||||
doReturn(true).when(mantleChunk).exists(2);
|
||||
doReturn(floorMatter).when(mantleChunk).get(0);
|
||||
doReturn(ceilingMatter).when(mantleChunk).get(2);
|
||||
doReturn(true).when(floorMatter).hasSlice(MatterCavern.class);
|
||||
doReturn(true).when(ceilingMatter).hasSlice(MatterCavern.class);
|
||||
doReturn(floorSlice).when(floorMatter).getSlice(MatterCavern.class);
|
||||
doReturn(ceilingSlice).when(ceilingMatter).getSlice(MatterCavern.class);
|
||||
doReturn(new MatterCavern(true, "custom/floor", (byte) 0)).when(floorSlice).get(1, 6, 2);
|
||||
doReturn(null).when(ceilingSlice).get(1, 10, 2);
|
||||
CaveTerrainSnapshot terrain = CaveTerrainSnapshot.capture(mantleChunk, 2, 2);
|
||||
|
||||
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache = new Long2ObjectOpenHashMap<>();
|
||||
Map<String, IrisBiome> customBiomeCache = new HashMap<>();
|
||||
IrisCarveModifier.CaveInputs resolverState = new IrisCarveModifier.CaveInputs(engine);
|
||||
|
||||
IrisBiome floor = modifier.resolveCaveBoundaryBiome(
|
||||
mantleChunk, 1, 6, 2, 40, 44, resolverState, caveBiomeCache, customBiomeCache);
|
||||
terrain, 1, 6, 2, 40, 44, resolverState, caveBiomeCache, customBiomeCache);
|
||||
IrisBiome ceiling = modifier.resolveCaveBoundaryBiome(
|
||||
mantleChunk, 1, 42, 2, 40, 44, resolverState, caveBiomeCache, customBiomeCache);
|
||||
terrain, 1, 42, 2, 40, 44, resolverState, caveBiomeCache, customBiomeCache);
|
||||
|
||||
assertSame(customFloor, floor);
|
||||
assertSame(resolvedCeiling, ceiling);
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
package art.arcane.iris.generation.stage;
|
||||
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.generation.mantle.CaveTerrainSnapshot;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.terrain.InferredType;
|
||||
@@ -56,7 +57,7 @@ public class IrisCarveModifierInferenceIsolationTest {
|
||||
Method paintBoundaryZone = IrisCarveModifier.class.getDeclaredMethod(
|
||||
"paintBoundaryZone",
|
||||
Hunk.class,
|
||||
MantleChunk.class,
|
||||
CaveTerrainSnapshot.class,
|
||||
CarveWallBuffer.class,
|
||||
int.class,
|
||||
int.class,
|
||||
@@ -72,7 +73,7 @@ public class IrisCarveModifierInferenceIsolationTest {
|
||||
paintBoundaryZone.invoke(
|
||||
modifier,
|
||||
mock(Hunk.class),
|
||||
mock(MantleChunk.class),
|
||||
CaveTerrainSnapshot.capture(mock(MantleChunk.class), 0, 0),
|
||||
walls,
|
||||
0,
|
||||
0,
|
||||
|
||||
@@ -5,6 +5,7 @@ import art.arcane.iris.generation.decoration.IrisCeilingDecorator;
|
||||
import art.arcane.iris.generation.decoration.IrisSurfaceDecorator;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.SeedManager;
|
||||
import art.arcane.iris.generation.mantle.CaveTerrainSnapshot;
|
||||
import art.arcane.iris.generation.mantle.EngineMantle;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.decoration.IrisDecorationPart;
|
||||
@@ -38,6 +39,7 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
@@ -78,7 +80,7 @@ public class IrisNaturalCaveScanTest {
|
||||
throw failure;
|
||||
});
|
||||
assertSame(failure, assertThrows(IllegalStateException.class,
|
||||
() -> fixture.modifier.decorateNaturalCaves(-32, 48, output)));
|
||||
() -> fixture.modifier.decorateNaturalCaves(-32, 48, output, null)));
|
||||
verify(fixture.chunk).release();
|
||||
}
|
||||
|
||||
@@ -92,7 +94,7 @@ public class IrisNaturalCaveScanTest {
|
||||
IllegalStateException failure = new IllegalStateException("height unavailable");
|
||||
doAnswer(call -> { throw failure; }).when(output).getHeight();
|
||||
assertSame(failure, assertThrows(IllegalStateException.class,
|
||||
() -> fixture.modifier.decorateNaturalCaves(-32, 48, output)));
|
||||
() -> fixture.modifier.decorateNaturalCaves(-32, 48, output, null)));
|
||||
verify(fixture.chunk).release();
|
||||
}
|
||||
|
||||
@@ -104,7 +106,7 @@ public class IrisNaturalCaveScanTest {
|
||||
Hunk<NativeBlockState> fullOutput = full.sparseOutput(Hunk.newArrayHunk(16, 32, 16));
|
||||
List<String> fullWrites = new ArrayList<>();
|
||||
full.modifier.decorateNaturalCaves(-32, 48,
|
||||
fullOutput.listen((x, y, z, state) -> fullWrites.add(x + ":" + y + ":" + z + ":" + state.key())));
|
||||
fullOutput.listen((x, y, z, state) -> fullWrites.add(x + ":" + y + ":" + z + ":" + state.key())), null);
|
||||
|
||||
Fixture bounded = new Fixture(true);
|
||||
ChunkGenerator.ChunkData chunkData = mock(ChunkGenerator.ChunkData.class);
|
||||
@@ -114,7 +116,7 @@ public class IrisNaturalCaveScanTest {
|
||||
bounded.sparseOutput(holder);
|
||||
List<String> boundedWrites = new ArrayList<>();
|
||||
bounded.modifier.decorateNaturalCaves(-32, 48, new ColumnExtentListeningHunk<>(holder,
|
||||
(x, y, z, state) -> boundedWrites.add(x + ":" + y + ":" + z + ":" + state.key())));
|
||||
(x, y, z, state) -> boundedWrites.add(x + ":" + y + ":" + z + ":" + state.key())), null);
|
||||
|
||||
assertTrue("fixture must decorate a zone above the initial column top",
|
||||
full.calls.contains("floor:0:0:27:1"));
|
||||
@@ -141,7 +143,7 @@ public class IrisNaturalCaveScanTest {
|
||||
Hunk<NativeBlockState> output = fixture.output(width, depth);
|
||||
List<String> writes = new ArrayList<>();
|
||||
fixture.modifier.decorateNaturalCaves(-32, 48,
|
||||
output.listen((x, y, z, state) -> writes.add(x + ":" + y + ":" + z + ":" + state.key())));
|
||||
output.listen((x, y, z, state) -> writes.add(x + ":" + y + ":" + z + ":" + state.key())), null);
|
||||
List<String> expectedCalls = new ArrayList<>();
|
||||
List<String> expectedMarkers = new ArrayList<>();
|
||||
List<String> expectedWrites = new ArrayList<>();
|
||||
@@ -239,7 +241,7 @@ public class IrisNaturalCaveScanTest {
|
||||
IrisBiome biome = mock(IrisBiome.class);
|
||||
doReturn(new IrisDecorator[]{new IrisDecorator()}).when(biome).getDecoratorBucket(IrisDecorationPart.NONE);
|
||||
doReturn(new IrisDecorator[]{new IrisDecorator()}).when(biome).getDecoratorBucket(IrisDecorationPart.CEILING);
|
||||
doReturn(biome).when(modifier).resolveCaveBoundaryBiome(eq(chunk), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), any(), any());
|
||||
doReturn(biome).when(modifier).resolveCaveBoundaryBiome(argThat((CaveTerrainSnapshot terrain) -> terrain != null && terrain.covers(-2, 3)), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), any(), any());
|
||||
IrisDecorantActuator decorant = mock(IrisDecorantActuator.class);
|
||||
IrisSurfaceDecorator surface = mock(IrisSurfaceDecorator.class);
|
||||
IrisCeilingDecorator roof = mock(IrisCeilingDecorator.class);
|
||||
|
||||
Reference in new issue
Block a user