mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +00:00
Deposit clump fast paths
- Clump randoms use a plain-field LCG with the RNG sequence and a step jump - Single-block palettes build footprints from row masks and jump the pick draws - Ellipsoid nodes, row offsets and node sines reuse thread scratch and size tables - Terrain-scoped footprint plans that cannot reach the chunk surface are skipped - Above-terrain plans are skipped when no solid host sits above the surface - Preparation runs as at most four balanced slices with one join per chunk - Placement caches target, bedrock and replaceable checks per block state
This commit is contained in:
1 parent
af90765a92
commit
9cd470c7c8
8 files changed
+743
-142
No files matched your search
@@ -0,0 +1,65 @@
|
||||
package art.arcane.iris.generation.decoration;
|
||||
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class DepositRandomTest {
|
||||
@Test
|
||||
public void everyDrawMatchesTheRngSequence() {
|
||||
int[] bounds = {1, 2, 3, 5, 7, 16, 100, 1 << 20, Integer.MAX_VALUE};
|
||||
for (long seed : new long[]{0L, 1L, -1L, 42L, 0x9E3779B97F4A7C15L, Long.MIN_VALUE}) {
|
||||
RNG reference = new RNG(seed);
|
||||
DepositRandom random = new DepositRandom(seed);
|
||||
assertEquals(reference.getSeed(), random.getSeed());
|
||||
for (int draw = 0; draw < 4000; draw++) {
|
||||
int bound = bounds[draw % bounds.length];
|
||||
switch (draw % 9) {
|
||||
case 0 -> assertEquals(reference.nextInt(), random.nextInt());
|
||||
case 1 -> assertEquals(reference.nextInt(bound), random.nextInt(bound));
|
||||
case 2 -> assertEquals(reference.nextLong(), random.nextLong());
|
||||
case 3 -> assertEquals(Double.doubleToRawLongBits(reference.nextDouble()),
|
||||
Double.doubleToRawLongBits(random.nextDouble()));
|
||||
case 4 -> assertEquals(Float.floatToRawIntBits(reference.nextFloat()),
|
||||
Float.floatToRawIntBits(random.nextFloat()));
|
||||
case 5 -> assertEquals(reference.nextBoolean(), random.nextBoolean());
|
||||
case 6 -> assertEquals(Double.doubleToRawLongBits(reference.d()),
|
||||
Double.doubleToRawLongBits(random.d()));
|
||||
case 7 -> assertEquals(reference.i(-bound / 2, bound), random.i(-bound / 2, bound));
|
||||
default -> assertEquals(reference.i(0, 1), random.i(0, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void skipAdvancesExactlyLikeRepeatedSteps() {
|
||||
for (long seed : new long[]{3L, -11L, 0x5DEECE66DL}) {
|
||||
for (long steps : new long[]{0L, 1L, 2L, 3L, 7L, 64L, 65L, 1000L, 123457L}) {
|
||||
DepositRandom stepped = new DepositRandom(seed);
|
||||
DepositRandom jumped = new DepositRandom(seed);
|
||||
for (long step = 0L; step < steps; step++) {
|
||||
stepped.nextInt();
|
||||
}
|
||||
jumped.skip(steps);
|
||||
assertEquals("seed " + seed + " steps " + steps, stepped.state(), jumped.state());
|
||||
assertEquals(stepped.nextLong(), jumped.nextLong());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reseedingAndRestoringReproduceTheSequence() {
|
||||
DepositRandom random = new DepositRandom();
|
||||
for (long seed : new long[]{5L, 77L, -9001L}) {
|
||||
random.setSeed(seed);
|
||||
RNG reference = new RNG(seed);
|
||||
long saved = random.state();
|
||||
long first = random.nextLong();
|
||||
assertEquals(reference.nextLong(), first);
|
||||
random.restore(saved);
|
||||
assertEquals(first, random.nextLong());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,6 +260,56 @@ public class IrisDepositTuningTest {
|
||||
assertNull(generator.sampleClumpFootprint(new RNG(1L), data));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uniformEllipsoidFootprintCellsMatchTheBuiltClumpForEitherRandom() {
|
||||
IrisData data = mock(IrisData.class);
|
||||
IrisDepositGenerator generator = generatorWithState(data, true);
|
||||
generator.setShape(IrisDepositShape.VANILLA_ELLIPSOID);
|
||||
for (int size : new int[]{0, 1, 2, 3, 4, 9, 17, 20, 25, 48, 64}) {
|
||||
generator.setMinSize(size).setMaxSize(size);
|
||||
for (long seed = 0; seed < 300; seed++) {
|
||||
RNG built = new RNG(seed);
|
||||
IrisObject clump = generator.getClump(null, built, data);
|
||||
long expectedNext = built.nextLong();
|
||||
List<String> expected = new ArrayList<>();
|
||||
VectorMap<NativeBlockState>.Cursor cursor = clump.getBlocks().cursor();
|
||||
while (cursor.next()) {
|
||||
expected.add(cursor.key().getBlockX() + "," + cursor.key().getBlockY() + ","
|
||||
+ cursor.key().getBlockZ() + "=" + cursor.value().key());
|
||||
}
|
||||
for (RNG sampled : new RNG[]{new RNG(seed), new DepositRandom(seed)}) {
|
||||
IrisDepositGenerator.ClumpFootprint footprint = generator.sampleClumpFootprint(sampled, data);
|
||||
assertEquals(clump.getW(), footprint.width());
|
||||
IrisDepositGenerator.ClumpCells cells = footprint.cells();
|
||||
List<String> actual = new ArrayList<>();
|
||||
for (int cell = 0; cell < cells.size(); cell++) {
|
||||
actual.add(cells.x(cell) + "," + cells.y(cell) + "," + cells.z(cell) + "="
|
||||
+ cells.block(cell).key());
|
||||
}
|
||||
assertEquals("size " + size + " seed " + seed, expected, actual);
|
||||
assertEquals("size " + size + " seed " + seed, expectedNext, sampled.nextLong());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void footprintOffsetBoundNeverExceedsTheLowestEllipsoidCell() {
|
||||
IrisData data = mock(IrisData.class);
|
||||
IrisDepositGenerator generator = generatorWithState(data, true);
|
||||
generator.setShape(IrisDepositShape.VANILLA_ELLIPSOID);
|
||||
for (int size = 1; size <= 64; size++) {
|
||||
generator.setMinSize(size).setMaxSize(size);
|
||||
int bound = IrisDepositGenerator.lowestFootprintOffset(size);
|
||||
for (long seed = 0; seed < 400; seed++) {
|
||||
IrisDepositGenerator.ClumpFootprint footprint = generator.sampleClumpFootprint(new DepositRandom(seed), data);
|
||||
if (!footprint.empty()) {
|
||||
assertTrue("size " + size + " seed " + seed, footprint.minY() >= bound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ellipsoidFootprintCellsMatchTheBuiltClumpInPlacementOrder() {
|
||||
IrisData data = mock(IrisData.class);
|
||||
|
||||
+12
-13
@@ -54,8 +54,8 @@ public class IrisDepositModifierContextTest {
|
||||
try (Fixture fixture = new Fixture(engine, true)) {
|
||||
fixture.generateAndCheckCleanup();
|
||||
verify(engine, times(1)).captureGenerationRuntimeBinding();
|
||||
verify(engine, times(4)).openGenerationRuntimeScope(fixture.binding);
|
||||
assertEquals(4, fixture.closedScopes.get());
|
||||
verify(engine, times(1)).openGenerationRuntimeScope(fixture.binding);
|
||||
assertEquals(1, fixture.closedScopes.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,13 +84,13 @@ public class IrisDepositModifierContextTest {
|
||||
assertSame(failure, assertThrows(IllegalStateException.class, () ->
|
||||
fixture.modifier.modify(32, -48, Hunk.newArrayHunk(16, 16, 16), true, fixture.context)));
|
||||
verify(fixture.chunk).release();
|
||||
assertEquals(4, fixture.closedScopes.get());
|
||||
assertEquals(1, fixture.closedScopes.get());
|
||||
assertNull(IrisContext.get());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void callerPlanFailureStillDrainsWorkersAndRestoresContext() throws Exception {
|
||||
public void callerPlanFailureQueuesNoWorkAndRestoresContext() throws Exception {
|
||||
try (Fixture fixture = new Fixture(mock(IrisEngine.class, RETURNS_DEEP_STUBS), true)) {
|
||||
IllegalStateException failure = new IllegalStateException("caller deposit plan failed");
|
||||
doThrow(failure).when(fixture.biomeDeposit).getSpawnChance();
|
||||
@@ -102,25 +102,24 @@ public class IrisDepositModifierContextTest {
|
||||
assertEquals(19L, IrisContext.require().getGenerationSessionId());
|
||||
}
|
||||
verify(fixture.chunk).release();
|
||||
assertEquals(3, fixture.closedScopes.get());
|
||||
assertEquals(10, fixture.completed.get());
|
||||
assertEquals(0, fixture.closedScopes.get());
|
||||
assertEquals(0, fixture.completed.get());
|
||||
assertNull(IrisContext.get());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void callerFailureRetainsDistinctWorkerFailuresAfterDraining() throws Exception {
|
||||
public void distinctWorkerFailuresAreRetainedAfterDraining() throws Exception {
|
||||
try (Fixture fixture = new Fixture(mock(IrisEngine.class, RETURNS_DEEP_STUBS), true)) {
|
||||
IllegalStateException caller = new IllegalStateException("caller failure");
|
||||
IllegalArgumentException firstWorker = new IllegalArgumentException("first worker failure");
|
||||
UnsupportedOperationException secondWorker = new UnsupportedOperationException("second worker failure");
|
||||
when(fixture.dimensionDeposit.getMinPerChunk()).thenReturn(200);
|
||||
when(fixture.dimensionDeposit.getMaxPerChunk()).thenReturn(200);
|
||||
doThrow(firstWorker, secondWorker).when(fixture.dimensionDeposit).getClump(any(), any(), any());
|
||||
doThrow(caller).when(fixture.biomeDeposit).getSpawnChance();
|
||||
assertSame(caller, assertThrows(IllegalStateException.class, () ->
|
||||
assertSame(firstWorker, assertThrows(IllegalArgumentException.class, () ->
|
||||
fixture.modifier.modify(32, -48, Hunk.newArrayHunk(16, 16, 16), true, fixture.context)));
|
||||
assertEquals(2, caller.getSuppressed().length);
|
||||
assertSame(firstWorker, caller.getSuppressed()[0]);
|
||||
assertSame(secondWorker, caller.getSuppressed()[1]);
|
||||
assertEquals(1, firstWorker.getSuppressed().length);
|
||||
assertSame(secondWorker, firstWorker.getSuppressed()[0]);
|
||||
verify(fixture.chunk).release();
|
||||
assertEquals(3, fixture.closedScopes.get());
|
||||
assertNull(IrisContext.get());
|
||||
|
||||
+21
-9
@@ -79,16 +79,16 @@ public class IrisDepositModifierOrderingTest {
|
||||
|
||||
@Test
|
||||
public void reverseWorkerCompletionKeepsDimensionRegionBiomePlacementOrder() throws Exception {
|
||||
try (Fixture fixture = new Fixture(true, 1)) {
|
||||
try (Fixture fixture = new Fixture(true, 96, true)) {
|
||||
fixture.generate();
|
||||
assertEquals(List.of("biome", "region", "dimension"), fixture.completed);
|
||||
assertEquals(List.of("biome", "region", "dimension"), fixture.firstCompletions());
|
||||
fixture.assertBiomeWins();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inlinePreparationUsesTheSameOverlappingHostRules() throws Exception {
|
||||
try (Fixture fixture = new Fixture(false, 1)) {
|
||||
try (Fixture fixture = new Fixture(false, 1, false)) {
|
||||
fixture.generate();
|
||||
assertEquals(List.of("dimension", "region", "biome"), fixture.completed);
|
||||
fixture.assertBiomeWins();
|
||||
@@ -107,7 +107,7 @@ public class IrisDepositModifierOrderingTest {
|
||||
Collections.sort(expectedSamples);
|
||||
|
||||
for (boolean parallel : new boolean[]{false, true}) {
|
||||
try (Fixture fixture = new Fixture(parallel, 25)) {
|
||||
try (Fixture fixture = new Fixture(parallel, 25, false)) {
|
||||
fixture.generate();
|
||||
Collections.sort(fixture.samples);
|
||||
assertEquals(expectedSamples, fixture.samples);
|
||||
@@ -290,11 +290,13 @@ public class IrisDepositModifierOrderingTest {
|
||||
private final MantleChunk<Matter> chunk;
|
||||
private final boolean parallel;
|
||||
private final int attempts;
|
||||
private final boolean reverse;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Fixture(boolean parallel, int attempts) {
|
||||
private Fixture(boolean parallel, int attempts, boolean reverse) {
|
||||
this.parallel = parallel;
|
||||
this.attempts = attempts;
|
||||
this.reverse = reverse;
|
||||
IrisBiome biome = mock(IrisBiome.class, withSettings().stubOnly());
|
||||
IrisRegion region = mock(IrisRegion.class, withSettings().stubOnly());
|
||||
ChunkedDataCache<IrisBiome> biomes = new ChunkedDataCache<>(
|
||||
@@ -348,11 +350,9 @@ public class IrisDepositModifierOrderingTest {
|
||||
}
|
||||
}
|
||||
when(generator.getClump(any(), any(), any())).thenAnswer(invocation -> {
|
||||
assertTrue("Placement must release the first clump window before more are prepared",
|
||||
samples.size() < 32 || output.get(0, 4, 0) != stone);
|
||||
if (parallel && attempts == 1 && name.equals("dimension")) {
|
||||
if (reverse && name.equals("dimension")) {
|
||||
assertTrue(regionReady.await(10, TimeUnit.SECONDS));
|
||||
} else if (parallel && attempts == 1 && name.equals("region")) {
|
||||
} else if (reverse && name.equals("region")) {
|
||||
assertTrue(biomeReady.await(10, TimeUnit.SECONDS));
|
||||
}
|
||||
RNG rng = invocation.getArgument(1);
|
||||
@@ -376,6 +376,18 @@ public class IrisDepositModifierOrderingTest {
|
||||
verify(chunk).release();
|
||||
}
|
||||
|
||||
private List<String> firstCompletions() {
|
||||
List<String> order = new ArrayList<>();
|
||||
synchronized (completed) {
|
||||
for (String name : completed) {
|
||||
if (!order.contains(name)) {
|
||||
order.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
private void assertBiomeWins() {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
|
||||
+73
-1
@@ -8,6 +8,7 @@ import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.decoration.IrisDepositBiomeScope;
|
||||
import art.arcane.iris.generation.decoration.IrisDepositGenerator;
|
||||
import art.arcane.iris.generation.decoration.IrisDepositHeightDistribution;
|
||||
import art.arcane.iris.generation.decoration.IrisDepositPlacementScope;
|
||||
import art.arcane.iris.generation.decoration.IrisDepositShape;
|
||||
import art.arcane.iris.generation.decoration.IrisDepositVariant;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
@@ -129,6 +130,56 @@ public class IrisDepositModifierParityTest {
|
||||
assertEquals(0, clumps.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void terrainFootprintsAboveTheHighestSurfaceAreSkippedWithoutChangingOutput() {
|
||||
long[] expected = {-681153659952315909L, 784L, 2118L, 53L, 67L, 10L};
|
||||
for (boolean multicore : new boolean[]{false, true}) {
|
||||
AtomicInteger skipped = new AtomicInteger();
|
||||
long[] withSkipped = generateConfigured(101L, multicore, true, false, new AtomicInteger(), null, true,
|
||||
skyDeposit(47, skipped));
|
||||
assertEquals(Arrays.toString(expected), Arrays.toString(withSkipped));
|
||||
assertEquals(0, skipped.get());
|
||||
AtomicInteger sampled = new AtomicInteger();
|
||||
long[] withSampled = generateConfigured(101L, multicore, true, false, new AtomicInteger(), null, true,
|
||||
skyDeposit(46, sampled));
|
||||
assertEquals(Arrays.toString(expected), Arrays.toString(withSampled));
|
||||
assertTrue(sampled.get() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aboveTerrainDepositsWithoutHostsAreSkippedWithoutChangingOutput() {
|
||||
long[] expected = {-681153659952315909L, 784L, 2118L, 53L, 67L, 10L};
|
||||
for (boolean multicore : new boolean[]{false, true}) {
|
||||
AtomicInteger vacant = new AtomicInteger();
|
||||
long[] withoutHosts = generateConfigured(101L, multicore, true, false, new AtomicInteger(), null, true,
|
||||
floatingDeposit(vacant), false);
|
||||
assertEquals(Arrays.toString(expected), Arrays.toString(withoutHosts));
|
||||
assertEquals(0, vacant.get());
|
||||
AtomicInteger hosted = new AtomicInteger();
|
||||
generateConfigured(101L, multicore, true, false, new AtomicInteger(), null, true,
|
||||
floatingDeposit(hosted), true);
|
||||
assertTrue(hosted.get() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
private IrisDepositGenerator floatingDeposit(AtomicInteger clumps) {
|
||||
return new CountingPaletteGenerator(state("andesite"), clumps)
|
||||
.setShape(IrisDepositShape.VANILLA_ELLIPSOID).setMinSize(9).setMaxSize(9)
|
||||
.setMinPerChunk(40).setMaxPerChunk(40).setMinHeight(0).setMaxHeight(47)
|
||||
.setHeightDistribution(IrisDepositHeightDistribution.UNIFORM).setSurfaceClearance(0)
|
||||
.setPlacementScope(IrisDepositPlacementScope.ABOVE_TERRAIN)
|
||||
.setPerClumpSpawnChance(1D).setReplaceableBlocks(new KList<>("stone"));
|
||||
}
|
||||
|
||||
private IrisDepositGenerator skyDeposit(int minimumHeight, AtomicInteger clumps) {
|
||||
return new CountingPaletteGenerator(state("andesite"), clumps)
|
||||
.setShape(IrisDepositShape.VANILLA_ELLIPSOID).setMinSize(17).setMaxSize(17)
|
||||
.setMinPerChunk(60).setMaxPerChunk(60).setMinHeight(minimumHeight).setMaxHeight(400)
|
||||
.setHeightDistribution(IrisDepositHeightDistribution.UNIFORM).setSurfaceClearance(0)
|
||||
.setPerClumpSpawnChance(1D).setReplaceableBlocks(new KList<>("stone"));
|
||||
}
|
||||
|
||||
private long[] generateConfigured(long seed, boolean multicore) {
|
||||
return generateConfigured(seed, multicore, true, false, new AtomicInteger());
|
||||
}
|
||||
@@ -143,10 +194,25 @@ public class IrisDepositModifierParityTest {
|
||||
return generateConfigured(seed, multicore, caveVariant, enumerablePack, caveBiomeLookups, unmatchableClumps, true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private long[] generateConfigured(long seed, boolean multicore, boolean caveVariant, boolean enumerablePack,
|
||||
AtomicInteger caveBiomeLookups, AtomicInteger unmatchableClumps,
|
||||
boolean oreScaling) {
|
||||
return generateConfigured(seed, multicore, caveVariant, enumerablePack, caveBiomeLookups, unmatchableClumps,
|
||||
oreScaling, null, false);
|
||||
}
|
||||
|
||||
private long[] generateConfigured(long seed, boolean multicore, boolean caveVariant, boolean enumerablePack,
|
||||
AtomicInteger caveBiomeLookups, AtomicInteger unmatchableClumps,
|
||||
boolean oreScaling, IrisDepositGenerator extraSurfaceDeposit) {
|
||||
return generateConfigured(seed, multicore, caveVariant, enumerablePack, caveBiomeLookups, unmatchableClumps,
|
||||
oreScaling, extraSurfaceDeposit, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private long[] generateConfigured(long seed, boolean multicore, boolean caveVariant, boolean enumerablePack,
|
||||
AtomicInteger caveBiomeLookups, AtomicInteger unmatchableClumps,
|
||||
boolean oreScaling, IrisDepositGenerator extraSurfaceDeposit,
|
||||
boolean floatingHost) {
|
||||
Engine engine = mock(Engine.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS));
|
||||
ChunkContext context = mock(ChunkContext.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS));
|
||||
IrisDimension dimension = new IrisDimension();
|
||||
@@ -189,6 +255,9 @@ public class IrisDepositModifierParityTest {
|
||||
.setIncludedBiomes(new KList<>("elsewhere"));
|
||||
surface.getDeposits().add(unmatchable);
|
||||
}
|
||||
if (extraSurfaceDeposit != null) {
|
||||
surface.getDeposits().add(extraSurfaceDeposit);
|
||||
}
|
||||
when(engine.getHeight()).thenReturn(48);
|
||||
when(engine.getMinHeight()).thenReturn(-64);
|
||||
when(engine.getSeedManager().getDeposit()).thenReturn(seed);
|
||||
@@ -241,6 +310,9 @@ public class IrisDepositModifierParityTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (floatingHost) {
|
||||
terrain.set(8, 44, 8, state("stone"));
|
||||
}
|
||||
MultiBurst burst = new MultiBurst("Deposit parity", () -> 4);
|
||||
when(engine.burst()).thenReturn(burst);
|
||||
try (MockedStatic<B> blocks = mockStatic(B.class, withSettings().stubOnly().defaultAnswer(CALLS_REAL_METHODS))) {
|
||||
|
||||
Reference in new issue
Block a user