mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 13:30:08 +00:00
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:
1 parent
75353d43c7
commit
420148fcc7
2 files changed
+103
-36
No files matched your search
@@ -50,6 +50,9 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockState> {
|
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;
|
private final RNG rng;
|
||||||
|
|
||||||
public IrisDepositModifier(Engine engine) {
|
public IrisDepositModifier(Engine engine) {
|
||||||
@@ -78,31 +81,47 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
|
|||||||
|
|
||||||
BurstExecutor burst = burst().burst(multicore);
|
BurstExecutor burst = burst().burst(multicore);
|
||||||
long seed = x * 341873128712L + z * 132897987541L;
|
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();
|
MantleChunk chunk = getEngine().getMantle().getMantle().getChunk(x, z).use();
|
||||||
try {
|
try {
|
||||||
try {
|
for (int i = 0; i < generators.size(); i++) {
|
||||||
for (int i = 0; i < generators.size(); i++) {
|
DepositPlan plan = plan(generators.get(i), rng.nextParallelRNG(seed * (i + 1L)));
|
||||||
int index = i;
|
for (int first = 0; first < plan.attempts(); first += CLUMPS_PER_BATCH) {
|
||||||
IrisDepositGenerator generator = generators.get(i);
|
int firstAttempt = first;
|
||||||
RNG generatorRng = rng.nextParallelRNG(seed * (i + 1L));
|
int limit = Math.min(plan.attempts(), first + CLUMPS_PER_BATCH);
|
||||||
|
int index = pending++;
|
||||||
burst.queue(scopedDepositTask(
|
burst.queue(scopedDepositTask(
|
||||||
() -> prepared[index] = prepare(generator, generatorRng, x, z, null, context), context));
|
() -> prepared[index] = prepare(plan, firstAttempt, limit, x, z, null, context), context));
|
||||||
}
|
if (pending == prepared.length) {
|
||||||
} finally {
|
completeBatches(burst, prepared, chunk, terrain, x, z, context);
|
||||||
// complete() must run before release() even when queueing throws — already
|
pending = 0;
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
completeBatches(burst, prepared, chunk, terrain, x, z, context);
|
||||||
} finally {
|
} 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) {
|
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) {
|
private DepositPlan plan(IrisDepositGenerator generator, RNG rng) {
|
||||||
if (k.getSpawnChance() < rng.d()) {
|
if (generator.getSpawnChance() < rng.d()) {
|
||||||
return new PreparedDeposit(k, false, List.of());
|
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();
|
boolean needsCaveBiome = oreDeposit || k.usesCaveBiomeFilter();
|
||||||
IrisDimensionCarvingResolver.State carvingState = needsCaveBiome ? new IrisDimensionCarvingResolver.State() : null;
|
IrisDimensionCarvingResolver.State carvingState = needsCaveBiome ? new IrisDimensionCarvingResolver.State() : null;
|
||||||
|
List<PreparedClump> clumps = new ArrayList<>(limit - first);
|
||||||
int attempts = rng.i(k.getMinPerChunk(), k.getMaxPerChunk() + 1);
|
for (int l = first; l < limit; l++) {
|
||||||
List<PreparedClump> clumps = new ArrayList<>(Math.max(0, attempts));
|
|
||||||
for (int l = 0; l < attempts; l++) {
|
|
||||||
RNG clumpRng = rng.nextParallelRNG(l + 1L);
|
RNG clumpRng = rng.nextParallelRNG(l + 1L);
|
||||||
if (k.getPerClumpSpawnChance() < clumpRng.d()) {
|
if (k.getPerClumpSpawnChance() < clumpRng.d()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -464,6 +492,9 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record DepositPlan(IrisDepositGenerator generator, RNG rng, boolean ore, int attempts) {
|
||||||
|
}
|
||||||
|
|
||||||
private record PreparedDeposit(IrisDepositGenerator generator, boolean ore, List<PreparedClump> clumps) {
|
private record PreparedDeposit(IrisDepositGenerator generator, boolean ore, List<PreparedClump> clumps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+45
-9
@@ -9,6 +9,7 @@ import art.arcane.iris.spi.IrisPlatform;
|
|||||||
import art.arcane.iris.spi.IrisPlatforms;
|
import art.arcane.iris.spi.IrisPlatforms;
|
||||||
import art.arcane.iris.spi.PlatformBlockState;
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
import art.arcane.iris.spi.PlatformRegistries;
|
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.math.IrisBlockVector;
|
||||||
import art.arcane.iris.util.common.parallel.BurstExecutor;
|
import art.arcane.iris.util.common.parallel.BurstExecutor;
|
||||||
import art.arcane.iris.util.common.parallel.MultiBurst;
|
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.MantleChunk;
|
||||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||||
import art.arcane.volmlib.util.matter.Matter;
|
import art.arcane.volmlib.util.matter.Matter;
|
||||||
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
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.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
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.mock;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -49,7 +54,6 @@ public class IrisDepositModifierOrderingTest {
|
|||||||
PlatformRegistries registries = mock(PlatformRegistries.class);
|
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||||
PlatformBlockState stone = state("stone");
|
PlatformBlockState stone = state("stone");
|
||||||
when(registries.block(anyString())).thenReturn(stone);
|
when(registries.block(anyString())).thenReturn(stone);
|
||||||
when(registries.deepSlateOre(any(), any())).thenAnswer(invocation -> invocation.getArgument(1));
|
|
||||||
IrisPlatform platform = mock(IrisPlatform.class);
|
IrisPlatform platform = mock(IrisPlatform.class);
|
||||||
when(platform.registries()).thenReturn(registries);
|
when(platform.registries()).thenReturn(registries);
|
||||||
IrisPlatforms.bind(platform);
|
IrisPlatforms.bind(platform);
|
||||||
@@ -62,7 +66,7 @@ public class IrisDepositModifierOrderingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void reverseWorkerCompletionKeepsDimensionRegionBiomePlacementOrder() throws Exception {
|
public void reverseWorkerCompletionKeepsDimensionRegionBiomePlacementOrder() throws Exception {
|
||||||
try (Fixture fixture = new Fixture(true)) {
|
try (Fixture fixture = new Fixture(true, 1)) {
|
||||||
fixture.generate();
|
fixture.generate();
|
||||||
assertEquals(List.of("biome", "region", "dimension"), fixture.completed);
|
assertEquals(List.of("biome", "region", "dimension"), fixture.completed);
|
||||||
fixture.assertBiomeWins();
|
fixture.assertBiomeWins();
|
||||||
@@ -71,13 +75,35 @@ public class IrisDepositModifierOrderingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void inlinePreparationUsesTheSameOverlappingHostRules() throws Exception {
|
public void inlinePreparationUsesTheSameOverlappingHostRules() throws Exception {
|
||||||
try (Fixture fixture = new Fixture(false)) {
|
try (Fixture fixture = new Fixture(false, 1)) {
|
||||||
fixture.generate();
|
fixture.generate();
|
||||||
assertEquals(List.of("dimension", "region", "biome"), fixture.completed);
|
assertEquals(List.of("dimension", "region", "biome"), fixture.completed);
|
||||||
fixture.assertBiomeWins();
|
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) {
|
private static PlatformBlockState state(String material) {
|
||||||
PlatformBlockState state = mock(PlatformBlockState.class);
|
PlatformBlockState state = mock(PlatformBlockState.class);
|
||||||
when(state.key()).thenReturn("minecraft:" + material);
|
when(state.key()).thenReturn("minecraft:" + material);
|
||||||
@@ -88,6 +114,7 @@ public class IrisDepositModifierOrderingTest {
|
|||||||
private static final class Fixture implements AutoCloseable {
|
private static final class Fixture implements AutoCloseable {
|
||||||
private final ExecutorService executor = Executors.newFixedThreadPool(3);
|
private final ExecutorService executor = Executors.newFixedThreadPool(3);
|
||||||
private final List<String> completed = Collections.synchronizedList(new ArrayList<>());
|
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 biomeReady = new CountDownLatch(1);
|
||||||
private final CountDownLatch regionReady = new CountDownLatch(1);
|
private final CountDownLatch regionReady = new CountDownLatch(1);
|
||||||
private final Engine engine = mock(Engine.class, RETURNS_DEEP_STUBS);
|
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 Hunk<PlatformBlockState> output = Hunk.newArrayHunk(16, 16, 16);
|
||||||
private final MantleChunk<Matter> chunk;
|
private final MantleChunk<Matter> chunk;
|
||||||
private final boolean parallel;
|
private final boolean parallel;
|
||||||
|
private final int attempts;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private Fixture(boolean parallel) {
|
private Fixture(boolean parallel, int attempts) {
|
||||||
this.parallel = parallel;
|
this.parallel = parallel;
|
||||||
|
this.attempts = attempts;
|
||||||
when(engine.getHeight()).thenReturn(16);
|
when(engine.getHeight()).thenReturn(16);
|
||||||
when(engine.getCaveBiome(anyInt(), anyInt(), anyInt(), any())).thenReturn(null);
|
when(engine.getCaveBiome(anyInt(), anyInt(), anyInt(), any())).thenReturn(null);
|
||||||
when(engine.getDimension().getDepositVariants()).thenReturn(new KList<>());
|
when(engine.getDimension().getDepositVariants()).thenReturn(new KList<>());
|
||||||
@@ -134,8 +163,8 @@ public class IrisDepositModifierOrderingTest {
|
|||||||
IrisDepositGenerator generator = mock(IrisDepositGenerator.class);
|
IrisDepositGenerator generator = mock(IrisDepositGenerator.class);
|
||||||
when(generator.getSpawnChance()).thenReturn(1D);
|
when(generator.getSpawnChance()).thenReturn(1D);
|
||||||
when(generator.getPerClumpSpawnChance()).thenReturn(1D);
|
when(generator.getPerClumpSpawnChance()).thenReturn(1D);
|
||||||
when(generator.getMinPerChunk()).thenReturn(1);
|
when(generator.getMinPerChunk()).thenReturn(attempts);
|
||||||
when(generator.getMaxPerChunk()).thenReturn(1);
|
when(generator.getMaxPerChunk()).thenReturn(attempts);
|
||||||
when(generator.getMinHeight()).thenReturn(4);
|
when(generator.getMinHeight()).thenReturn(4);
|
||||||
when(generator.getMaxHeight()).thenReturn(4);
|
when(generator.getMaxHeight()).thenReturn(4);
|
||||||
when(generator.getPlacementScope()).thenReturn(IrisDepositPlacementScope.FULL_HEIGHT);
|
when(generator.getPlacementScope()).thenReturn(IrisDepositPlacementScope.FULL_HEIGHT);
|
||||||
@@ -149,11 +178,15 @@ public class IrisDepositModifierOrderingTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
when(generator.getClump(any(), any(), any())).thenAnswer(invocation -> {
|
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));
|
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));
|
assertTrue(biomeReady.await(10, TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
RNG rng = invocation.getArgument(1);
|
||||||
|
samples.add(name + ":" + rng.getSeed());
|
||||||
completed.add(name);
|
completed.add(name);
|
||||||
if (name.equals("biome")) {
|
if (name.equals("biome")) {
|
||||||
biomeReady.countDown();
|
biomeReady.countDown();
|
||||||
@@ -166,7 +199,10 @@ public class IrisDepositModifierOrderingTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void generate() {
|
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();
|
verify(chunk).release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user