mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +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.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
import art.arcane.iris.generation.block.BoundBlockState;
|
import art.arcane.iris.generation.block.BoundBlockState;
|
||||||
import art.arcane.iris.generation.block.IrisCustomData;
|
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.block.data.BlockData;
|
||||||
import org.bukkit.generator.ChunkGenerator.ChunkData;
|
import org.bukkit.generator.ChunkGenerator.ChunkData;
|
||||||
|
|
||||||
@SuppressWarnings("ClassCanBeRecord")
|
import java.lang.invoke.MethodHandles;
|
||||||
public class ChunkDataHunkHolder extends AtomicHunk<NativeBlockState> implements NativeBlockVolume {
|
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 BoundBlockState AIR = BoundBlockState.of("AIR");
|
||||||
|
private static final VarHandle COLUMN_TOPS = MethodHandles.arrayElementVarHandle(int[].class);
|
||||||
|
|
||||||
private final ChunkData chunk;
|
private final ChunkData chunk;
|
||||||
|
private final NativeBlockState[] data;
|
||||||
|
private final int[] columnTops;
|
||||||
|
private final int zStride;
|
||||||
|
|
||||||
public ChunkDataHunkHolder(ChunkData chunk) {
|
public ChunkDataHunkHolder(ChunkData chunk) {
|
||||||
super(16, chunk.getMaxHeight() - chunk.getMinHeight(), 16);
|
super(16, chunk.getMaxHeight() - chunk.getMinHeight(), 16);
|
||||||
this.chunk = chunk;
|
this.chunk = chunk;
|
||||||
|
this.zStride = 16 * getHeight();
|
||||||
|
this.data = new NativeBlockState[zStride * 16];
|
||||||
|
this.columnTops = new int[256];
|
||||||
|
Arrays.fill(columnTops, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -56,7 +72,16 @@ public class ChunkDataHunkHolder extends AtomicHunk<NativeBlockState> implements
|
|||||||
if (t == null) {
|
if (t == null) {
|
||||||
return;
|
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
|
@Override
|
||||||
@@ -65,13 +90,23 @@ public class ChunkDataHunkHolder extends AtomicHunk<NativeBlockState> implements
|
|||||||
return AIR.get();
|
return AIR.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeBlockState b = super.getRaw(x, y, z);
|
NativeBlockState b = data[index(x, y, z)];
|
||||||
|
|
||||||
return b != null ? b : AIR.get();
|
return b != null ? b : AIR.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public NativeBlockState getStoredRaw(int x, int y, int z) {
|
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() {
|
public void apply() {
|
||||||
@@ -90,7 +125,7 @@ public class ChunkDataHunkHolder extends AtomicHunk<NativeBlockState> implements
|
|||||||
int runStart = -1;
|
int runStart = -1;
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
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();
|
BlockData block = state == null ? null : (BlockData) state.nativeHandle();
|
||||||
// Custom wrappers are not real Bukkit data; write the vanilla base like the
|
// Custom wrappers are not real Bukkit data; write the vanilla base like the
|
||||||
// NMS fast path (NMSBinding.applyChunkDataBlocks) does.
|
// 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.volmlib.util.atomics.AtomicRollingSequence;
|
||||||
import art.arcane.iris.generation.context.ChunkContext;
|
import art.arcane.iris.generation.context.ChunkContext;
|
||||||
import art.arcane.iris.generation.context.IrisContext;
|
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.BlockCoordinates;
|
||||||
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||||
import art.arcane.volmlib.util.hunk.Hunk;
|
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)) {
|
IrisContext.Scope generationScope = IrisContext.open(this, lease.sessionId(), null)) {
|
||||||
getEngineData().getStatistics().generatedChunk();
|
getEngineData().getStatistics().generatedChunk();
|
||||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
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)) {
|
if (getDimension().isDebugChunkCrossSections() && ((x >> 4) % getDimension().getDebugCrossSectionsMod() == 0 || (z >> 4) % getDimension().getDebugCrossSectionsMod() == 0)) {
|
||||||
NativeBlockState crossSection = B.getState("CRYING_OBSIDIAN");
|
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.IrisRiverMaterialConfig;
|
||||||
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveAction;
|
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveAction;
|
||||||
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveCell;
|
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.ChunkContext;
|
||||||
import art.arcane.iris.generation.context.IrisContext;
|
import art.arcane.iris.generation.context.IrisContext;
|
||||||
import art.arcane.iris.generation.block.B;
|
import art.arcane.iris.generation.block.B;
|
||||||
@@ -896,12 +897,16 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
int width = output.getWidth();
|
int width = output.getWidth();
|
||||||
int depth = output.getDepth();
|
int depth = output.getDepth();
|
||||||
int height = output.getHeight();
|
int height = output.getHeight();
|
||||||
|
ColumnExtent extent = output instanceof ColumnExtent columnExtent ? columnExtent : null;
|
||||||
for (int localX = 0; localX < width; localX++) {
|
for (int localX = 0; localX < width; localX++) {
|
||||||
for (int localZ = 0; localZ < depth; localZ++) {
|
for (int localZ = 0; localZ < depth; localZ++) {
|
||||||
int worldX = blockX + localX;
|
int worldX = blockX + localX;
|
||||||
int worldZ = blockZ + localZ;
|
int worldZ = blockZ + localZ;
|
||||||
int floor = -1;
|
int floor = -1;
|
||||||
for (int y = 1; y < height; y++) {
|
for (int y = 1; y < height; y++) {
|
||||||
|
if (extent != null && y > extent.highestStoredY(localX, localZ)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
NativeBlockState state = output.getRaw(localX, y, localZ);
|
NativeBlockState state = output.getRaw(localX, y, localZ);
|
||||||
if (B.isSolid(state)) {
|
if (B.isSolid(state)) {
|
||||||
if (floor >= 0) {
|
if (floor >= 0) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package art.arcane.iris.generation.stage;
|
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.decoration.IrisSpeleothems;
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
import art.arcane.iris.generation.runtime.EngineAssignedModifier;
|
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) {
|
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);
|
NativeBlockState b = output.get(x, i, z);
|
||||||
|
|
||||||
if (b != null) {
|
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.nativelib.terrain.NativeBlockState;
|
||||||
import art.arcane.volmlib.util.hunk.Hunk;
|
import art.arcane.volmlib.util.hunk.Hunk;
|
||||||
import art.arcane.iris.generation.block.B;
|
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.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;
|
||||||
@@ -91,6 +96,46 @@ public class IrisNaturalCaveScanTest {
|
|||||||
verify(fixture.chunk).release();
|
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 {
|
private static void verifyScan(int width, int depth) throws Exception {
|
||||||
Fixture fixture = new Fixture();
|
Fixture fixture = new Fixture();
|
||||||
Hunk<NativeBlockState> output = fixture.output(width, depth);
|
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> calls = new ArrayList<>();
|
||||||
private final List<String> markers = new ArrayList<>();
|
private final List<String> markers = new ArrayList<>();
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private Fixture() throws Exception {
|
private Fixture() throws Exception {
|
||||||
|
this(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Fixture(boolean buildAboveTop) throws Exception {
|
||||||
Engine engine = mock(Engine.class);
|
Engine engine = mock(Engine.class);
|
||||||
EngineMantle engineMantle = mock(EngineMantle.class);
|
EngineMantle engineMantle = mock(EngineMantle.class);
|
||||||
Mantle<Matter> mantle = mock(Mantle.class);
|
Mantle<Matter> mantle = mock(Mantle.class);
|
||||||
@@ -211,6 +260,9 @@ public class IrisNaturalCaveScanTest {
|
|||||||
for (int height = 20; height <= 22; height++) {
|
for (int height = 20; height <= 22; height++) {
|
||||||
output.setRaw(x, height, z, air);
|
output.setRaw(x, height, z, air);
|
||||||
}
|
}
|
||||||
|
if (buildAboveTop) {
|
||||||
|
output.setRaw(x, 31, z, stone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}).when(surface).decorate(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), eq(biome), any(), anyInt(), anyInt());
|
}).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());
|
}).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) {
|
private Hunk<NativeBlockState> output(int width, int depth) {
|
||||||
Hunk<NativeBlockState> output = Hunk.newArrayHunk(width, 32, depth);
|
Hunk<NativeBlockState> output = Hunk.newArrayHunk(width, 32, depth);
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
|
|||||||
+44
@@ -1,6 +1,8 @@
|
|||||||
package art.arcane.iris.generation.stage;
|
package art.arcane.iris.generation.stage;
|
||||||
|
|
||||||
import art.arcane.iris.generation.block.B;
|
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.BurstExecutor;
|
||||||
import art.arcane.iris.generation.concurrent.MultiBurst;
|
import art.arcane.iris.generation.concurrent.MultiBurst;
|
||||||
import art.arcane.iris.generation.decoration.IrisProceduralBlocks;
|
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.Hunk;
|
||||||
import art.arcane.volmlib.util.hunk.storage.ArrayHunk;
|
import art.arcane.volmlib.util.hunk.storage.ArrayHunk;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -24,6 +27,7 @@ import java.util.Random;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
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;
|
||||||
@@ -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
|
@Test
|
||||||
public void unrelatedTallColumnsAreScannedOnceDuringRepeatedCleanup() {
|
public void unrelatedTallColumnsAreScannedOnceDuringRepeatedCleanup() {
|
||||||
Fixture fixture = new Fixture();
|
Fixture fixture = new Fixture();
|
||||||
|
|||||||
Reference in new issue
Block a user