mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-22 16:17:44 +00:00
Plain chunk output buffer
- ChunkDataHunkHolder stores cells in a plain array and tracks each column's highest stored y - natural cave decoration and perfection stop scanning above the column top
This commit is contained in:
1 parent
8b5c109d55
commit
bc618e20ec
9 files changed
+331
-10
No files matched your search
@@ -23,19 +23,35 @@ import art.arcane.volmlib.nativelib.terrain.NativeBlockVolume;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.iris.generation.block.BoundBlockState;
|
||||
import art.arcane.iris.generation.block.IrisCustomData;
|
||||
import art.arcane.volmlib.util.hunk.storage.AtomicHunk;
|
||||
import art.arcane.volmlib.util.hunk.storage.StorageHunk;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.generator.ChunkGenerator.ChunkData;
|
||||
|
||||
@SuppressWarnings("ClassCanBeRecord")
|
||||
public class ChunkDataHunkHolder extends AtomicHunk<NativeBlockState> implements NativeBlockVolume {
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Plain-array block buffer for one chunk. Stages write disjoint columns when they fan out and every hand-off
|
||||
* between threads is a join, so the cells need no per-access fences. Each column tracks its highest stored y;
|
||||
* a raise is a CAS so concurrent writers to one column can never lose the maximum.
|
||||
*/
|
||||
public class ChunkDataHunkHolder extends StorageHunk<NativeBlockState> implements NativeBlockVolume, ColumnExtent {
|
||||
private static final BoundBlockState AIR = BoundBlockState.of("AIR");
|
||||
private static final VarHandle COLUMN_TOPS = MethodHandles.arrayElementVarHandle(int[].class);
|
||||
|
||||
private final ChunkData chunk;
|
||||
private final NativeBlockState[] data;
|
||||
private final int[] columnTops;
|
||||
private final int zStride;
|
||||
|
||||
public ChunkDataHunkHolder(ChunkData chunk) {
|
||||
super(16, chunk.getMaxHeight() - chunk.getMinHeight(), 16);
|
||||
this.chunk = chunk;
|
||||
this.zStride = 16 * getHeight();
|
||||
this.data = new NativeBlockState[zStride * 16];
|
||||
this.columnTops = new int[256];
|
||||
Arrays.fill(columnTops, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,7 +72,16 @@ public class ChunkDataHunkHolder extends AtomicHunk<NativeBlockState> implements
|
||||
if (t == null) {
|
||||
return;
|
||||
}
|
||||
super.setRaw(x, y, z, t);
|
||||
data[index(x, y, z)] = t;
|
||||
int column = (z << 4) | x;
|
||||
int top = (int) COLUMN_TOPS.getOpaque(columnTops, column);
|
||||
while (y > top) {
|
||||
int witness = (int) COLUMN_TOPS.compareAndExchange(columnTops, column, top, y);
|
||||
if (witness == top) {
|
||||
break;
|
||||
}
|
||||
top = witness;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,13 +90,23 @@ public class ChunkDataHunkHolder extends AtomicHunk<NativeBlockState> implements
|
||||
return AIR.get();
|
||||
}
|
||||
|
||||
NativeBlockState b = super.getRaw(x, y, z);
|
||||
NativeBlockState b = data[index(x, y, z)];
|
||||
|
||||
return b != null ? b : AIR.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NativeBlockState getStoredRaw(int x, int y, int z) {
|
||||
return super.getRaw(x, y, z);
|
||||
return data[index(x, y, z)];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int highestStoredY(int x, int z) {
|
||||
return (int) COLUMN_TOPS.getOpaque(columnTops, (z << 4) | x);
|
||||
}
|
||||
|
||||
private int index(int x, int y, int z) {
|
||||
return (z * zStride) + (y << 4) + x;
|
||||
}
|
||||
|
||||
public void apply() {
|
||||
@@ -90,7 +125,7 @@ public class ChunkDataHunkHolder extends AtomicHunk<NativeBlockState> implements
|
||||
int runStart = -1;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
NativeBlockState state = super.getRaw(x, y, z);
|
||||
NativeBlockState state = getStoredRaw(x, y, z);
|
||||
BlockData block = state == null ? null : (BlockData) state.nativeHandle();
|
||||
// Custom wrappers are not real Bukkit data; write the vanilla base like the
|
||||
// NMS fast path (NMSBinding.applyChunkDataBlocks) does.
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2026 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.generation.chunk;
|
||||
|
||||
import art.arcane.volmlib.util.hunk.Hunk;
|
||||
|
||||
/**
|
||||
* A block volume that knows, per column, the highest y it holds a value for. Every cell above that y reads as
|
||||
* unwritten, so column scans can stop there without changing what they observe.
|
||||
*/
|
||||
public interface ColumnExtent {
|
||||
int highestStoredY(int x, int z);
|
||||
|
||||
static int highestStoredY(Hunk<?> hunk, int x, int z) {
|
||||
return hunk instanceof ColumnExtent extent ? extent.highestStoredY(x, z) : hunk.getHeight() - 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2026 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.generation.chunk;
|
||||
|
||||
import art.arcane.volmlib.util.function.Consumer4;
|
||||
import art.arcane.volmlib.util.hunk.Hunk;
|
||||
import art.arcane.volmlib.util.hunk.view.ListeningHunk;
|
||||
|
||||
public final class ColumnExtentListeningHunk<T> extends ListeningHunk<T> implements ColumnExtent {
|
||||
private final Hunk<T> source;
|
||||
|
||||
public ColumnExtentListeningHunk(Hunk<T> source, Consumer4<Integer, Integer, Integer, T> listener) {
|
||||
super(source, listener);
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int highestStoredY(int x, int z) {
|
||||
return ColumnExtent.highestStoredY(source, x, z);
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.volmlib.util.atomics.AtomicRollingSequence;
|
||||
import art.arcane.iris.generation.context.ChunkContext;
|
||||
import art.arcane.iris.generation.context.IrisContext;
|
||||
import art.arcane.iris.generation.chunk.ColumnExtentListeningHunk;
|
||||
import art.arcane.volmlib.util.documentation.BlockCoordinates;
|
||||
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||
import art.arcane.volmlib.util.hunk.Hunk;
|
||||
@@ -1465,7 +1466,7 @@ public class IrisEngine implements Engine {
|
||||
IrisContext.Scope generationScope = IrisContext.open(this, lease.sessionId(), null)) {
|
||||
getEngineData().getStatistics().generatedChunk();
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
Hunk<NativeBlockState> blocks = vblocks.listen((xx, y, zz, t) -> catchBlockUpdates(x + xx, y, z + zz, t));
|
||||
Hunk<NativeBlockState> blocks = new ColumnExtentListeningHunk<>(vblocks, (xx, y, zz, t) -> catchBlockUpdates(x + xx, y, z + zz, t));
|
||||
|
||||
if (getDimension().isDebugChunkCrossSections() && ((x >> 4) % getDimension().getDebugCrossSectionsMod() == 0 || (z >> 4) % getDimension().getDebugCrossSectionsMod() == 0)) {
|
||||
NativeBlockState crossSection = B.getState("CRYING_OBSIDIAN");
|
||||
|
||||
@@ -40,6 +40,7 @@ import art.arcane.iris.generation.decoration.IrisProceduralBlocks;
|
||||
import art.arcane.iris.generation.hydrology.IrisRiverMaterialConfig;
|
||||
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveAction;
|
||||
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveCell;
|
||||
import art.arcane.iris.generation.chunk.ColumnExtent;
|
||||
import art.arcane.iris.generation.context.ChunkContext;
|
||||
import art.arcane.iris.generation.context.IrisContext;
|
||||
import art.arcane.iris.generation.block.B;
|
||||
@@ -896,12 +897,16 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
int width = output.getWidth();
|
||||
int depth = output.getDepth();
|
||||
int height = output.getHeight();
|
||||
ColumnExtent extent = output instanceof ColumnExtent columnExtent ? columnExtent : null;
|
||||
for (int localX = 0; localX < width; localX++) {
|
||||
for (int localZ = 0; localZ < depth; localZ++) {
|
||||
int worldX = blockX + localX;
|
||||
int worldZ = blockZ + localZ;
|
||||
int floor = -1;
|
||||
for (int y = 1; y < height; y++) {
|
||||
if (extent != null && y > extent.highestStoredY(localX, localZ)) {
|
||||
break;
|
||||
}
|
||||
NativeBlockState state = output.getRaw(localX, y, localZ);
|
||||
if (B.isSolid(state)) {
|
||||
if (floor >= 0) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
package art.arcane.iris.generation.stage;
|
||||
|
||||
import art.arcane.iris.generation.chunk.ColumnExtent;
|
||||
import art.arcane.iris.generation.decoration.IrisSpeleothems;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.EngineAssignedModifier;
|
||||
@@ -203,7 +204,7 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<NativeBlockSt
|
||||
}
|
||||
|
||||
private int getHeight(Hunk<NativeBlockState> output, int x, int z) {
|
||||
for (int i = output.getHeight() - 1; i >= 0; i--) {
|
||||
for (int i = Math.min(output.getHeight() - 1, ColumnExtent.highestStoredY(output, x, z)); i >= 0; i--) {
|
||||
NativeBlockState b = output.get(x, i, z);
|
||||
|
||||
if (b != null) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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