mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
Stabilized deposit overlap order
- Clumps prepare in parallel and place in dimension region biome order - Placement evaluates host rules against preceding deposits - Each clump uses independent deterministic random state - Configured attempt counts are sampled once per chunk - Block traversal reuses cursor coordinates - Tests cover reversed worker completion and generation context cleanup
This commit is contained in:
1 parent
6276bb7327
commit
79690edaaf
3 files changed
+321
-53
No files matched your search
@@ -40,9 +40,14 @@ import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
|||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||||
import art.arcane.iris.util.common.parallel.BurstExecutor;
|
import art.arcane.iris.util.common.parallel.BurstExecutor;
|
||||||
|
import art.arcane.iris.util.common.math.IrisBlockVector;
|
||||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||||
import art.arcane.iris.spi.PlatformBlockState;
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
import art.arcane.iris.util.common.data.B;
|
import art.arcane.iris.util.common.data.B;
|
||||||
|
import art.arcane.iris.util.common.data.VectorMap;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockState> {
|
public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockState> {
|
||||||
private final RNG rng;
|
private final RNG rng;
|
||||||
@@ -62,37 +67,42 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
|
|||||||
public void generateDeposits(Hunk<PlatformBlockState> terrain, int x, int z, boolean multicore, ChunkContext context) {
|
public void generateDeposits(Hunk<PlatformBlockState> terrain, int x, int z, boolean multicore, ChunkContext context) {
|
||||||
IrisRegion region = context.getRegion().get(7, 7);
|
IrisRegion region = context.getRegion().get(7, 7);
|
||||||
IrisBiome biome = context.getBiome().get(7, 7);
|
IrisBiome biome = context.getBiome().get(7, 7);
|
||||||
BurstExecutor burst = burst().burst(multicore);
|
List<IrisDepositGenerator> generators = new ArrayList<>(
|
||||||
|
getDimension().getDeposits().size() + region.getDeposits().size() + biome.getDeposits().size());
|
||||||
|
generators.addAll(getDimension().getDeposits());
|
||||||
|
generators.addAll(region.getDeposits());
|
||||||
|
generators.addAll(biome.getDeposits());
|
||||||
|
if (generators.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BurstExecutor burst = burst().burst(multicore);
|
||||||
long seed = x * 341873128712L + z * 132897987541L;
|
long seed = x * 341873128712L + z * 132897987541L;
|
||||||
long mask = 0;
|
PreparedDeposit[] prepared = new PreparedDeposit[generators.size()];
|
||||||
MantleChunk chunk = getEngine().getMantle().getMantle().getChunk(x, z).use();
|
MantleChunk chunk = getEngine().getMantle().getMantle().getChunk(x, z).use();
|
||||||
try {
|
try {
|
||||||
for (IrisDepositGenerator k : getDimension().getDeposits()) {
|
try {
|
||||||
long finalSeed = seed * ++mask;
|
for (int i = 0; i < generators.size(); i++) {
|
||||||
burst.queue(scopedDepositTask(
|
int index = i;
|
||||||
() -> generate(k, chunk, terrain, rng.nextParallelRNG(finalSeed), x, z, false, context), context));
|
IrisDepositGenerator generator = generators.get(i);
|
||||||
|
RNG generatorRng = rng.nextParallelRNG(seed * (i + 1L));
|
||||||
|
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 (IrisDepositGenerator k : region.getDeposits()) {
|
for (PreparedDeposit deposit : prepared) {
|
||||||
long finalSeed = seed * ++mask;
|
if (deposit != null) {
|
||||||
burst.queue(scopedDepositTask(
|
place(deposit, chunk, terrain, x, z, null, context);
|
||||||
() -> generate(k, chunk, terrain, rng.nextParallelRNG(finalSeed), x, z, false, context), context));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (IrisDepositGenerator k : biome.getDeposits()) {
|
|
||||||
long finalSeed = seed * ++mask;
|
|
||||||
burst.queue(scopedDepositTask(
|
|
||||||
() -> generate(k, chunk, terrain, rng.nextParallelRNG(finalSeed), x, z, false, context), context));
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
// complete() must run before release() even when queueing throws — already
|
chunk.release();
|
||||||
// submitted burst tasks must never write into a released chunk.
|
|
||||||
try {
|
|
||||||
burst.complete();
|
|
||||||
} finally {
|
|
||||||
chunk.release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,17 +125,27 @@ 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) {
|
||||||
IrisDimensionCarvingResolver.State carvingState = new IrisDimensionCarvingResolver.State();
|
place(prepare(k, rng, cx, cz, he, context), chunk, data, cx, cz, he, context);
|
||||||
if (k.getSpawnChance() < rng.d())
|
}
|
||||||
return;
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
boolean oreDeposit = k.isOre(getData());
|
boolean oreDeposit = k.isOre(getData());
|
||||||
|
boolean needsCaveBiome = oreDeposit || k.usesCaveBiomeFilter();
|
||||||
|
IrisDimensionCarvingResolver.State carvingState = needsCaveBiome ? new IrisDimensionCarvingResolver.State() : null;
|
||||||
|
|
||||||
for (int l = 0; l < rng.i(k.getMinPerChunk(), k.getMaxPerChunk() + 1); l++) {
|
int attempts = rng.i(k.getMinPerChunk(), k.getMaxPerChunk() + 1);
|
||||||
if (k.getPerClumpSpawnChance() < rng.d())
|
List<PreparedClump> clumps = new ArrayList<>(Math.max(0, attempts));
|
||||||
|
for (int l = 0; l < attempts; l++) {
|
||||||
|
RNG clumpRng = rng.nextParallelRNG(l + 1L);
|
||||||
|
if (k.getPerClumpSpawnChance() < clumpRng.d()) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
IrisObject clump = k.getClump(getEngine(), rng, getData());
|
IrisObject clump = k.getClump(getEngine(), clumpRng, getData());
|
||||||
|
|
||||||
int dim = clump.getW();
|
int dim = clump.getW();
|
||||||
int min = dim / 2;
|
int min = dim / 2;
|
||||||
@@ -136,8 +156,8 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
|
|||||||
max = 9;
|
max = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
int x = rng.i(min, max + 1);
|
int x = clumpRng.i(min, max + 1);
|
||||||
int z = rng.i(min, max + 1);
|
int z = clumpRng.i(min, max + 1);
|
||||||
int terrainSurface = getDepositTerrainSurface(cx, cz, x, z, he, context);
|
int terrainSurface = getDepositTerrainSurface(cx, cz, x, z, he, context);
|
||||||
int height = k.getPlacementScope() == IrisDepositPlacementScope.TERRAIN
|
int height = k.getPlacementScope() == IrisDepositPlacementScope.TERRAIN
|
||||||
? depositSurfaceLimit(terrainSurface, k.getSurfaceClearance())
|
? depositSurfaceLimit(terrainSurface, k.getSurfaceClearance())
|
||||||
@@ -148,19 +168,20 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
|
|||||||
}
|
}
|
||||||
|
|
||||||
int y = sampleHeight(
|
int y = sampleHeight(
|
||||||
k.getHeightDistribution(), rng, k.getMinHeight(), k.getMaxHeight(),
|
k.getHeightDistribution(), clumpRng, k.getMinHeight(), k.getMaxHeight(),
|
||||||
Math.min(height, getEngine().getHeight() - 1));
|
Math.min(height, getEngine().getHeight() - 1));
|
||||||
if (y == Integer.MIN_VALUE) {
|
if (y == Integer.MIN_VALUE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean clippedHeight = k.getHeightDistribution() == IrisDepositHeightDistribution.CLIPPED_UNIFORM;
|
boolean clippedHeight = k.getHeightDistribution() == IrisDepositHeightDistribution.CLIPPED_UNIFORM;
|
||||||
if (clippedHeight && y > height - 2)
|
if (clippedHeight && y > height - 2) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
int biomeY = Math.max(0, Math.min(getEngine().getHeight() - 1, y));
|
int biomeY = Math.max(0, Math.min(getEngine().getHeight() - 1, y));
|
||||||
IrisBiome surfaceBiome = context.getBiome().get(x, z);
|
IrisBiome surfaceBiome = context.getBiome().get(x, z);
|
||||||
IrisBiome depositBiome = oreDeposit || k.usesCaveBiomeFilter()
|
IrisBiome depositBiome = needsCaveBiome
|
||||||
? getEngine().getCaveBiome(
|
? getEngine().getCaveBiome(
|
||||||
(cx << 4) + x, biomeY, (cz << 4) + z, carvingState)
|
(cx << 4) + x, biomeY, (cz << 4) + z, carvingState)
|
||||||
: null;
|
: null;
|
||||||
@@ -172,13 +193,13 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
|
|||||||
if (depositBiome != null) {
|
if (depositBiome != null) {
|
||||||
double frequencyMultiplier = depositBiome.getOreDepositFrequencyMultiplier();
|
double frequencyMultiplier = depositBiome.getOreDepositFrequencyMultiplier();
|
||||||
if (frequencyMultiplier < 1D
|
if (frequencyMultiplier < 1D
|
||||||
&& !passesOreFrequency(frequencyMultiplier, rng.d())) {
|
&& !passesOreFrequency(frequencyMultiplier, clumpRng.d())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
double sizeMultiplier = depositBiome.getOreDepositSizeMultiplier();
|
double sizeMultiplier = depositBiome.getOreDepositSizeMultiplier();
|
||||||
if (sizeMultiplier != 1D) {
|
if (sizeMultiplier != 1D) {
|
||||||
IrisObject scaledClump = k.getClump(getEngine(), rng, getData(), sizeMultiplier);
|
IrisObject scaledClump = k.getClump(getEngine(), clumpRng, getData(), sizeMultiplier);
|
||||||
int scaledDimension = scaledClump.getW();
|
int scaledDimension = scaledClump.getW();
|
||||||
x = clampDepositCenter(x, scaledDimension, 16);
|
x = clampDepositCenter(x, scaledDimension, 16);
|
||||||
if (clippedHeight) {
|
if (clippedHeight) {
|
||||||
@@ -190,9 +211,30 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IrisDimension dimension = getDimension();
|
clumps.add(new PreparedClump(clump, x, y, z, clumpRng));
|
||||||
|
}
|
||||||
|
return new PreparedDeposit(k, oreDeposit, clumps);
|
||||||
|
}
|
||||||
|
|
||||||
for (art.arcane.iris.util.common.math.IrisBlockVector j : clump.getBlocks().keys()) {
|
private void place(PreparedDeposit deposit, MantleChunk chunk, Hunk<PlatformBlockState> data,
|
||||||
|
int cx, int cz, HeightMap he, ChunkContext context) {
|
||||||
|
if (deposit.clumps().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IrisDepositGenerator k = deposit.generator();
|
||||||
|
boolean oreDeposit = deposit.ore();
|
||||||
|
IrisDimensionCarvingResolver.State carvingState = new IrisDimensionCarvingResolver.State();
|
||||||
|
IrisDimension dimension = getDimension();
|
||||||
|
for (PreparedClump prepared : deposit.clumps()) {
|
||||||
|
IrisObject clump = prepared.object();
|
||||||
|
int x = prepared.x();
|
||||||
|
int y = prepared.y();
|
||||||
|
int z = prepared.z();
|
||||||
|
RNG rng = prepared.rng();
|
||||||
|
|
||||||
|
VectorMap<PlatformBlockState>.Cursor cursor = clump.getBlocks().cursor();
|
||||||
|
while (cursor.next()) {
|
||||||
|
IrisBlockVector j = cursor.key();
|
||||||
int nx = j.getBlockX() + x;
|
int nx = j.getBlockX() + x;
|
||||||
int ny = j.getBlockY() + y;
|
int ny = j.getBlockY() + y;
|
||||||
int nz = j.getBlockZ() + z;
|
int nz = j.getBlockZ() + z;
|
||||||
@@ -236,7 +278,7 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (chunk.get(nx, ny, nz, MatterCavern.class) == null) {
|
if (chunk.get(nx, ny, nz, MatterCavern.class) == null) {
|
||||||
PlatformBlockState ore = clump.getBlocks().get(j);
|
PlatformBlockState ore = cursor.value();
|
||||||
PlatformBlockState remapped = resolveDepositVariant(
|
PlatformBlockState remapped = resolveDepositVariant(
|
||||||
cx, cz, nx, ny, nz, ore, dimension, context, carvingState);
|
cx, cz, nx, ny, nz, ore, dimension, context, carvingState);
|
||||||
PlatformBlockState finalBlock = remapped != null
|
PlatformBlockState finalBlock = remapped != null
|
||||||
@@ -421,4 +463,10 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record PreparedDeposit(IrisDepositGenerator generator, boolean ore, List<PreparedClump> clumps) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record PreparedClump(IrisObject object, int x, int y, int z, RNG rng) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+46
-13
@@ -3,6 +3,9 @@ package art.arcane.iris.engine.modifier;
|
|||||||
import art.arcane.iris.engine.IrisEngine;
|
import art.arcane.iris.engine.IrisEngine;
|
||||||
import art.arcane.iris.engine.framework.Engine;
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
import art.arcane.iris.engine.object.IrisDepositGenerator;
|
import art.arcane.iris.engine.object.IrisDepositGenerator;
|
||||||
|
import art.arcane.iris.engine.object.IrisDepositHeightDistribution;
|
||||||
|
import art.arcane.iris.engine.object.IrisDepositPlacementScope;
|
||||||
|
import art.arcane.iris.engine.object.IrisObject;
|
||||||
import art.arcane.iris.spi.IrisPlatform;
|
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;
|
||||||
@@ -16,6 +19,7 @@ import art.arcane.volmlib.util.collection.KList;
|
|||||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||||
import art.arcane.volmlib.util.matter.Matter;
|
import art.arcane.volmlib.util.matter.Matter;
|
||||||
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
@@ -33,14 +37,12 @@ import static org.junit.Assert.assertSame;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
import static org.mockito.ArgumentMatchers.same;
|
|
||||||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.spy;
|
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -90,6 +92,26 @@ public class IrisDepositModifierContextTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void failedClumpChanceDoesNotResampleTheConfiguredAttemptCount() {
|
||||||
|
Engine engine = mock(Engine.class, RETURNS_DEEP_STUBS);
|
||||||
|
IrisDepositGenerator generator = mock(IrisDepositGenerator.class);
|
||||||
|
when(generator.getSpawnChance()).thenReturn(1D);
|
||||||
|
when(generator.getMinPerChunk()).thenReturn(0);
|
||||||
|
when(generator.getMaxPerChunk()).thenReturn(4);
|
||||||
|
when(generator.getPerClumpSpawnChance()).thenReturn(0D);
|
||||||
|
RNG rng = mock(RNG.class);
|
||||||
|
when(rng.d()).thenReturn(0.5D);
|
||||||
|
when(rng.i(0, 5)).thenReturn(4, 0);
|
||||||
|
when(rng.nextParallelRNG(anyLong())).thenReturn(rng);
|
||||||
|
|
||||||
|
new IrisDepositModifier(engine).generate(generator, null, null, rng, 0, 0, false, null);
|
||||||
|
|
||||||
|
verify(generator, times(4)).getPerClumpSpawnChance();
|
||||||
|
verify(rng, times(1)).i(0, 5);
|
||||||
|
verify(generator, never()).getClump(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
private static final class Fixture implements AutoCloseable {
|
private static final class Fixture implements AutoCloseable {
|
||||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
private final Engine engine;
|
private final Engine engine;
|
||||||
@@ -138,16 +160,27 @@ public class IrisDepositModifierContextTest {
|
|||||||
return null;
|
return null;
|
||||||
}).when(scope).close();
|
}).when(scope).close();
|
||||||
}
|
}
|
||||||
modifier = spy(new IrisDepositModifier(engine));
|
when(engine.getHeight()).thenReturn(16);
|
||||||
doAnswer(invocation -> {
|
IrisObject clump = new IrisObject(1, 1, 1);
|
||||||
assertNotSame(caller, Thread.currentThread());
|
for (IrisDepositGenerator generator : new IrisDepositGenerator[]{dimensionDeposit, regionDeposit, biomeDeposit}) {
|
||||||
assertEquals(scoped, workerRuntime.get());
|
when(generator.getSpawnChance()).thenReturn(1D);
|
||||||
assertSame(engine, IrisContext.require().getEngine());
|
when(generator.getMinPerChunk()).thenReturn(1);
|
||||||
assertSame(context, IrisContext.require().getChunkContext());
|
when(generator.getMaxPerChunk()).thenReturn(1);
|
||||||
assertEquals(71L, IrisContext.require().getGenerationSessionId());
|
when(generator.getPerClumpSpawnChance()).thenReturn(1D);
|
||||||
completed.incrementAndGet();
|
when(generator.getPlacementScope()).thenReturn(IrisDepositPlacementScope.FULL_HEIGHT);
|
||||||
return null;
|
when(generator.getHeightDistribution()).thenReturn(IrisDepositHeightDistribution.UNIFORM);
|
||||||
}).when(modifier).generate(any(), same(chunk), any(), any(), eq(2), eq(-3), eq(false), same(context));
|
when(generator.matchesBiome(any(), any())).thenReturn(true);
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
assertNotSame(caller, Thread.currentThread());
|
||||||
|
assertEquals(scoped, workerRuntime.get());
|
||||||
|
assertSame(engine, IrisContext.require().getEngine());
|
||||||
|
assertSame(context, IrisContext.require().getChunkContext());
|
||||||
|
assertEquals(71L, IrisContext.require().getGenerationSessionId());
|
||||||
|
completed.incrementAndGet();
|
||||||
|
return clump;
|
||||||
|
}).when(generator).getClump(any(), any(), any());
|
||||||
|
}
|
||||||
|
modifier = new IrisDepositModifier(engine);
|
||||||
doAnswer(invocation -> {
|
doAnswer(invocation -> {
|
||||||
assertEquals(3, completed.get());
|
assertEquals(3, completed.get());
|
||||||
assertEquals(scoped ? 3 : 0, closedScopes.get());
|
assertEquals(scoped ? 3 : 0, closedScopes.get());
|
||||||
|
|||||||
+187
@@ -0,0 +1,187 @@
|
|||||||
|
package art.arcane.iris.engine.modifier;
|
||||||
|
|
||||||
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.object.IrisDepositGenerator;
|
||||||
|
import art.arcane.iris.engine.object.IrisDepositHeightDistribution;
|
||||||
|
import art.arcane.iris.engine.object.IrisDepositPlacementScope;
|
||||||
|
import art.arcane.iris.engine.object.IrisObject;
|
||||||
|
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.math.IrisBlockVector;
|
||||||
|
import art.arcane.iris.util.common.parallel.BurstExecutor;
|
||||||
|
import art.arcane.iris.util.common.parallel.MultiBurst;
|
||||||
|
import art.arcane.iris.util.project.context.ChunkContext;
|
||||||
|
import art.arcane.iris.util.project.hunk.Hunk;
|
||||||
|
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 org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
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.Mockito.RETURNS_DEEP_STUBS;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class IrisDepositModifierOrderingTest {
|
||||||
|
@Before
|
||||||
|
public void bindPlatform() {
|
||||||
|
IrisPlatforms.unbind();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void unbindPlatform() {
|
||||||
|
IrisPlatforms.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void reverseWorkerCompletionKeepsDimensionRegionBiomePlacementOrder() throws Exception {
|
||||||
|
try (Fixture fixture = new Fixture(true)) {
|
||||||
|
fixture.generate();
|
||||||
|
assertEquals(List.of("biome", "region", "dimension"), fixture.completed);
|
||||||
|
fixture.assertBiomeWins();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void inlinePreparationUsesTheSameOverlappingHostRules() throws Exception {
|
||||||
|
try (Fixture fixture = new Fixture(false)) {
|
||||||
|
fixture.generate();
|
||||||
|
assertEquals(List.of("dimension", "region", "biome"), fixture.completed);
|
||||||
|
fixture.assertBiomeWins();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PlatformBlockState state(String material) {
|
||||||
|
PlatformBlockState state = mock(PlatformBlockState.class);
|
||||||
|
when(state.key()).thenReturn("minecraft:" + material);
|
||||||
|
when(state.materialKey()).thenReturn("minecraft:" + material);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 CountDownLatch biomeReady = new CountDownLatch(1);
|
||||||
|
private final CountDownLatch regionReady = new CountDownLatch(1);
|
||||||
|
private final Engine engine = mock(Engine.class, RETURNS_DEEP_STUBS);
|
||||||
|
private final ChunkContext context = mock(ChunkContext.class, RETURNS_DEEP_STUBS);
|
||||||
|
private final PlatformBlockState stone = state("stone");
|
||||||
|
private final PlatformBlockState granite = state("granite");
|
||||||
|
private final PlatformBlockState diorite = state("diorite");
|
||||||
|
private final PlatformBlockState diamond = state("diamond_ore");
|
||||||
|
private final Hunk<PlatformBlockState> output = Hunk.newArrayHunk(16, 16, 16);
|
||||||
|
private final MantleChunk<Matter> chunk;
|
||||||
|
private final boolean parallel;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Fixture(boolean parallel) {
|
||||||
|
this.parallel = parallel;
|
||||||
|
when(engine.getHeight()).thenReturn(16);
|
||||||
|
when(engine.getCaveBiome(anyInt(), anyInt(), anyInt(), any())).thenReturn(null);
|
||||||
|
when(engine.getDimension().getDepositVariants()).thenReturn(new KList<>());
|
||||||
|
when(context.getRegion().get(anyInt(), anyInt()).getDepositVariants()).thenReturn(new KList<>());
|
||||||
|
when(context.getGenerationSessionId()).thenReturn(11L);
|
||||||
|
chunk = mock(MantleChunk.class);
|
||||||
|
Mantle<Matter> mantle = engine.getMantle().getMantle();
|
||||||
|
doReturn(chunk).when(mantle).getChunk(0, 0);
|
||||||
|
when(chunk.use()).thenReturn(chunk);
|
||||||
|
MultiBurst pool = mock(MultiBurst.class);
|
||||||
|
BurstExecutor burst = new BurstExecutor(executor, 3);
|
||||||
|
burst.setMulticore(parallel);
|
||||||
|
when(engine.burst()).thenReturn(pool);
|
||||||
|
when(pool.burst(parallel)).thenReturn(burst);
|
||||||
|
IrisDepositGenerator dimensionDeposit = generator("dimension", stone, granite);
|
||||||
|
IrisDepositGenerator regionDeposit = generator("region", granite, diorite);
|
||||||
|
IrisDepositGenerator biomeDeposit = generator("biome", diorite, diamond);
|
||||||
|
when(engine.getDimension().getDeposits()).thenReturn(new KList<>(dimensionDeposit));
|
||||||
|
when(context.getRegion().get(7, 7).getDeposits()).thenReturn(new KList<>(regionDeposit));
|
||||||
|
when(context.getBiome().get(7, 7).getDeposits()).thenReturn(new KList<>(biomeDeposit));
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
output.set(x, 4, z, stone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IrisDepositGenerator generator(String name, PlatformBlockState requiredHost, PlatformBlockState replacement) {
|
||||||
|
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.getMinHeight()).thenReturn(4);
|
||||||
|
when(generator.getMaxHeight()).thenReturn(4);
|
||||||
|
when(generator.getPlacementScope()).thenReturn(IrisDepositPlacementScope.FULL_HEIGHT);
|
||||||
|
when(generator.getHeightDistribution()).thenReturn(IrisDepositHeightDistribution.UNIFORM);
|
||||||
|
when(generator.matchesBiome(any(), any())).thenReturn(true);
|
||||||
|
when(generator.canReplace(requiredHost)).thenReturn(true);
|
||||||
|
IrisObject clump = new IrisObject(33, 1, 33);
|
||||||
|
for (int x = -16; x <= 16; x++) {
|
||||||
|
for (int z = -16; z <= 16; z++) {
|
||||||
|
clump.getBlocks().put(new IrisBlockVector(x, 0, z), replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when(generator.getClump(any(), any(), any())).thenAnswer(invocation -> {
|
||||||
|
if (parallel && name.equals("dimension")) {
|
||||||
|
assertTrue(regionReady.await(10, TimeUnit.SECONDS));
|
||||||
|
} else if (parallel && name.equals("region")) {
|
||||||
|
assertTrue(biomeReady.await(10, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
completed.add(name);
|
||||||
|
if (name.equals("biome")) {
|
||||||
|
biomeReady.countDown();
|
||||||
|
} else if (name.equals("region")) {
|
||||||
|
regionReady.countDown();
|
||||||
|
}
|
||||||
|
return clump;
|
||||||
|
});
|
||||||
|
return generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generate() {
|
||||||
|
new IrisDepositModifier(engine).generateDeposits(output, 0, 0, parallel, context);
|
||||||
|
verify(chunk).release();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertBiomeWins() {
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
assertSame("Deposit collision at " + x + ",4," + z, diamond, output.get(x, 4, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
executor.shutdownNow();
|
||||||
|
assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user