mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
Merge L5-output
This commit is contained in:
commit
c4dcb32334
33 files changed
+1055
-432
No files matched your search
+5
-7
@@ -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());
|
||||
}
|
||||
}
|
||||
+4
-6
@@ -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;
|
||||
}
|
||||
|
||||
+4
-4
@@ -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());
|
||||
|
||||
+1
-3
@@ -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);
|
||||
|
||||
+48
-32
@@ -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++) {
|
||||
|
||||
+44
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user