Merge L5-output

This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-28 12:19:03 -04:00
commit c4dcb32334
33 files changed
+1055 -432

No files matched your search

@@ -103,20 +103,18 @@ public class FloatingIslandBoundarySamplerTest {
}
@Test
public void parent_cachesResolvedBiomeColumns() {
public void parent_readsOnlyTheQueriedColumnWithoutBuildingTheBoundaryField() {
IrisBiome parent = new IrisBiome();
IrisBiome other = new IrisBiome();
AtomicInteger calls = new AtomicInteger();
FloatingIslandBoundarySampler sampler = new FloatingIslandBoundarySampler((x, z) -> {
calls.incrementAndGet();
return parent;
return x == 12 && z == -9 ? parent : other;
});
assertSame(parent, sampler.parent(12, -9));
int firstReadCount = calls.get();
assertSame(parent, sampler.parent(12, -9));
assertEquals(firstReadCount, calls.get());
int rawWidth = 16 + (FloatingIslandBoundarySampler.EDGE_FADE_RADIUS * 2) + 2;
assertEquals(rawWidth * rawWidth, firstReadCount);
assertSame(other, sampler.parent(13, -9));
assertEquals(2, calls.get());
}
@Test
@@ -0,0 +1,99 @@
package art.arcane.iris.generation.chunk;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import org.bukkit.generator.ChunkGenerator;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ChunkDataHunkHolderTest {
@Test
public void columnTopTracksTheHighestStoredValueAndIgnoresNulls() {
ChunkDataHunkHolder holder = holder(64);
NativeBlockState stone = mock(NativeBlockState.class);
assertEquals(-1, holder.highestStoredY(3, 7));
holder.setRaw(3, 40, 7, null);
assertEquals(-1, holder.highestStoredY(3, 7));
holder.setRaw(3, 12, 7, stone);
holder.setRaw(3, 30, 7, stone);
holder.setRaw(3, 5, 7, stone);
assertEquals(30, holder.highestStoredY(3, 7));
assertEquals(-1, holder.highestStoredY(7, 3));
assertSame(stone, holder.getStoredRaw(3, 30, 7));
assertNull(holder.getStoredRaw(3, 31, 7));
assertEquals(30, ColumnExtent.highestStoredY(new ColumnExtentListeningHunk<>(holder, (x, y, z, state) -> {
}), 3, 7));
}
@Test
public void cellsKeepTheZMajorLayout() {
ChunkDataHunkHolder holder = holder(32);
List<NativeBlockState> states = new ArrayList<>();
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 32; y++) {
NativeBlockState state = mock(NativeBlockState.class);
states.add(state);
holder.setRaw(x, y, z, state);
}
}
}
int index = 0;
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 32; y++) {
assertSame(states.get(index++), holder.getStoredRaw(x, y, z));
}
}
}
}
@Test
public void concurrentWritersNeverLoseTheColumnMaximum() throws InterruptedException {
for (int trial = 0; trial < 20; trial++) {
ChunkDataHunkHolder holder = holder(768);
NativeBlockState stone = mock(NativeBlockState.class);
int threads = 8;
CountDownLatch start = new CountDownLatch(1);
List<Thread> writers = new ArrayList<>();
for (int thread = 0; thread < threads; thread++) {
int offset = thread;
Thread writer = new Thread(() -> {
try {
start.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
for (int y = offset; y < 768; y += threads) {
holder.setRaw(9, y, 4, stone);
}
});
writer.start();
writers.add(writer);
}
start.countDown();
for (Thread writer : writers) {
writer.join();
}
assertEquals(767, holder.highestStoredY(9, 4));
}
}
private static ChunkDataHunkHolder holder(int height) {
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
when(chunk.getMinHeight()).thenReturn(0);
when(chunk.getMaxHeight()).thenReturn(height);
return new ChunkDataHunkHolder(chunk);
}
}
@@ -18,6 +18,7 @@ import org.junit.Test;
import org.mockito.MockedStatic;
import java.lang.reflect.Proxy;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -110,6 +111,48 @@ public class LinkedTerrainChunkTest {
}
}
@Test
public void columnBiomeHunkMatchesTheLinkedChunkView() {
NativeBiome plains = BukkitBiome.of(Biome.PLAINS);
NativeBiome desert = BukkitBiome.of(Biome.DESERT);
NativeBiome alternate = mock(NativeBiome.class);
when(alternate.nativeHandle()).thenReturn(Biome.DESERT);
NativeBiome[] palette = {plains, desert, alternate};
for (int trial = 0; trial < 12; trial++) {
Random random = new Random(9127L + trial);
TerrainChunkBiomeHunkView expected = new TerrainChunkBiomeHunkView(chunk());
ColumnBiomeHunk actual = new ColumnBiomeHunk(16);
int operations = 1 + random.nextInt(80);
for (int operation = 0; operation < operations; operation++) {
NativeBiome biome = palette[random.nextInt(palette.length)];
int x = random.nextInt(16);
int z = random.nextInt(16);
int kind = random.nextInt(trial % 3 == 0 ? 1 : 3);
if (kind == 0) {
expected.set(x, 0, z, x, 15, z, biome);
actual.set(x, 0, z, x, 15, z, biome);
} else if (kind == 1) {
int minimumY = random.nextInt(16);
int maximumY = minimumY + random.nextInt(16 - minimumY);
expected.set(x, minimumY, z, x, maximumY, z, biome);
actual.set(x, minimumY, z, x, maximumY, z, biome);
} else {
int y = random.nextInt(20) - 2;
int rawX = x + (random.nextBoolean() ? 16 : 0);
expected.setRaw(rawX, y, z, biome);
actual.setRaw(rawX, y, z, biome);
}
}
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = -2; y < 18; y++) {
assertSame("biome " + x + "," + y + "," + z, expected.getRaw(x, y, z), actual.getRaw(x, y, z));
}
}
}
}
}
private static LinkedTerrainChunk chunk() {
ChunkData data = mock(ChunkData.class);
when(data.getMinHeight()).thenReturn(-16);
@@ -112,7 +112,7 @@ public class EngineShutdownDrainTest {
doAnswer(invocation -> {
Future<?> continuation = worker.submit(() -> {
try (IrisEngine.GenerationRuntimeScope ignored = fixture.engine.openGenerationRuntimeScope(binding)) {
assertSame(binding, fixture.engine.generationRuntimeScopes.current());
assertSame(binding, fixture.engine.threadState.binding());
assertSame(fixture.runtime, fixture.engine.runtime);
assertTrue(fixture.engine.getClosing().get());
assertThrows(IllegalStateException.class, () -> fixture.engine.requireRunning("start generation"));
@@ -513,16 +513,16 @@ public class EngineShutdownDrainTest {
assembly.mantle = mock(EngineMantle.class);
assembly.ownsMantle = true;
RuntimeAssembly previous = mock(RuntimeAssembly.class);
fixture.engine.runtimeAssembly.set(previous);
fixture.engine.threadState.setAssembly(previous);
IllegalStateException failure = new IllegalStateException("Planner still active");
doAnswer(invocation -> {
assertSame(assembly, fixture.engine.runtimeAssembly.get());
assertSame(assembly, fixture.engine.threadState.assembly());
throw failure;
}).when(assembly.complex).close();
assertSame(failure, fixture.shutdown.closeAssembly(assembly, null));
assertSame(previous, fixture.engine.runtimeAssembly.get());
fixture.engine.runtimeAssembly.remove();
assertSame(previous, fixture.engine.threadState.assembly());
fixture.engine.threadState.setAssembly(null);
assertTrue(fixture.shutdown.retainsData(detachedTarget.getData()));
assertTrue(fixture.shutdown.closeDetachedTarget(detachedTarget, null) instanceof IllegalStateException);
verify(detachedTarget, never()).close();
@@ -531,13 +531,13 @@ public class EngineShutdownDrainTest {
verify(fixture.target, never()).close();
verifyNoInteractions(assembly.mantle);
doAnswer(invocation -> {
assertSame(assembly, fixture.engine.runtimeAssembly.get());
assertSame(assembly, fixture.engine.threadState.assembly());
return null;
}).when(assembly.complex).close();
fixture.shutdown.close();
}
assertNull(fixture.engine.runtimeAssembly.get());
assertNull(fixture.engine.threadState.assembly());
assertFalse(assembly.ownsMantle);
assertFalse(fixture.shutdown.retainsData(detachedTarget.getData()));
verify(assembly.mantle).close();
@@ -851,10 +851,9 @@ public class EngineShutdownDrainTest {
setField(engine, "shutdownSequence", shutdown);
setField(engine, "engineDataStore", mock(EngineDataStore.class));
setField(engine, "lifecycleLock", new Object());
setField(engine, "runtimeAssembly", new ThreadLocal<RuntimeAssembly>());
setField(engine, "threadState", new EngineThreadState());
setField(engine, "detachedGenerationRuntimes", ConcurrentHashMap.newKeySet());
setField(engine, "retiringGenerationRuntimes", ConcurrentHashMap.newKeySet());
setField(engine, "generationRuntimeScopes", new GenerationRuntimeScopeState());
AtomicBoolean closing = new AtomicBoolean();
setField(engine, "closing", closing);
when(engine.getClosing()).thenReturn(closing);
@@ -0,0 +1,109 @@
package art.arcane.iris.generation.runtime;
import art.arcane.iris.generation.runtime.EngineRuntimeBuilder.RuntimeAssembly;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
public class EngineThreadStateTest {
@Test
public void bindingsShareOneFrameAndReleaseItWhenAllAreCleared() {
EngineThreadState state = new EngineThreadState();
RuntimeAssembly assembly = mock(RuntimeAssembly.class);
IrisEngine.GenerationRuntimeBinding binding = new IrisEngine.GenerationRuntimeBinding(
mock(IrisEngine.class), mock(GenerationRuntime.class));
assertNull(state.current());
state.setAssembly(assembly);
IrisEngine.GenerationRuntimeScope scope = state.open(binding);
EngineThreadState.Frame frame = state.current();
assertSame(assembly, frame.assembly);
assertSame(binding, frame.binding);
state.setAssembly(null);
assertSame(frame, state.current());
assertSame(binding, state.binding());
scope.close();
assertNull(state.current());
}
@Test
public void nestedScopesRestoreThePreviousBindingInOrder() {
EngineThreadState state = new EngineThreadState();
IrisEngine.GenerationRuntimeBinding outer = new IrisEngine.GenerationRuntimeBinding(
mock(IrisEngine.class), mock(GenerationRuntime.class));
IrisEngine.GenerationRuntimeBinding inner = new IrisEngine.GenerationRuntimeBinding(
mock(IrisEngine.class), mock(GenerationRuntime.class));
IrisEngine.GenerationRuntimeScope outerScope = state.open(outer);
IrisEngine.GenerationRuntimeScope innerScope = state.open(inner);
assertThrows(IllegalStateException.class, outerScope::close);
innerScope.close();
assertSame(outer, state.binding());
outerScope.close();
assertNull(state.current());
}
@Test
public void threadsSharingATableSlotKeepSeparateFrames() throws InterruptedException {
EngineThreadState state = new EngineThreadState();
IrisEngine.GenerationRuntimeBinding mainBinding = new IrisEngine.GenerationRuntimeBinding(
mock(IrisEngine.class), mock(GenerationRuntime.class));
IrisEngine.GenerationRuntimeBinding otherBinding = new IrisEngine.GenerationRuntimeBinding(
mock(IrisEngine.class), mock(GenerationRuntime.class));
IrisEngine.GenerationRuntimeScope mainScope = state.open(mainBinding);
long slot = Thread.currentThread().threadId() & 255;
AtomicReference<Object> otherSeen = new AtomicReference<>();
AtomicReference<Object> otherAfterClose = new AtomicReference<>(otherBinding);
Thread colliding = null;
while (colliding == null) {
Thread candidate = new Thread(() -> {
IrisEngine.GenerationRuntimeScope scope = state.open(otherBinding);
otherSeen.set(state.binding());
scope.close();
otherAfterClose.set(state.current());
});
if ((candidate.threadId() & 255) == slot) {
colliding = candidate;
}
}
colliding.start();
colliding.join();
assertSame(otherBinding, otherSeen.get());
assertNull(otherAfterClose.get());
assertSame(mainBinding, state.binding());
mainScope.close();
assertNull(state.current());
}
@Test
public void framesAreThreadConfinedAndScopesRejectForeignClose() throws InterruptedException {
EngineThreadState state = new EngineThreadState();
IrisEngine.GenerationRuntimeBinding binding = new IrisEngine.GenerationRuntimeBinding(
mock(IrisEngine.class), mock(GenerationRuntime.class));
IrisEngine.GenerationRuntimeScope scope = state.open(binding);
AtomicReference<Object> seen = new AtomicReference<>(binding);
AtomicReference<Throwable> foreignClose = new AtomicReference<>();
Thread other = new Thread(() -> {
seen.set(state.current());
try {
scope.close();
} catch (Throwable failure) {
foreignClose.set(failure);
}
});
other.start();
other.join();
assertNull(seen.get());
assertSame(IllegalStateException.class, foreignClose.get().getClass());
scope.close();
assertNull(state.current());
}
}
@@ -103,7 +103,7 @@ public class IrisCarveWallPaletteScopeTest {
IrisContext.Scope context = IrisContext.open(fixture.engine, 71L, fixture.context);
MockedStatic<TerrainMatterView> terrain = mockStatic(TerrainMatterView.class)) {
if (mode == Mode.ASSEMBLY) {
fixture.engine.runtimeAssembly.set(fixture.assembly);
fixture.engine.threadState.setAssembly(fixture.assembly);
}
terrain.when(() -> TerrainMatterView.iterate(eq(fixture.chunk), eq(MatterCavern.class), any()))
.thenAnswer(call -> {
@@ -137,7 +137,7 @@ public class IrisCarveWallPaletteScopeTest {
verify(fixture.chunk).release();
assertEquals(1, fixture.dataLookups.get());
} finally {
fixture.engine.runtimeAssembly.remove();
fixture.engine.threadState.setAssembly(null);
}
}
assertSame(fixture.active.data(), fixture.engine.getData());
@@ -199,9 +199,7 @@ public class IrisCarveWallPaletteScopeTest {
active = runtime(mock(IrisData.class), mock(IrisComplex.class), engineMantle);
detached = runtime(mock(IrisData.class), mock(IrisComplex.class), engineMantle);
field(engine, IrisEngine.class, "lifecycleLock", new Object());
field(engine, IrisEngine.class, "runtimeAssembly", new ThreadLocal<EngineRuntimeBuilder.RuntimeAssembly>());
field(engine, IrisEngine.class, "biomeEnvironmentScopes", new ThreadLocal<>());
field(engine, IrisEngine.class, "generationRuntimeScopes", new GenerationRuntimeScopeState());
field(engine, IrisEngine.class, "threadState", new EngineThreadState());
field(engine, IrisEngine.class, "detachedGenerationRuntimes", Collections.newSetFromMap(new IdentityHashMap<>()));
field(engine, IrisEngine.class, "retiringGenerationRuntimes", Collections.newSetFromMap(new IdentityHashMap<>()));
engine.detachedGenerationRuntimes.add(detached);
@@ -255,7 +253,7 @@ public class IrisCarveWallPaletteScopeTest {
private IrisBiome scopedBiome(IrisBiome biome) {
try (IrisEngine.GenerationRuntimeScope ignored = engine.openGenerationRuntimeScope(
new IrisEngine.GenerationRuntimeBinding(engine, active))) {
assertSame(engine.runtimeAssembly.get() == null ? active.data() : assemblyData, engine.getData());
assertSame(engine.threadState.assembly() == null ? active.data() : assemblyData, engine.getData());
}
return biome;
}
@@ -62,7 +62,7 @@ public class IrisEngineEntryHydrologyTest {
doAnswer(invocation -> {
assertSame(engine, IrisContext.require().getEngine());
assertEquals(93L, IrisContext.require().getGenerationSessionId());
assertSame(binding, engine.generationRuntimeScopes.current());
assertSame(binding, engine.threadState.binding());
return null;
}).when(hydrology).prepareChunkColumns(0, 0);
AtomicReference<Callable<Void>> queued = new AtomicReference<>();
@@ -75,7 +75,7 @@ public class IrisEngineEntryHydrologyTest {
verify(hydrology).prepareChunkColumns(0, 0);
verify(lease).close();
assertNull(IrisContext.get());
assertNull(engine.generationRuntimeScopes.current());
assertNull(engine.threadState.binding());
tasks.drainBackgroundTasks("test").requireComplete("test");
}
}
@@ -325,9 +325,9 @@ public class IrisEngineEntryHydrologyTest {
Field closing = IrisEngine.class.getDeclaredField("closing");
closing.setAccessible(true);
closing.set(engine, new AtomicBoolean());
Field scopes = IrisEngine.class.getDeclaredField("generationRuntimeScopes");
Field scopes = IrisEngine.class.getDeclaredField("threadState");
scopes.setAccessible(true);
scopes.set(engine, new GenerationRuntimeScopeState());
scopes.set(engine, new EngineThreadState());
Field lifecycle = IrisEngine.class.getDeclaredField("lifecycleLock");
lifecycle.setAccessible(true);
lifecycle.set(engine, new Object());
@@ -923,9 +923,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
IrisEngine engine = mock(IrisEngine.class, CALLS_REAL_METHODS);
setField(engine, "lifecycleLock", new Object());
setField(engine, "generationHistoryRuntimeRouterLock", new Object());
setField(engine, "runtimeAssembly", new ThreadLocal<EngineRuntimeBuilder.RuntimeAssembly>());
setField(engine, "biomeEnvironmentScopes", new ThreadLocal<Object>());
setField(engine, "generationRuntimeScopes", new GenerationRuntimeScopeState());
setField(engine, "threadState", new EngineThreadState());
Set<GenerationRuntime> detached = Collections.synchronizedSet(
Collections.newSetFromMap(new IdentityHashMap<GenerationRuntime, Boolean>()));
setField(engine, "detachedGenerationRuntimes", detached);
@@ -20,7 +20,6 @@ import art.arcane.volmlib.util.hunk.Hunk;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterBiomeInject;
import org.junit.After;
import org.junit.ClassRule;
import org.junit.Test;
@@ -34,13 +33,13 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyDouble;
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.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -54,19 +53,58 @@ public class IrisBiomeActuatorCoordinateTest {
}
@Test
@SuppressWarnings("unchecked")
public void historicalChunkDoesNotPublishNewPersistentMetadata() {
public void historicalStackChunkDoesNotClearPersistentMetadata() {
Engine engine = mock(Engine.class);
ChunkContext context = mock(ChunkContext.class);
IrisComplex complex = mock(IrisComplex.class);
when(context.hasDimensionStack()).thenReturn(true);
when(context.getComplex()).thenReturn(complex);
when(complex.allowsMantleChunkWrite(0, 0)).thenReturn(false);
IrisBiomeActuator.publishNaturalMetadata(engine, 0, 0, mock(Hunk.class), context);
IrisDimensionStackActuator.clearHostNaturalMetadata(engine, 0, 0, 16, context);
verifyNoMoreInteractions(engine);
}
@Test
public void unstackedChunkNeverTouchesTheMantle() {
Engine engine = mock(Engine.class);
ChunkContext context = mock(ChunkContext.class);
when(context.hasDimensionStack()).thenReturn(false);
IrisDimensionStackActuator.clearHostNaturalMetadata(engine, 0, 0, 16, context);
verifyNoMoreInteractions(engine);
}
@Test
@SuppressWarnings("unchecked")
public void actuatorWritesColumnBiomesWithoutMantleMetadata() {
bindPlatform();
IrisComplex complex = mock(IrisComplex.class);
Engine engine = engine(complex);
IrisBiomeActuator actuator = new IrisBiomeActuator(engine);
Hunk<NativeBiome> output = mock(Hunk.class);
when(output.getWidth()).thenReturn(2);
when(output.getDepth()).thenReturn(2);
when(output.getHeight()).thenReturn(8);
ChunkedDataCache<IrisBiome> biomeCache = currentBiomeCache(engine);
ChunkContext context = mock(ChunkContext.class);
when(context.getComplex()).thenReturn(complex);
when(context.getBiome()).thenReturn(biomeCache);
actuator.onActuate(100, -200, output, false, context);
NativeBiome plains = IrisPlatforms.get().registries().biome("minecraft:plains");
for (int x = 0; x < 2; x++) {
for (int z = 0; z < 2; z++) {
verify(output).set(x, 0, z, x, 7, z, plains);
}
}
verifyNoInteractions(engine.getMantle().getMantle());
verify(IrisPlatforms.get().biomeWriter(), never()).biomeIdFor(anyString());
}
@Test
@SuppressWarnings("unchecked")
public void derivativeScatterUsesEachWorldColumnCoordinate() {
@@ -101,19 +139,6 @@ public class IrisBiomeActuatorCoordinateTest {
assertEquals(List.of("100,-200", "100,-199", "101,-200", "101,-199"), samples);
}
@Test
@SuppressWarnings("unchecked")
public void stackedBiomeReplacementRemovesEveryExistingMarkerWithItsTypedSlice() {
Mantle<Matter> mantle = mock(Mantle.class);
IrisBiomeActuator.clearBiomeMatterRange(mantle, 100, -200, 4, 6);
verify(mantle).remove(100, 4, -200, MatterBiomeInject.class);
verify(mantle).remove(100, 5, -200, MatterBiomeInject.class);
verify(mantle).remove(100, 6, -200, MatterBiomeInject.class);
verifyNoMoreInteractions(mantle);
}
@Test
@SuppressWarnings("unchecked")
public void transitionColumnOverlaysFrozenPhysicalKeyAfterCurrentBiome() {
@@ -143,11 +168,8 @@ public class IrisBiomeActuatorCoordinateTest {
verify(biomeCache).get(0, 0);
verify(IrisPlatforms.get().registries()).biome("iris:old-physical");
verify(IrisPlatforms.get().biomeWriter(), never()).biomeIdFor("iris:old-physical");
Mantle<Matter> mantle = engine.getMantle().getMantle();
verify(mantle).set(eq(100), eq(0), eq(-200), argThat(value -> {
MatterBiomeInject injection = (MatterBiomeInject) value;
return !injection.isCustom() && "iris:old-physical".equals(injection.getBiomeKey());
}));
verify(output, times(2)).set(0, 0, 0, 0, 0, 0, IrisPlatforms.get().registries().biome("iris:old-physical"));
verifyNoInteractions(engine.getMantle().getMantle());
}
@Test
@@ -184,15 +206,9 @@ public class IrisBiomeActuatorCoordinateTest {
verify(transitionPlan, never()).historicalPhysicalBiomeKeyAt(anyInt(), anyInt(), anyInt());
verify(output).set(eq(0), eq(0), eq(0), eq(0), eq(3), eq(0), any(NativeBiome.class));
verify(output).set(eq(0), eq(4), eq(0), eq(0), eq(7), eq(0), any(NativeBiome.class));
Mantle<Matter> mantle = engine.getMantle().getMantle();
verify(mantle).set(eq(100), eq(0), eq(-200), argThat(value -> {
MatterBiomeInject injection = (MatterBiomeInject) value;
return "iris:old-bottom".equals(injection.getBiomeKey());
}));
verify(mantle).set(eq(100), eq(4), eq(-200), argThat(value -> {
MatterBiomeInject injection = (MatterBiomeInject) value;
return "iris:old-top".equals(injection.getBiomeKey());
}));
verify(IrisPlatforms.get().registries()).biome("iris:old-bottom");
verify(IrisPlatforms.get().registries()).biome("iris:old-top");
verifyNoInteractions(engine.getMantle().getMantle());
}
@SuppressWarnings("unchecked")
@@ -12,6 +12,11 @@ import art.arcane.iris.generation.decoration.IrisDecorator;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import art.arcane.volmlib.util.hunk.Hunk;
import art.arcane.iris.generation.block.B;
import art.arcane.iris.generation.chunk.ChunkDataHunkHolder;
import art.arcane.iris.generation.chunk.ColumnExtentListeningHunk;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import org.bukkit.generator.ChunkGenerator;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.matter.Matter;
@@ -91,6 +96,46 @@ public class IrisNaturalCaveScanTest {
verify(fixture.chunk).release();
}
@Test
public void columnExtentScanMatchesFullScanWhenDecorationBuildsAboveTheColumnTop() throws Exception {
IrisPlatforms.bind(mock(IrisPlatform.class));
try {
Fixture full = new Fixture(true);
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())));
Fixture bounded = new Fixture(true);
ChunkGenerator.ChunkData chunkData = mock(ChunkGenerator.ChunkData.class);
doReturn(0).when(chunkData).getMinHeight();
doReturn(32).when(chunkData).getMaxHeight();
ChunkDataHunkHolder holder = new ChunkDataHunkHolder(chunkData);
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())));
assertTrue("fixture must decorate a zone above the initial column top",
full.calls.contains("floor:0:0:27:1"));
assertEquals(full.calls, bounded.calls);
assertEquals(fullWrites, boundedWrites);
assertEquals(full.markers, bounded.markers);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 32; y++) {
NativeBlockState expected = fullOutput.getRaw(x, y, z);
NativeBlockState actual = holder.getStoredRaw(x, y, z);
assertEquals("block " + x + ":" + y + ":" + z,
expected == null ? null : expected.key(), actual == null ? null : actual.key());
}
}
}
} finally {
IrisPlatforms.unbind();
}
}
private static void verifyScan(int width, int depth) throws Exception {
Fixture fixture = new Fixture();
Hunk<NativeBlockState> output = fixture.output(width, depth);
@@ -169,8 +214,12 @@ public class IrisNaturalCaveScanTest {
private final List<String> calls = new ArrayList<>();
private final List<String> markers = new ArrayList<>();
@SuppressWarnings("unchecked")
private Fixture() throws Exception {
this(false);
}
@SuppressWarnings("unchecked")
private Fixture(boolean buildAboveTop) throws Exception {
Engine engine = mock(Engine.class);
EngineMantle engineMantle = mock(EngineMantle.class);
Mantle<Matter> mantle = mock(Mantle.class);
@@ -211,6 +260,9 @@ public class IrisNaturalCaveScanTest {
for (int height = 20; height <= 22; height++) {
output.setRaw(x, height, z, air);
}
if (buildAboveTop) {
output.setRaw(x, 31, z, stone);
}
}
return null;
}).when(surface).decorate(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), eq(biome), any(), anyInt(), anyInt());
@@ -225,6 +277,20 @@ public class IrisNaturalCaveScanTest {
}).when(roof).decorate(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), eq(biome), any(), anyInt(), anyInt());
}
private <H extends Hunk<NativeBlockState>> H sparseOutput(H output) {
for (int x = 0; x < output.getWidth(); x++) {
for (int z = 0; z < output.getDepth(); z++) {
for (int y = 0; y < 32; y++) {
NativeBlockState state = initialState(this, y);
if (state != air) {
output.setRaw(x, y, z, state);
}
}
}
}
return output;
}
private Hunk<NativeBlockState> output(int width, int depth) {
Hunk<NativeBlockState> output = Hunk.newArrayHunk(width, 32, depth);
for (int x = 0; x < width; x++) {
@@ -1,6 +1,8 @@
package art.arcane.iris.generation.stage;
import art.arcane.iris.generation.block.B;
import art.arcane.iris.generation.chunk.ChunkDataHunkHolder;
import art.arcane.iris.generation.chunk.ColumnExtentListeningHunk;
import art.arcane.iris.generation.concurrent.BurstExecutor;
import art.arcane.iris.generation.concurrent.MultiBurst;
import art.arcane.iris.generation.decoration.IrisProceduralBlocks;
@@ -13,6 +15,7 @@ import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import art.arcane.volmlib.util.hunk.Hunk;
import art.arcane.volmlib.util.hunk.storage.ArrayHunk;
import org.bukkit.block.data.BlockData;
import org.bukkit.generator.ChunkGenerator;
import org.junit.Rule;
import org.junit.Test;
@@ -24,6 +27,7 @@ import java.util.Random;
import java.util.concurrent.ExecutorService;
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.anyString;
@@ -55,6 +59,46 @@ public class IrisPerfectionModifierColumnTest {
}
}
@Test
public void columnExtentTopSearchMatchesFullHeightSearch() {
Fixture fixture = new Fixture();
Random random = new Random(55031L);
NativeBlockState[] palette = {null, fixture.air, fixture.water, fixture.stone,
fixture.decorant, fixture.half, fixture.spike(true, false), fixture.spike(false, true)};
for (int trial = 0; trial < 8; trial++) {
ChunkDataHunkHolder bounded = holder(64);
ChunkDataHunkHolder full = holder(64);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int top = random.nextInt(64);
for (int y = 0; y <= top; y++) {
NativeBlockState state = y == 0 ? fixture.stone : palette[random.nextInt(palette.length)];
bounded.setRaw(x, y, z, state);
full.setRaw(x, y, z, state);
}
}
}
fixture.modifier.onModify(0, 0, new ColumnExtentListeningHunk<>(bounded, (x, y, z, state) -> {
}), false, null);
fixture.modifier.onModify(0, 0, full.listen((x, y, z, state) -> {
}), false, null);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 64; y++) {
assertSame("voxel " + x + "," + y + "," + z, full.getStoredRaw(x, y, z), bounded.getStoredRaw(x, y, z));
}
}
}
}
}
private static ChunkDataHunkHolder holder(int height) {
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
when(chunk.getMinHeight()).thenReturn(0);
when(chunk.getMaxHeight()).thenReturn(height);
return new ChunkDataHunkHolder(chunk);
}
@Test
public void unrelatedTallColumnsAreScannedOnceDuringRepeatedCleanup() {
Fixture fixture = new Fixture();