Palette section buffer

- Add SectionBuffer: chunk palette of written instances and short[4096] ids per section in y, z, x order
- Column tops are indexed per x-slice and padded so fan-out slices do not share a cache line
- Add ColumnRunHunk and forward column runs through the listening hunk and block update listener
- Terrain painter emits column runs and evaluates ores only inside the column's ore bands
- Bukkit generation writes into SectionBuffer and applies it through ChunkDataOutput
- Remove ChunkDataHunkHolder and the probe's ProductionBlockHunk copy
- Probe production buffers use SectionBuffer
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-21 23:54:58 -04:00
1 parent f283d482a4
commit 4d2e52bcc7
19 files changed
+955 -469

No files matched your search

@@ -1,169 +0,0 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 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.iris.platform.bukkit.nms.INMS;
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.StorageHunk;
import org.bukkit.block.data.BlockData;
import org.bukkit.generator.ChunkGenerator.ChunkData;
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
public int getWidth() {
return 16;
}
@Override
public int getDepth() {
return 16;
}
@Override
public void setRaw(int x, int y, int z, NativeBlockState t) {
// Block-hunk contract: null means "no write", never "erase" — ChunkDataHunkView and
// the modded ModdedBlockBuffer already discard nulls, and storing one here made the
// Bukkit output diverge from the modded loaders for the same engine emission.
if (t == null) {
return;
}
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
public NativeBlockState getRaw(int x, int y, int z) {
if (y < 0 || y >= getHeight()) {
return AIR.get();
}
NativeBlockState b = data[index(x, y, z)];
return b != null ? b : AIR.get();
}
@Override
public NativeBlockState getStoredRaw(int x, int y, int 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() {
applyTo(chunk);
}
public void applyTo(ChunkData target) {
if (INMS.get().applyChunkDataBlocks(target, this)) {
return;
}
int height = getHeight();
for (int x = 0; x < getWidth(); x++) {
for (int z = 0; z < getDepth(); z++) {
BlockData activeBlock = null;
int runStart = -1;
for (int y = 0; y < height; y++) {
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.
if (block instanceof IrisCustomData custom) {
block = custom.getBase();
}
if (block == null) {
flushRun(target, x, z, runStart, y, activeBlock);
activeBlock = null;
runStart = -1;
continue;
}
if (activeBlock != null && activeBlock.equals(block)) {
continue;
}
flushRun(target, x, z, runStart, y, activeBlock);
activeBlock = block;
runStart = y;
}
flushRun(target, x, z, runStart, height, activeBlock);
}
}
}
private void flushRun(ChunkData target, int x, int z, int startY, int endY, BlockData block) {
if (block == null || startY < 0 || endY <= startY) {
return;
}
int minY = target.getMinHeight();
if (endY - startY == 1) {
target.setBlock(x, startY + minY, z, block);
return;
}
target.setRegion(x, startY + minY, z, x + 1, endY + minY, z + 1, block);
}
}
@@ -0,0 +1,82 @@
/*
* 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.iris.generation.block.IrisCustomData;
import art.arcane.iris.platform.bukkit.nms.INMS;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import org.bukkit.block.data.BlockData;
import org.bukkit.generator.ChunkGenerator.ChunkData;
public final class ChunkDataOutput {
private ChunkDataOutput() {
}
public static void apply(ChunkData target, SectionBuffer blocks) {
if (INMS.get().applyChunkDataBlocks(target, blocks)) {
return;
}
int height = blocks.getHeight();
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
BlockData activeBlock = null;
int runStart = -1;
for (int y = 0; y < height; y++) {
NativeBlockState state = blocks.getStoredRaw(x, y, z);
BlockData block = state == null ? null : (BlockData) state.nativeHandle();
if (block instanceof IrisCustomData custom) {
block = custom.getBase();
}
if (block == null) {
flushRun(target, x, z, runStart, y, activeBlock);
activeBlock = null;
runStart = -1;
continue;
}
if (activeBlock != null && activeBlock.equals(block)) {
continue;
}
flushRun(target, x, z, runStart, y, activeBlock);
activeBlock = block;
runStart = y;
}
flushRun(target, x, z, runStart, height, activeBlock);
}
}
}
private static void flushRun(ChunkData target, int x, int z, int startY, int endY, BlockData block) {
if (block == null || startY < 0 || endY <= startY) {
return;
}
int minY = target.getMinHeight();
if (endY - startY == 1) {
target.setBlock(x, startY + minY, z, block);
return;
}
target.setRegion(x, startY + minY, z, x + 1, endY + minY, z + 1, block);
}
}
@@ -20,7 +20,7 @@ package art.arcane.iris.generation.chunk;
import art.arcane.volmlib.util.hunk.Hunk;
public final class ColumnExtentListeningHunk<T> implements Hunk<T>, ColumnExtent {
public final class ColumnExtentListeningHunk<T> implements Hunk<T>, ColumnExtent, ColumnRunHunk<T> {
private final Hunk<T> source;
private final Listener<T> listener;
@@ -35,6 +35,12 @@ public final class ColumnExtentListeningHunk<T> implements Hunk<T>, ColumnExtent
source.setRaw(x, y, z, t);
}
@Override
public void fillColumnRun(int x, int z, int yFromInclusive, int yToInclusive, T value) {
listener.onRun(x, z, yFromInclusive, yToInclusive, value);
ColumnRunHunk.fillColumnRun(source, x, z, yFromInclusive, yToInclusive, value);
}
@Override
public T getRaw(int x, int y, int z) {
return source.getRaw(x, y, z);
@@ -63,5 +69,11 @@ public final class ColumnExtentListeningHunk<T> implements Hunk<T>, ColumnExtent
@FunctionalInterface
public interface Listener<T> {
void onWrite(int x, int y, int z, T value);
default void onRun(int x, int z, int yFromInclusive, int yToInclusive, T value) {
for (int y = yToInclusive; y >= yFromInclusive; y--) {
onWrite(x, y, z, value);
}
}
}
}
@@ -0,0 +1,36 @@
/*
* 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;
public interface ColumnRunHunk<T> {
void fillColumnRun(int x, int z, int yFromInclusive, int yToInclusive, T value);
@SuppressWarnings("unchecked")
static <T> void fillColumnRun(Hunk<T> hunk, int x, int z, int yFromInclusive, int yToInclusive, T value) {
if (hunk instanceof ColumnRunHunk<?> runs) {
((ColumnRunHunk<T>) runs).fillColumnRun(x, z, yFromInclusive, yToInclusive, value);
return;
}
for (int y = yToInclusive; y >= yFromInclusive; y--) {
hunk.setRaw(x, y, z, value);
}
}
}
@@ -0,0 +1,268 @@
/*
* 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.iris.generation.block.BoundBlockState;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import art.arcane.volmlib.nativelib.terrain.NativeSectionVolume;
import art.arcane.volmlib.util.hunk.Hunk;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Arrays;
public final class SectionBuffer implements Hunk<NativeBlockState>, NativeSectionVolume, ColumnExtent, ColumnRunHunk<NativeBlockState> {
private static final BoundBlockState AIR = BoundBlockState.of("AIR");
private static final VarHandle SECTIONS = MethodHandles.arrayElementVarHandle(short[][].class);
private static final VarHandle COLUMN_TOPS = MethodHandles.arrayElementVarHandle(int[].class);
private static final int SECTION_CELLS = 4096;
private static final int MAX_PALETTE_ID = 0xFFFF;
private static final int INITIAL_PALETTE_CAPACITY = 64;
private static final int COLUMN_SLICE_SHIFT = 5;
private final int height;
private final short[][] sections;
private final int[] columnTops;
private final Object paletteLock = new Object();
private PaletteEntry[] slots;
private NativeBlockState[] states;
private int paletteSize;
public SectionBuffer(int height) {
if (height <= 0) {
throw new IllegalArgumentException("Unsupported section buffer height " + height);
}
this.height = height;
this.sections = new short[(height + 15) >> 4][];
this.columnTops = new int[16 << COLUMN_SLICE_SHIFT];
Arrays.fill(columnTops, -1);
this.slots = new PaletteEntry[INITIAL_PALETTE_CAPACITY << 1];
this.states = new NativeBlockState[INITIAL_PALETTE_CAPACITY];
this.paletteSize = 1;
}
@Override
public int getWidth() {
return 16;
}
@Override
public int getHeight() {
return height;
}
@Override
public int getDepth() {
return 16;
}
public int paletteId(NativeBlockState state) {
if (state == null) {
return 0;
}
PaletteEntry[] table = slots;
int mask = table.length - 1;
int slot = System.identityHashCode(state) & mask;
PaletteEntry entry;
while ((entry = table[slot]) != null) {
if (entry.state == state) {
return entry.id;
}
slot = (slot + 1) & mask;
}
return insert(state);
}
@Override
public void setRaw(int x, int y, int z, NativeBlockState t) {
if (t == null) {
return;
}
writableSection(y >> 4)[((y & 15) << 8) | (z << 4) | x] = (short) paletteId(t);
raiseTop((x << COLUMN_SLICE_SHIFT) | z, y);
}
@Override
public NativeBlockState getRaw(int x, int y, int z) {
if (y < 0 || y >= height) {
return AIR.get();
}
short[] section = sections[y >> 4];
if (section != null) {
int id = section[((y & 15) << 8) | (z << 4) | x] & 0xFFFF;
if (id != 0) {
return state(id);
}
}
return AIR.get();
}
@Override
public NativeBlockState getStoredRaw(int x, int y, int z) {
if (y < 0 || y >= height) {
return null;
}
short[] section = sections[y >> 4];
if (section == null) {
return null;
}
int id = section[((y & 15) << 8) | (z << 4) | x] & 0xFFFF;
return id == 0 ? null : state(id);
}
@Override
public void fillColumnRun(int x, int z, int yFromInclusive, int yToInclusive, NativeBlockState value) {
if (value == null || yToInclusive < yFromInclusive) {
return;
}
fillColumnRun(x, z, yFromInclusive, yToInclusive, paletteId(value));
}
public void fillColumnRun(int localX, int localZ, int yFromInclusive, int yToInclusive, int paletteId) {
if (paletteId == 0 || yToInclusive < yFromInclusive) {
return;
}
short value = (short) paletteId;
int column = (localZ << 4) | localX;
int y = yFromInclusive;
while (y <= yToInclusive) {
int sectionIndex = y >> 4;
short[] section = writableSection(sectionIndex);
int last = Math.min(yToInclusive, (sectionIndex << 4) | 15);
int end = ((last & 15) << 8) | column;
for (int index = ((y & 15) << 8) | column; index <= end; index += 256) {
section[index] = value;
}
y = last + 1;
}
raiseTop((localX << COLUMN_SLICE_SHIFT) | localZ, yToInclusive);
}
@Override
public int highestStoredY(int x, int z) {
return (int) COLUMN_TOPS.getOpaque(columnTops, (x << COLUMN_SLICE_SHIFT) | z);
}
@Override
public int sectionCount() {
return sections.length;
}
@Override
public short[] section(int sectionIndex) {
return sections[sectionIndex];
}
@Override
public NativeBlockState[] palette() {
synchronized (paletteLock) {
return Arrays.copyOf(states, paletteSize);
}
}
private short[] writableSection(int sectionIndex) {
short[] section = sections[sectionIndex];
if (section != null) {
return section;
}
short[] created = new short[SECTION_CELLS];
short[] witness = (short[]) SECTIONS.compareAndExchange(sections, sectionIndex, (short[]) null, created);
return witness == null ? created : witness;
}
private void raiseTop(int column, int y) {
int top = (int) COLUMN_TOPS.getOpaque(columnTops, column);
while (y > top) {
int witness = (int) COLUMN_TOPS.compareAndExchange(columnTops, column, top, y);
if (witness == top) {
return;
}
top = witness;
}
}
private NativeBlockState state(int id) {
NativeBlockState[] current = states;
if (id < current.length) {
NativeBlockState state = current[id];
if (state != null) {
return state;
}
}
synchronized (paletteLock) {
return states[id];
}
}
private int insert(NativeBlockState state) {
synchronized (paletteLock) {
PaletteEntry[] table = slots;
int mask = table.length - 1;
int slot = System.identityHashCode(state) & mask;
PaletteEntry entry;
while ((entry = table[slot]) != null) {
if (entry.state == state) {
return entry.id;
}
slot = (slot + 1) & mask;
}
int id = paletteSize;
if (id > MAX_PALETTE_ID) {
throw new IllegalStateException("A chunk wrote more than " + MAX_PALETTE_ID + " distinct block state instances");
}
NativeBlockState[] current = states;
if (id == current.length) {
current = Arrays.copyOf(current, current.length << 1);
}
current[id] = state;
states = current;
paletteSize = id + 1;
PaletteEntry created = new PaletteEntry(state, id);
if (id << 1 >= table.length) {
slots = rehash(table, created);
} else {
table[slot] = created;
}
return id;
}
}
private static PaletteEntry[] rehash(PaletteEntry[] table, PaletteEntry created) {
PaletteEntry[] grown = new PaletteEntry[table.length << 1];
for (PaletteEntry entry : table) {
if (entry != null) {
place(grown, entry);
}
}
place(grown, created);
return grown;
}
private static void place(PaletteEntry[] table, PaletteEntry entry) {
int mask = table.length - 1;
int slot = System.identityHashCode(entry.state) & mask;
while (table[slot] != null) {
slot = (slot + 1) & mask;
}
table[slot] = entry;
}
private record PaletteEntry(NativeBlockState state, int id) {
}
}
@@ -79,6 +79,14 @@ public final class IrisOreBands {
return minY <= maxY;
}
public int lowestY() {
return minY;
}
public int highestY() {
return maxY;
}
public boolean contains(int y) {
return y >= minY && y <= maxY;
}
@@ -50,4 +50,18 @@ final class ChunkBlockUpdateListener implements ColumnExtentListeningHunk.Listen
}
quiet = state;
}
@Override
public void onRun(int x, int z, int yFromInclusive, int yToInclusive, NativeBlockState state) {
if (state == null || state == quiet) {
return;
}
if (B.isUpdatable(state) || state.isCustom()) {
for (int y = yToInclusive; y >= yFromInclusive; y--) {
updater.catchBlockUpdates(originX + x, y, originZ + z, state);
}
return;
}
quiet = state;
}
}
@@ -36,6 +36,7 @@ import art.arcane.iris.generation.hydrology.IrisRiverMaterialConfig;
import art.arcane.iris.generation.hydrology.IrisSurfaceRiverBankConfig;
import art.arcane.iris.generation.terrain.Terrain3DColumn;
import art.arcane.iris.generation.block.BoundBlockState;
import art.arcane.iris.generation.chunk.ColumnRunHunk;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.generation.context.ChunkedDataCache;
import art.arcane.iris.generation.context.ChunkContext;
@@ -176,6 +177,15 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
private IrisOreBands regionSurfaceOres;
private IrisOreBands biomeUndergroundOres;
private IrisOreBands regionUndergroundOres;
private int surfaceOreTop;
private int surfaceOreFloor;
private int undergroundOreTop;
private int undergroundOreFloor;
private int columnX;
private int columnZ;
private NativeBlockState runState;
private int runTop = -1;
private int runBottom;
private ColumnPainter(int x, int z, Hunk<NativeBlockState> h, ChunkContext context) {
this.x = x;
@@ -260,8 +270,12 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
regionSurfaceOres = hideOres ? IrisOreBands.EMPTY : region.getSurfaceOreBands();
biomeUndergroundOres = hideOres ? IrisOreBands.EMPTY : biome.getUndergroundOreBands();
regionUndergroundOres = hideOres ? IrisOreBands.EMPTY : region.getUndergroundOreBands();
boolean hasSurfaceOres = biomeSurfaceOres.hasOres() || regionSurfaceOres.hasOres() || dimensionSurfaceOres.hasOres();
boolean hasUndergroundOres = biomeUndergroundOres.hasOres() || regionUndergroundOres.hasOres() || dimensionUndergroundOres.hasOres();
surfaceOreTop = oreTop(biomeSurfaceOres, regionSurfaceOres, dimensionSurfaceOres);
surfaceOreFloor = oreFloor(biomeSurfaceOres, regionSurfaceOres, dimensionSurfaceOres);
undergroundOreTop = oreTop(biomeUndergroundOres, regionUndergroundOres, dimensionUndergroundOres);
undergroundOreFloor = oreFloor(biomeUndergroundOres, regionUndergroundOres, dimensionUndergroundOres);
columnX = xf;
columnZ = zf;
int bedrockFloor = bedrockEnabled ? 1 : 0;
KList<NativeBlockState> blocks = null;
KList<NativeBlockState> ceilingBlocks = null;
@@ -269,7 +283,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
for (int i = topY; i >= 0; i--) {
if (i == 0 && bedrockEnabled) {
h.setRaw(xf, i, zf, bedrock);
put(i, bedrock);
continue;
}
@@ -280,16 +294,16 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
ceilingBlocks = null;
}
if (terrainSpan < 0 || i > terrainColumn.floor(terrainSpan)) {
h.setRaw(xf, i, zf, air);
put(i, air);
continue;
}
layerSurfaceY = terrainColumn.floor(terrainSpan);
layerCeilingY = terrainColumn.ceiling(terrainSpan);
}
NativeBlockState ore = hasSurfaceOres ? surfaceOre(realX, i, realZ) : null;
NativeBlockState ore = i <= surfaceOreTop && i >= surfaceOreFloor ? surfaceOre(realX, i, realZ) : null;
if (ore != null) {
h.setRaw(xf, i, zf, ore);
put(i, ore);
continue;
}
@@ -298,7 +312,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
if (hydrologyFluid == null && fblocks == null) {
fblocks = biome.generateSeaLayers(realX, realZ, rng, hf - he, data);
}
h.setRaw(xf, i, zf, HydrologyFluidLayerSelector.select(
put(i, HydrologyFluidLayerSelector.select(
fblocks,
fdepth,
fluid,
@@ -310,7 +324,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
if (i <= he) {
int depth = layerSurfaceY - i;
if (depth == 0 && mappedSurfaceBlock != null) {
h.setRaw(xf, i, zf, mappedSurfaceBlock);
put(i, mappedSurfaceBlock);
continue;
}
if (blocks == null) {
@@ -324,7 +338,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
int deepFloor = Math.max(bedrockFloor, layerCeilingY > 0 ? layerCeilingY + 2 : 0);
if (i >= deepFloor && depth >= Math.max(blocks.size(), paintDepth)) {
paintDeep(xf, zf, realX, realZ, i, deepFloor, rock, hasSurfaceOres, hasUndergroundOres);
paintDeep(realX, realZ, i, deepFloor, rock);
i = deepFloor;
continue;
}
@@ -335,7 +349,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
2, layerCeilingY, data, complex);
}
int ceilingDepth = i - layerCeilingY;
h.setRaw(xf, i, zf, ceilingBlocks.hasIndex(ceilingDepth)
put(i, ceilingBlocks.hasIndex(ceilingDepth)
? ceilingBlocks.get(ceilingDepth) : rock);
continue;
}
@@ -348,17 +362,19 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
if (riverOwned && depth <= riverBed.getPadding() && IrisProceduralBlocks.isGravityAffected(layerBlock)) {
layerBlock = riverBed.getPaddingPalette().get(rng, realX, i, realZ, data);
}
h.setRaw(xf, i, zf, layerBlock);
put(i, layerBlock);
continue;
}
ore = hasUndergroundOres ? undergroundOre(realX, i, realZ) : null;
h.setRaw(xf, i, zf, ore != null ? ore : rock);
ore = i <= undergroundOreTop && i >= undergroundOreFloor ? undergroundOre(realX, i, realZ) : null;
put(i, ore != null ? ore : rock);
}
}
flush();
if (upperContext != null) {
paintUpper(xf, zf, realX, realZ);
paintUpper(realX, realZ);
flush();
}
}
@@ -366,15 +382,78 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
* Paints the solid run from {@code top} down to {@code floor}, which lies below every biome
* layer, river material, mapped surface and cave ceiling of its span: only ores or rock land there.
*/
private void paintDeep(int xf, int zf, int realX, int realZ, int top, int floor, NativeBlockState rock,
boolean hasSurfaceOres, boolean hasUndergroundOres) {
for (int y = top; y >= floor; y--) {
NativeBlockState ore = hasSurfaceOres ? surfaceOre(realX, y, realZ) : null;
if (ore == null && hasUndergroundOres) {
private void paintDeep(int realX, int realZ, int top, int floor, NativeBlockState rock) {
int bandTop = Math.min(top, Math.max(surfaceOreTop, undergroundOreTop));
int bandFloor = Math.max(floor, Math.min(surfaceOreFloor, undergroundOreFloor));
if (bandTop < bandFloor) {
putRun(top, floor, rock);
return;
}
if (top > bandTop) {
putRun(top, bandTop + 1, rock);
}
for (int y = bandTop; y >= bandFloor; y--) {
NativeBlockState ore = y <= surfaceOreTop && y >= surfaceOreFloor ? surfaceOre(realX, y, realZ) : null;
if (ore == null && y <= undergroundOreTop && y >= undergroundOreFloor) {
ore = undergroundOre(realX, y, realZ);
}
h.setRaw(xf, y, zf, ore != null ? ore : rock);
put(y, ore != null ? ore : rock);
}
if (bandFloor > floor) {
putRun(bandFloor - 1, floor, rock);
}
}
private void put(int y, NativeBlockState state) {
putRun(y, y, state);
}
private void putRun(int top, int bottom, NativeBlockState state) {
if (runTop >= 0 && state == runState && top == runBottom - 1) {
runBottom = bottom;
return;
}
flush();
runState = state;
runTop = top;
runBottom = bottom;
}
private void flush() {
if (runTop < 0) {
return;
}
ColumnRunHunk.fillColumnRun(h, columnX, columnZ, runBottom, runTop, runState);
runTop = -1;
runState = null;
}
private static int oreTop(IrisOreBands first, IrisOreBands second, IrisOreBands third) {
int top = Integer.MIN_VALUE;
if (first.hasOres()) {
top = Math.max(top, first.highestY());
}
if (second.hasOres()) {
top = Math.max(top, second.highestY());
}
if (third.hasOres()) {
top = Math.max(top, third.highestY());
}
return top;
}
private static int oreFloor(IrisOreBands first, IrisOreBands second, IrisOreBands third) {
int floor = Integer.MAX_VALUE;
if (first.hasOres()) {
floor = Math.min(floor, first.lowestY());
}
if (second.hasOres()) {
floor = Math.min(floor, second.lowestY());
}
if (third.hasOres()) {
floor = Math.min(floor, third.lowestY());
}
return floor;
}
private NativeBlockState surfaceOre(int realX, int y, int realZ) {
@@ -405,7 +484,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
return ore;
}
private void paintUpper(int xf, int zf, int realX, int realZ) {
private void paintUpper(int realX, int realZ) {
UpperDimensionContext.Column upperColumn = upperContext.sampleColumn(realX, realZ);
int upperSurfaceY = upperColumn.surfaceY();
if (upperSurfaceY >= chunkHeight - 1) {
@@ -420,11 +499,11 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
for (int y = chunkHeight - 1; y >= upperSurfaceY; y--) {
if (y == chunkHeight - 1 && bedrockEnabled) {
h.setRaw(xf, y, zf, bedrock);
put(y, bedrock);
continue;
}
if (!upperColumn.isSolid(y)) {
h.setRaw(xf, y, zf, air);
put(y, air);
continue;
}
int faceY = upperColumn.faceY(y);
@@ -434,7 +513,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
upperBlocks = null;
}
if (y == faceY && upperMappedSurface != null) {
h.setRaw(xf, y, zf, upperMappedSurface);
put(y, upperMappedSurface);
continue;
}
int depthFromFace = y - faceY;
@@ -444,9 +523,9 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
upperContext.getData(), upperContext.getSurfaceSlopeStream(sourceSurfaceY));
}
if (upperBlocks != null && upperBlocks.hasIndex(depthFromFace)) {
h.setRaw(xf, y, zf, upperBlocks.get(depthFromFace));
put(y, upperBlocks.get(depthFromFace));
} else {
h.setRaw(xf, y, zf, upperRock);
put(y, upperRock);
}
}
}
@@ -20,7 +20,7 @@ package art.arcane.iris.platform.bukkit.nms;
import art.arcane.volmlib.nativelib.terrain.ServerShutdownBoundary;
import art.arcane.volmlib.nativelib.terrain.NativeBlockVolume;
import art.arcane.volmlib.nativelib.terrain.NativeSectionVolume;
import art.arcane.volmlib.nativelib.terrain.BukkitTerrainBuffer;
@@ -217,7 +217,7 @@ public interface INMSBinding extends NativeTerrainAccess {
return false;
}
default boolean applyChunkDataBlocks(ChunkGenerator.ChunkData chunkData, NativeBlockVolume data) {
default boolean applyChunkDataBlocks(ChunkGenerator.ChunkData chunkData, NativeSectionVolume data) {
return false;
}
@@ -76,7 +76,8 @@ import art.arcane.volmlib.util.math.M;
import art.arcane.volmlib.util.hunk.Hunk;
import art.arcane.iris.generation.chunk.ColumnBiomeHunk;
import art.arcane.iris.platform.bukkit.BukkitBiome;
import art.arcane.iris.generation.chunk.ChunkDataHunkHolder;
import art.arcane.iris.generation.chunk.ChunkDataOutput;
import art.arcane.iris.generation.chunk.SectionBuffer;
import art.arcane.iris.generation.context.ChunkContext;
import art.arcane.iris.generation.context.IrisContext;
import art.arcane.volmlib.util.io.ReactiveFolder;
@@ -1344,7 +1345,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
if (selected != null) {
selected.generateChunk(engine, TerrainChunk.create(d), x, z);
} else {
ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(d);
SectionBuffer blocks = new SectionBuffer(d.getMaxHeight() - d.getMinHeight());
Hunk<NativeBiome> biomes = new ColumnBiomeHunk(d.getMaxHeight() - d.getMinHeight(), () -> BukkitBiome.of(Biome.PLAINS));
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope =
openGenerationHistoryCoordinateScope(engine, x << 4, z << 4);
@@ -1352,7 +1353,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
engine.generate(x << 4, z << 4, blocks, biomes,
initialEntryPending || studioEntryBootstrapActive.get() || engine.shouldGenerateMulticore());
blocks.apply();
ChunkDataOutput.apply(d, blocks);
if (historyScope != null) {
historyScope.claimGeneratedSemantics();
}