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:
Brian Neumann-Fopiano committed 2026-09-28 11:11:11 -04:00
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) {