Bounded deposit preparation

- Deposit preparation retains at most 32 clumps between ordered placement passes
- Large deposit counts retain parallel preparation and stable per-clump seeds
- Overlap tests cover every attempt across preparation windows
- Deposit tests isolate platform block conversion from cached registry fixtures
This commit is contained in:
Brian Neumann-Fopiano
2026-09-06 03:46:25 -04:00
parent 75353d43c7
commit 420148fcc7
2 changed files with 103 additions and 36 deletions
@@ -50,6 +50,9 @@ import java.util.ArrayList;
import java.util.List;
public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockState> {
private static final int CLUMPS_PER_BATCH = 8;
private static final int PREPARATION_BATCH_COUNT = 4;
private final RNG rng;
public IrisDepositModifier(Engine engine) {
@@ -78,31 +81,47 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
BurstExecutor burst = burst().burst(multicore);
long seed = x * 341873128712L + z * 132897987541L;
PreparedDeposit[] prepared = new PreparedDeposit[generators.size()];
PreparedDeposit[] prepared = new PreparedDeposit[PREPARATION_BATCH_COUNT];
int pending = 0;
MantleChunk chunk = getEngine().getMantle().getMantle().getChunk(x, z).use();
try {
try {
for (int i = 0; i < generators.size(); i++) {
int index = i;
IrisDepositGenerator generator = generators.get(i);
RNG generatorRng = rng.nextParallelRNG(seed * (i + 1L));
for (int i = 0; i < generators.size(); i++) {
DepositPlan plan = plan(generators.get(i), rng.nextParallelRNG(seed * (i + 1L)));
for (int first = 0; first < plan.attempts(); first += CLUMPS_PER_BATCH) {
int firstAttempt = first;
int limit = Math.min(plan.attempts(), first + CLUMPS_PER_BATCH);
int index = pending++;
burst.queue(scopedDepositTask(
() -> prepared[index] = prepare(generator, generatorRng, x, z, null, context), context));
}
} finally {
// complete() must run before release() even when queueing throws — already
// submitted burst tasks must never write into a released chunk.
burst.complete();
}
try (IrisContext.Scope chunkScope = IrisContext.open(getEngine(), context.getGenerationSessionId(), context)) {
for (PreparedDeposit deposit : prepared) {
if (deposit != null) {
place(deposit, chunk, terrain, x, z, null, context);
() -> prepared[index] = prepare(plan, firstAttempt, limit, x, z, null, context), context));
if (pending == prepared.length) {
completeBatches(burst, prepared, chunk, terrain, x, z, context);
pending = 0;
}
}
}
completeBatches(burst, prepared, chunk, terrain, x, z, context);
} finally {
chunk.release();
// complete() must run before release() even when queueing throws — already
// submitted burst tasks must never write into a released chunk.
try {
burst.complete();
} finally {
chunk.release();
}
}
}
private void completeBatches(BurstExecutor burst, PreparedDeposit[] prepared, MantleChunk chunk,
Hunk<PlatformBlockState> terrain, int x, int z, ChunkContext context) {
burst.complete();
try (IrisContext.Scope chunkScope = IrisContext.open(getEngine(), context.getGenerationSessionId(), context)) {
for (int i = 0; i < prepared.length; i++) {
PreparedDeposit deposit = prepared[i];
prepared[i] = null;
if (deposit != null) {
place(deposit, chunk, terrain, x, z, null, context);
}
}
}
}
@@ -125,21 +144,30 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
}
public void generate(IrisDepositGenerator k, MantleChunk chunk, Hunk<PlatformBlockState> data, RNG rng, int cx, int cz, boolean safe, HeightMap he, ChunkContext context) {
place(prepare(k, rng, cx, cz, he, context), chunk, data, cx, cz, he, context);
DepositPlan plan = plan(k, rng);
for (int first = 0; first < plan.attempts(); first += CLUMPS_PER_BATCH) {
int limit = Math.min(plan.attempts(), first + CLUMPS_PER_BATCH);
place(prepare(plan, first, limit, cx, cz, he, context), chunk, data, cx, cz, he, context);
}
}
private PreparedDeposit prepare(IrisDepositGenerator k, RNG rng, int cx, int cz, HeightMap he, ChunkContext context) {
if (k.getSpawnChance() < rng.d()) {
return new PreparedDeposit(k, false, List.of());
private DepositPlan plan(IrisDepositGenerator generator, RNG rng) {
if (generator.getSpawnChance() < rng.d()) {
return new DepositPlan(generator, rng, false, 0);
}
boolean ore = generator.isOre(getData());
int attempts = rng.i(generator.getMinPerChunk(), generator.getMaxPerChunk() + 1);
return new DepositPlan(generator, rng, ore, attempts);
}
boolean oreDeposit = k.isOre(getData());
private PreparedDeposit prepare(DepositPlan plan, int first, int limit, int cx, int cz, HeightMap he, ChunkContext context) {
IrisDepositGenerator k = plan.generator();
RNG rng = plan.rng();
boolean oreDeposit = plan.ore();
boolean needsCaveBiome = oreDeposit || k.usesCaveBiomeFilter();
IrisDimensionCarvingResolver.State carvingState = needsCaveBiome ? new IrisDimensionCarvingResolver.State() : null;
int attempts = rng.i(k.getMinPerChunk(), k.getMaxPerChunk() + 1);
List<PreparedClump> clumps = new ArrayList<>(Math.max(0, attempts));
for (int l = 0; l < attempts; l++) {
List<PreparedClump> clumps = new ArrayList<>(limit - first);
for (int l = first; l < limit; l++) {
RNG clumpRng = rng.nextParallelRNG(l + 1L);
if (k.getPerClumpSpawnChance() < clumpRng.d()) {
continue;
@@ -464,6 +492,9 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
return null;
}
private record DepositPlan(IrisDepositGenerator generator, RNG rng, boolean ore, int attempts) {
}
private record PreparedDeposit(IrisDepositGenerator generator, boolean ore, List<PreparedClump> clumps) {
}
@@ -9,6 +9,7 @@ import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.spi.PlatformRegistries;
import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.common.math.IrisBlockVector;
import art.arcane.iris.util.common.parallel.BurstExecutor;
import art.arcane.iris.util.common.parallel.MultiBurst;
@@ -18,9 +19,11 @@ import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.math.RNG;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.util.ArrayList;
import java.util.Collections;
@@ -37,7 +40,9 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -49,7 +54,6 @@ public class IrisDepositModifierOrderingTest {
PlatformRegistries registries = mock(PlatformRegistries.class);
PlatformBlockState stone = state("stone");
when(registries.block(anyString())).thenReturn(stone);
when(registries.deepSlateOre(any(), any())).thenAnswer(invocation -> invocation.getArgument(1));
IrisPlatform platform = mock(IrisPlatform.class);
when(platform.registries()).thenReturn(registries);
IrisPlatforms.bind(platform);
@@ -62,7 +66,7 @@ public class IrisDepositModifierOrderingTest {
@Test
public void reverseWorkerCompletionKeepsDimensionRegionBiomePlacementOrder() throws Exception {
try (Fixture fixture = new Fixture(true)) {
try (Fixture fixture = new Fixture(true, 1)) {
fixture.generate();
assertEquals(List.of("biome", "region", "dimension"), fixture.completed);
fixture.assertBiomeWins();
@@ -71,13 +75,35 @@ public class IrisDepositModifierOrderingTest {
@Test
public void inlinePreparationUsesTheSameOverlappingHostRules() throws Exception {
try (Fixture fixture = new Fixture(false)) {
try (Fixture fixture = new Fixture(false, 1)) {
fixture.generate();
assertEquals(List.of("dimension", "region", "biome"), fixture.completed);
fixture.assertBiomeWins();
}
}
@Test
public void manyAttemptBatchesKeepEveryClumpSeedAndOrderedHostReplacement() throws Exception {
List<String> expectedSamples = new ArrayList<>();
RNG generatorRng = new RNG(0L).nextParallelRNG(0L);
for (String name : List.of("dimension", "region", "biome")) {
for (int i = 0; i < 25; i++) {
expectedSamples.add(name + ":" + generatorRng.nextParallelRNG(i + 1L).getSeed());
}
}
Collections.sort(expectedSamples);
for (boolean parallel : new boolean[]{false, true}) {
try (Fixture fixture = new Fixture(parallel, 25)) {
fixture.generate();
Collections.sort(fixture.samples);
assertEquals(expectedSamples, fixture.samples);
assertEquals(75, fixture.completed.size());
fixture.assertBiomeWins();
}
}
}
private static PlatformBlockState state(String material) {
PlatformBlockState state = mock(PlatformBlockState.class);
when(state.key()).thenReturn("minecraft:" + material);
@@ -88,6 +114,7 @@ public class IrisDepositModifierOrderingTest {
private static final class Fixture implements AutoCloseable {
private final ExecutorService executor = Executors.newFixedThreadPool(3);
private final List<String> completed = Collections.synchronizedList(new ArrayList<>());
private final List<String> samples = Collections.synchronizedList(new ArrayList<>());
private final CountDownLatch biomeReady = new CountDownLatch(1);
private final CountDownLatch regionReady = new CountDownLatch(1);
private final Engine engine = mock(Engine.class, RETURNS_DEEP_STUBS);
@@ -99,10 +126,12 @@ public class IrisDepositModifierOrderingTest {
private final Hunk<PlatformBlockState> output = Hunk.newArrayHunk(16, 16, 16);
private final MantleChunk<Matter> chunk;
private final boolean parallel;
private final int attempts;
@SuppressWarnings("unchecked")
private Fixture(boolean parallel) {
private Fixture(boolean parallel, int attempts) {
this.parallel = parallel;
this.attempts = attempts;
when(engine.getHeight()).thenReturn(16);
when(engine.getCaveBiome(anyInt(), anyInt(), anyInt(), any())).thenReturn(null);
when(engine.getDimension().getDepositVariants()).thenReturn(new KList<>());
@@ -134,8 +163,8 @@ public class IrisDepositModifierOrderingTest {
IrisDepositGenerator generator = mock(IrisDepositGenerator.class);
when(generator.getSpawnChance()).thenReturn(1D);
when(generator.getPerClumpSpawnChance()).thenReturn(1D);
when(generator.getMinPerChunk()).thenReturn(1);
when(generator.getMaxPerChunk()).thenReturn(1);
when(generator.getMinPerChunk()).thenReturn(attempts);
when(generator.getMaxPerChunk()).thenReturn(attempts);
when(generator.getMinHeight()).thenReturn(4);
when(generator.getMaxHeight()).thenReturn(4);
when(generator.getPlacementScope()).thenReturn(IrisDepositPlacementScope.FULL_HEIGHT);
@@ -149,11 +178,15 @@ public class IrisDepositModifierOrderingTest {
}
}
when(generator.getClump(any(), any(), any())).thenAnswer(invocation -> {
if (parallel && name.equals("dimension")) {
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")) {
assertTrue(regionReady.await(10, TimeUnit.SECONDS));
} else if (parallel && name.equals("region")) {
} else if (parallel && attempts == 1 && name.equals("region")) {
assertTrue(biomeReady.await(10, TimeUnit.SECONDS));
}
RNG rng = invocation.getArgument(1);
samples.add(name + ":" + rng.getSeed());
completed.add(name);
if (name.equals("biome")) {
biomeReady.countDown();
@@ -166,7 +199,10 @@ public class IrisDepositModifierOrderingTest {
}
private void generate() {
new IrisDepositModifier(engine).generateDeposits(output, 0, 0, parallel, context);
try (MockedStatic<B> blocks = mockStatic(B.class, CALLS_REAL_METHODS)) {
blocks.when(() -> B.toDeepSlateOre(any(), any())).thenAnswer(invocation -> invocation.getArgument(1));
new IrisDepositModifier(engine).generateDeposits(output, 0, 0, parallel, context);
}
verify(chunk).release();
}