mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +00:00
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:
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;
|
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 Hunk<T> source;
|
||||||
private final Listener<T> listener;
|
private final Listener<T> listener;
|
||||||
|
|
||||||
@@ -35,6 +35,12 @@ public final class ColumnExtentListeningHunk<T> implements Hunk<T>, ColumnExtent
|
|||||||
source.setRaw(x, y, z, t);
|
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
|
@Override
|
||||||
public T getRaw(int x, int y, int z) {
|
public T getRaw(int x, int y, int z) {
|
||||||
return source.getRaw(x, y, z);
|
return source.getRaw(x, y, z);
|
||||||
@@ -63,5 +69,11 @@ public final class ColumnExtentListeningHunk<T> implements Hunk<T>, ColumnExtent
|
|||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface Listener<T> {
|
public interface Listener<T> {
|
||||||
void onWrite(int x, int y, int z, T value);
|
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;
|
return minY <= maxY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int lowestY() {
|
||||||
|
return minY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int highestY() {
|
||||||
|
return maxY;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean contains(int y) {
|
public boolean contains(int y) {
|
||||||
return y >= minY && y <= maxY;
|
return y >= minY && y <= maxY;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,4 +50,18 @@ final class ChunkBlockUpdateListener implements ColumnExtentListeningHunk.Listen
|
|||||||
}
|
}
|
||||||
quiet = state;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+105
-26
@@ -36,6 +36,7 @@ import art.arcane.iris.generation.hydrology.IrisRiverMaterialConfig;
|
|||||||
import art.arcane.iris.generation.hydrology.IrisSurfaceRiverBankConfig;
|
import art.arcane.iris.generation.hydrology.IrisSurfaceRiverBankConfig;
|
||||||
import art.arcane.iris.generation.terrain.Terrain3DColumn;
|
import art.arcane.iris.generation.terrain.Terrain3DColumn;
|
||||||
import art.arcane.iris.generation.block.BoundBlockState;
|
import art.arcane.iris.generation.block.BoundBlockState;
|
||||||
|
import art.arcane.iris.generation.chunk.ColumnRunHunk;
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
import art.arcane.iris.generation.context.ChunkedDataCache;
|
import art.arcane.iris.generation.context.ChunkedDataCache;
|
||||||
import art.arcane.iris.generation.context.ChunkContext;
|
import art.arcane.iris.generation.context.ChunkContext;
|
||||||
@@ -176,6 +177,15 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
private IrisOreBands regionSurfaceOres;
|
private IrisOreBands regionSurfaceOres;
|
||||||
private IrisOreBands biomeUndergroundOres;
|
private IrisOreBands biomeUndergroundOres;
|
||||||
private IrisOreBands regionUndergroundOres;
|
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) {
|
private ColumnPainter(int x, int z, Hunk<NativeBlockState> h, ChunkContext context) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
@@ -260,8 +270,12 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
regionSurfaceOres = hideOres ? IrisOreBands.EMPTY : region.getSurfaceOreBands();
|
regionSurfaceOres = hideOres ? IrisOreBands.EMPTY : region.getSurfaceOreBands();
|
||||||
biomeUndergroundOres = hideOres ? IrisOreBands.EMPTY : biome.getUndergroundOreBands();
|
biomeUndergroundOres = hideOres ? IrisOreBands.EMPTY : biome.getUndergroundOreBands();
|
||||||
regionUndergroundOres = hideOres ? IrisOreBands.EMPTY : region.getUndergroundOreBands();
|
regionUndergroundOres = hideOres ? IrisOreBands.EMPTY : region.getUndergroundOreBands();
|
||||||
boolean hasSurfaceOres = biomeSurfaceOres.hasOres() || regionSurfaceOres.hasOres() || dimensionSurfaceOres.hasOres();
|
surfaceOreTop = oreTop(biomeSurfaceOres, regionSurfaceOres, dimensionSurfaceOres);
|
||||||
boolean hasUndergroundOres = biomeUndergroundOres.hasOres() || regionUndergroundOres.hasOres() || dimensionUndergroundOres.hasOres();
|
surfaceOreFloor = oreFloor(biomeSurfaceOres, regionSurfaceOres, dimensionSurfaceOres);
|
||||||
|
undergroundOreTop = oreTop(biomeUndergroundOres, regionUndergroundOres, dimensionUndergroundOres);
|
||||||
|
undergroundOreFloor = oreFloor(biomeUndergroundOres, regionUndergroundOres, dimensionUndergroundOres);
|
||||||
|
columnX = xf;
|
||||||
|
columnZ = zf;
|
||||||
int bedrockFloor = bedrockEnabled ? 1 : 0;
|
int bedrockFloor = bedrockEnabled ? 1 : 0;
|
||||||
KList<NativeBlockState> blocks = null;
|
KList<NativeBlockState> blocks = null;
|
||||||
KList<NativeBlockState> ceilingBlocks = null;
|
KList<NativeBlockState> ceilingBlocks = null;
|
||||||
@@ -269,7 +283,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
|
|
||||||
for (int i = topY; i >= 0; i--) {
|
for (int i = topY; i >= 0; i--) {
|
||||||
if (i == 0 && bedrockEnabled) {
|
if (i == 0 && bedrockEnabled) {
|
||||||
h.setRaw(xf, i, zf, bedrock);
|
put(i, bedrock);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,16 +294,16 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
ceilingBlocks = null;
|
ceilingBlocks = null;
|
||||||
}
|
}
|
||||||
if (terrainSpan < 0 || i > terrainColumn.floor(terrainSpan)) {
|
if (terrainSpan < 0 || i > terrainColumn.floor(terrainSpan)) {
|
||||||
h.setRaw(xf, i, zf, air);
|
put(i, air);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
layerSurfaceY = terrainColumn.floor(terrainSpan);
|
layerSurfaceY = terrainColumn.floor(terrainSpan);
|
||||||
layerCeilingY = terrainColumn.ceiling(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) {
|
if (ore != null) {
|
||||||
h.setRaw(xf, i, zf, ore);
|
put(i, ore);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +312,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
if (hydrologyFluid == null && fblocks == null) {
|
if (hydrologyFluid == null && fblocks == null) {
|
||||||
fblocks = biome.generateSeaLayers(realX, realZ, rng, hf - he, data);
|
fblocks = biome.generateSeaLayers(realX, realZ, rng, hf - he, data);
|
||||||
}
|
}
|
||||||
h.setRaw(xf, i, zf, HydrologyFluidLayerSelector.select(
|
put(i, HydrologyFluidLayerSelector.select(
|
||||||
fblocks,
|
fblocks,
|
||||||
fdepth,
|
fdepth,
|
||||||
fluid,
|
fluid,
|
||||||
@@ -310,7 +324,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
if (i <= he) {
|
if (i <= he) {
|
||||||
int depth = layerSurfaceY - i;
|
int depth = layerSurfaceY - i;
|
||||||
if (depth == 0 && mappedSurfaceBlock != null) {
|
if (depth == 0 && mappedSurfaceBlock != null) {
|
||||||
h.setRaw(xf, i, zf, mappedSurfaceBlock);
|
put(i, mappedSurfaceBlock);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (blocks == null) {
|
if (blocks == null) {
|
||||||
@@ -324,7 +338,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
|
|
||||||
int deepFloor = Math.max(bedrockFloor, layerCeilingY > 0 ? layerCeilingY + 2 : 0);
|
int deepFloor = Math.max(bedrockFloor, layerCeilingY > 0 ? layerCeilingY + 2 : 0);
|
||||||
if (i >= deepFloor && depth >= Math.max(blocks.size(), paintDepth)) {
|
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;
|
i = deepFloor;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -335,7 +349,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
2, layerCeilingY, data, complex);
|
2, layerCeilingY, data, complex);
|
||||||
}
|
}
|
||||||
int ceilingDepth = i - layerCeilingY;
|
int ceilingDepth = i - layerCeilingY;
|
||||||
h.setRaw(xf, i, zf, ceilingBlocks.hasIndex(ceilingDepth)
|
put(i, ceilingBlocks.hasIndex(ceilingDepth)
|
||||||
? ceilingBlocks.get(ceilingDepth) : rock);
|
? ceilingBlocks.get(ceilingDepth) : rock);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -348,17 +362,19 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
if (riverOwned && depth <= riverBed.getPadding() && IrisProceduralBlocks.isGravityAffected(layerBlock)) {
|
if (riverOwned && depth <= riverBed.getPadding() && IrisProceduralBlocks.isGravityAffected(layerBlock)) {
|
||||||
layerBlock = riverBed.getPaddingPalette().get(rng, realX, i, realZ, data);
|
layerBlock = riverBed.getPaddingPalette().get(rng, realX, i, realZ, data);
|
||||||
}
|
}
|
||||||
h.setRaw(xf, i, zf, layerBlock);
|
put(i, layerBlock);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ore = hasUndergroundOres ? undergroundOre(realX, i, realZ) : null;
|
ore = i <= undergroundOreTop && i >= undergroundOreFloor ? undergroundOre(realX, i, realZ) : null;
|
||||||
h.setRaw(xf, i, zf, ore != null ? ore : rock);
|
put(i, ore != null ? ore : rock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
flush();
|
||||||
|
|
||||||
if (upperContext != null) {
|
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
|
* 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.
|
* 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,
|
private void paintDeep(int realX, int realZ, int top, int floor, NativeBlockState rock) {
|
||||||
boolean hasSurfaceOres, boolean hasUndergroundOres) {
|
int bandTop = Math.min(top, Math.max(surfaceOreTop, undergroundOreTop));
|
||||||
for (int y = top; y >= floor; y--) {
|
int bandFloor = Math.max(floor, Math.min(surfaceOreFloor, undergroundOreFloor));
|
||||||
NativeBlockState ore = hasSurfaceOres ? surfaceOre(realX, y, realZ) : null;
|
if (bandTop < bandFloor) {
|
||||||
if (ore == null && hasUndergroundOres) {
|
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);
|
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) {
|
private NativeBlockState surfaceOre(int realX, int y, int realZ) {
|
||||||
@@ -405,7 +484,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
return ore;
|
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);
|
UpperDimensionContext.Column upperColumn = upperContext.sampleColumn(realX, realZ);
|
||||||
int upperSurfaceY = upperColumn.surfaceY();
|
int upperSurfaceY = upperColumn.surfaceY();
|
||||||
if (upperSurfaceY >= chunkHeight - 1) {
|
if (upperSurfaceY >= chunkHeight - 1) {
|
||||||
@@ -420,11 +499,11 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
|
|
||||||
for (int y = chunkHeight - 1; y >= upperSurfaceY; y--) {
|
for (int y = chunkHeight - 1; y >= upperSurfaceY; y--) {
|
||||||
if (y == chunkHeight - 1 && bedrockEnabled) {
|
if (y == chunkHeight - 1 && bedrockEnabled) {
|
||||||
h.setRaw(xf, y, zf, bedrock);
|
put(y, bedrock);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!upperColumn.isSolid(y)) {
|
if (!upperColumn.isSolid(y)) {
|
||||||
h.setRaw(xf, y, zf, air);
|
put(y, air);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int faceY = upperColumn.faceY(y);
|
int faceY = upperColumn.faceY(y);
|
||||||
@@ -434,7 +513,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
upperBlocks = null;
|
upperBlocks = null;
|
||||||
}
|
}
|
||||||
if (y == faceY && upperMappedSurface != null) {
|
if (y == faceY && upperMappedSurface != null) {
|
||||||
h.setRaw(xf, y, zf, upperMappedSurface);
|
put(y, upperMappedSurface);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int depthFromFace = y - faceY;
|
int depthFromFace = y - faceY;
|
||||||
@@ -444,9 +523,9 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
upperContext.getData(), upperContext.getSurfaceSlopeStream(sourceSurfaceY));
|
upperContext.getData(), upperContext.getSurfaceSlopeStream(sourceSurfaceY));
|
||||||
}
|
}
|
||||||
if (upperBlocks != null && upperBlocks.hasIndex(depthFromFace)) {
|
if (upperBlocks != null && upperBlocks.hasIndex(depthFromFace)) {
|
||||||
h.setRaw(xf, y, zf, upperBlocks.get(depthFromFace));
|
put(y, upperBlocks.get(depthFromFace));
|
||||||
} else {
|
} 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.ServerShutdownBoundary;
|
||||||
|
|
||||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockVolume;
|
import art.arcane.volmlib.nativelib.terrain.NativeSectionVolume;
|
||||||
|
|
||||||
import art.arcane.volmlib.nativelib.terrain.BukkitTerrainBuffer;
|
import art.arcane.volmlib.nativelib.terrain.BukkitTerrainBuffer;
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ public interface INMSBinding extends NativeTerrainAccess {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean applyChunkDataBlocks(ChunkGenerator.ChunkData chunkData, NativeBlockVolume data) {
|
default boolean applyChunkDataBlocks(ChunkGenerator.ChunkData chunkData, NativeSectionVolume data) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ import art.arcane.volmlib.util.math.M;
|
|||||||
import art.arcane.volmlib.util.hunk.Hunk;
|
import art.arcane.volmlib.util.hunk.Hunk;
|
||||||
import art.arcane.iris.generation.chunk.ColumnBiomeHunk;
|
import art.arcane.iris.generation.chunk.ColumnBiomeHunk;
|
||||||
import art.arcane.iris.platform.bukkit.BukkitBiome;
|
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.ChunkContext;
|
||||||
import art.arcane.iris.generation.context.IrisContext;
|
import art.arcane.iris.generation.context.IrisContext;
|
||||||
import art.arcane.volmlib.util.io.ReactiveFolder;
|
import art.arcane.volmlib.util.io.ReactiveFolder;
|
||||||
@@ -1344,7 +1345,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
if (selected != null) {
|
if (selected != null) {
|
||||||
selected.generateChunk(engine, TerrainChunk.create(d), x, z);
|
selected.generateChunk(engine, TerrainChunk.create(d), x, z);
|
||||||
} else {
|
} 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));
|
Hunk<NativeBiome> biomes = new ColumnBiomeHunk(d.getMaxHeight() - d.getMinHeight(), () -> BukkitBiome.of(Biome.PLAINS));
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope =
|
||||||
openGenerationHistoryCoordinateScope(engine, x << 4, z << 4);
|
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)) {
|
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
|
||||||
engine.generate(x << 4, z << 4, blocks, biomes,
|
engine.generate(x << 4, z << 4, blocks, biomes,
|
||||||
initialEntryPending || studioEntryBootstrapActive.get() || engine.shouldGenerateMulticore());
|
initialEntryPending || studioEntryBootstrapActive.get() || engine.shouldGenerateMulticore());
|
||||||
blocks.apply();
|
ChunkDataOutput.apply(d, blocks);
|
||||||
if (historyScope != null) {
|
if (historyScope != null) {
|
||||||
historyScope.claimGeneratedSemantics();
|
historyScope.claimGeneratedSemantics();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import art.arcane.iris.spi.IrisPlatforms;
|
|||||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
import art.arcane.iris.spi.PlatformRegistries;
|
import art.arcane.iris.spi.PlatformRegistries;
|
||||||
import art.arcane.iris.testsupport.PlatformLeakGuard;
|
import art.arcane.iris.testsupport.PlatformLeakGuard;
|
||||||
import art.arcane.iris.generation.chunk.ChunkDataHunkHolder;
|
import art.arcane.iris.generation.chunk.SectionBuffer;
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -37,7 +36,7 @@ public class PlatformStateRebindTest {
|
|||||||
"art.arcane.iris.generation.biome.IrisBiomeLayerGenerator",
|
"art.arcane.iris.generation.biome.IrisBiomeLayerGenerator",
|
||||||
"art.arcane.iris.studio.generation.BiomeBuffetGenerator",
|
"art.arcane.iris.studio.generation.BiomeBuffetGenerator",
|
||||||
"art.arcane.iris.generation.mantle.EngineMantle",
|
"art.arcane.iris.generation.mantle.EngineMantle",
|
||||||
"art.arcane.iris.generation.chunk.ChunkDataHunkHolder");
|
"art.arcane.iris.generation.chunk.SectionBuffer");
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void unbindPlatform() {
|
public void unbindPlatform() {
|
||||||
@@ -47,10 +46,7 @@ public class PlatformStateRebindTest {
|
|||||||
@Test
|
@Test
|
||||||
public void chunkDataAirIsResolvedAgainstTheCurrentlyBoundPlatform() {
|
public void chunkDataAirIsResolvedAgainstTheCurrentlyBoundPlatform() {
|
||||||
NativeBlockState first = bindPlatform("first");
|
NativeBlockState first = bindPlatform("first");
|
||||||
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
|
SectionBuffer holder = new SectionBuffer(384);
|
||||||
when(chunk.getMinHeight()).thenReturn(-64);
|
|
||||||
when(chunk.getMaxHeight()).thenReturn(320);
|
|
||||||
ChunkDataHunkHolder holder = new ChunkDataHunkHolder(chunk);
|
|
||||||
|
|
||||||
assertSame(first, holder.getRaw(0, -1, 0));
|
assertSame(first, holder.getRaw(0, -1, 0));
|
||||||
|
|
||||||
|
|||||||
@@ -1,155 +0,0 @@
|
|||||||
package art.arcane.iris.generation.chunk;
|
|
||||||
|
|
||||||
import art.arcane.iris.platform.bukkit.nms.INMS;
|
|
||||||
import art.arcane.iris.platform.bukkit.nms.INMSBinding;
|
|
||||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
|
||||||
import org.bukkit.block.data.BlockData;
|
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mockito.MockedStatic;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
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.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
|
||||||
import static org.mockito.Mockito.doAnswer;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.mockStatic;
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fallbackAppliesExplicitCaveAirBetweenSolidRunsWithNegativeMinimumHeight() {
|
|
||||||
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
|
|
||||||
when(chunk.getMinHeight()).thenReturn(-256);
|
|
||||||
when(chunk.getMaxHeight()).thenReturn(512);
|
|
||||||
ChunkDataHunkHolder holder = new ChunkDataHunkHolder(chunk);
|
|
||||||
BlockData dirt = mock(BlockData.class);
|
|
||||||
BlockData coal = mock(BlockData.class);
|
|
||||||
BlockData caveAir = mock(BlockData.class);
|
|
||||||
NativeBlockState dirtState = state(dirt);
|
|
||||||
NativeBlockState coalState = state(coal);
|
|
||||||
NativeBlockState caveAirState = state(caveAir);
|
|
||||||
BlockData[] applied = new BlockData[768];
|
|
||||||
Arrays.fill(applied, dirt);
|
|
||||||
for (int y = 0; y <= 75; y++) {
|
|
||||||
holder.setRaw(14, y + 256, 6, y < 33 ? dirtState : y <= 54 ? caveAirState : coalState);
|
|
||||||
}
|
|
||||||
doAnswer(call -> {
|
|
||||||
int y = call.getArgument(1);
|
|
||||||
applied[y + 256] = call.getArgument(3);
|
|
||||||
return null;
|
|
||||||
}).when(chunk).setBlock(anyInt(), anyInt(), anyInt(), any(BlockData.class));
|
|
||||||
doAnswer(call -> {
|
|
||||||
int from = call.getArgument(1);
|
|
||||||
int to = call.getArgument(4);
|
|
||||||
BlockData block = call.getArgument(6);
|
|
||||||
Arrays.fill(applied, from + 256, to + 256, block);
|
|
||||||
return null;
|
|
||||||
}).when(chunk).setRegion(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(BlockData.class));
|
|
||||||
|
|
||||||
try (MockedStatic<INMS> nms = mockStatic(INMS.class)) {
|
|
||||||
nms.when(INMS::get).thenReturn(mock(INMSBinding.class));
|
|
||||||
holder.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int y = -256; y < 512; y++) {
|
|
||||||
BlockData expected = y >= 33 && y <= 54 ? caveAir : y >= 55 && y <= 75 ? coal : dirt;
|
|
||||||
assertSame("column at " + y, expected, applied[y + 256]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NativeBlockState state(BlockData data) {
|
|
||||||
NativeBlockState state = mock(NativeBlockState.class);
|
|
||||||
when(state.nativeHandle()).thenReturn(data);
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package art.arcane.iris.generation.chunk;
|
||||||
|
|
||||||
|
import art.arcane.iris.platform.bukkit.nms.INMS;
|
||||||
|
import art.arcane.iris.platform.bukkit.nms.INMSBinding;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class ChunkDataOutputTest {
|
||||||
|
@Test
|
||||||
|
public void nativeWriterReceivesTheSectionBuffer() {
|
||||||
|
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
|
||||||
|
SectionBuffer blocks = new SectionBuffer(64);
|
||||||
|
INMSBinding binding = mock(INMSBinding.class);
|
||||||
|
when(binding.applyChunkDataBlocks(chunk, blocks)).thenReturn(true);
|
||||||
|
|
||||||
|
try (MockedStatic<INMS> nms = mockStatic(INMS.class)) {
|
||||||
|
nms.when(INMS::get).thenReturn(binding);
|
||||||
|
ChunkDataOutput.apply(chunk, blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(binding).applyChunkDataBlocks(chunk, blocks);
|
||||||
|
verify(chunk, never()).setRegion(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(BlockData.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fallbackAppliesExplicitCaveAirBetweenSolidRunsWithNegativeMinimumHeight() {
|
||||||
|
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
|
||||||
|
when(chunk.getMinHeight()).thenReturn(-256);
|
||||||
|
when(chunk.getMaxHeight()).thenReturn(512);
|
||||||
|
SectionBuffer blocks = new SectionBuffer(768);
|
||||||
|
BlockData dirt = mock(BlockData.class);
|
||||||
|
BlockData coal = mock(BlockData.class);
|
||||||
|
BlockData caveAir = mock(BlockData.class);
|
||||||
|
NativeBlockState dirtState = state(dirt);
|
||||||
|
NativeBlockState coalState = state(coal);
|
||||||
|
NativeBlockState caveAirState = state(caveAir);
|
||||||
|
BlockData[] applied = new BlockData[768];
|
||||||
|
Arrays.fill(applied, dirt);
|
||||||
|
for (int y = 0; y <= 75; y++) {
|
||||||
|
blocks.setRaw(14, y + 256, 6, y < 33 ? dirtState : y <= 54 ? caveAirState : coalState);
|
||||||
|
}
|
||||||
|
doAnswer(call -> {
|
||||||
|
int y = call.getArgument(1);
|
||||||
|
applied[y + 256] = call.getArgument(3);
|
||||||
|
return null;
|
||||||
|
}).when(chunk).setBlock(anyInt(), anyInt(), anyInt(), any(BlockData.class));
|
||||||
|
doAnswer(call -> {
|
||||||
|
int from = call.getArgument(1);
|
||||||
|
int to = call.getArgument(4);
|
||||||
|
BlockData block = call.getArgument(6);
|
||||||
|
Arrays.fill(applied, from + 256, to + 256, block);
|
||||||
|
return null;
|
||||||
|
}).when(chunk).setRegion(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(BlockData.class));
|
||||||
|
|
||||||
|
try (MockedStatic<INMS> nms = mockStatic(INMS.class)) {
|
||||||
|
nms.when(INMS::get).thenReturn(mock(INMSBinding.class));
|
||||||
|
ChunkDataOutput.apply(chunk, blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y = -256; y < 512; y++) {
|
||||||
|
BlockData expected = y >= 33 && y <= 54 ? caveAir : y >= 55 && y <= 75 ? coal : dirt;
|
||||||
|
assertSame("column at " + y, expected, applied[y + 256]);
|
||||||
|
}
|
||||||
|
assertTrue(blocks.highestStoredY(14, 6) == 331);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NativeBlockState state(BlockData data) {
|
||||||
|
NativeBlockState state = mock(NativeBlockState.class);
|
||||||
|
when(state.nativeHandle()).thenReturn(data);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
package art.arcane.iris.generation.chunk;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.IrisPlatform;
|
||||||
|
import art.arcane.iris.spi.IrisPlatforms;
|
||||||
|
import art.arcane.iris.spi.PlatformRegistries;
|
||||||
|
import art.arcane.iris.testsupport.PlatformLeakGuard;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
|
import art.arcane.volmlib.util.hunk.Hunk;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class SectionBufferTest {
|
||||||
|
@ClassRule
|
||||||
|
public static final PlatformLeakGuard PLATFORM_GUARD = PlatformLeakGuard.clean();
|
||||||
|
|
||||||
|
private NativeBlockState air;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void bindPlatform() {
|
||||||
|
air = mock(NativeBlockState.class);
|
||||||
|
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||||
|
when(registries.block(anyString())).thenReturn(air);
|
||||||
|
IrisPlatform platform = mock(IrisPlatform.class);
|
||||||
|
when(platform.registries()).thenReturn(registries);
|
||||||
|
IrisPlatforms.bind(platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void unbindPlatform() {
|
||||||
|
IrisPlatforms.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void columnTopTracksTheHighestStoredValueAndIgnoresNulls() {
|
||||||
|
SectionBuffer buffer = new SectionBuffer(64);
|
||||||
|
NativeBlockState stone = mock(NativeBlockState.class);
|
||||||
|
|
||||||
|
assertEquals(-1, buffer.highestStoredY(3, 7));
|
||||||
|
buffer.setRaw(3, 40, 7, null);
|
||||||
|
assertEquals(-1, buffer.highestStoredY(3, 7));
|
||||||
|
buffer.setRaw(3, 12, 7, stone);
|
||||||
|
buffer.setRaw(3, 30, 7, stone);
|
||||||
|
buffer.setRaw(3, 5, 7, stone);
|
||||||
|
|
||||||
|
assertEquals(30, buffer.highestStoredY(3, 7));
|
||||||
|
assertEquals(-1, buffer.highestStoredY(7, 3));
|
||||||
|
assertSame(stone, buffer.getStoredRaw(3, 30, 7));
|
||||||
|
assertNull(buffer.getStoredRaw(3, 31, 7));
|
||||||
|
assertEquals(30, ColumnExtent.highestStoredY(new ColumnExtentListeningHunk<>(buffer, (x, y, z, state) -> {
|
||||||
|
}), 3, 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void unwrittenAndOutOfRangeCellsReadAsAir() {
|
||||||
|
SectionBuffer buffer = new SectionBuffer(48);
|
||||||
|
NativeBlockState stone = mock(NativeBlockState.class);
|
||||||
|
buffer.setRaw(2, 20, 9, stone);
|
||||||
|
|
||||||
|
assertSame(air, buffer.getRaw(2, 21, 9));
|
||||||
|
assertSame(air, buffer.getRaw(2, 3, 9));
|
||||||
|
assertSame(air, buffer.getRaw(2, -1, 9));
|
||||||
|
assertSame(air, buffer.getRaw(2, 48, 9));
|
||||||
|
assertSame(stone, buffer.getRaw(2, 20, 9));
|
||||||
|
assertNull(buffer.getStoredRaw(2, 21, 9));
|
||||||
|
assertNull(buffer.getStoredRaw(2, 3, 9));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void paletteKeepsEveryDistinctInstance() {
|
||||||
|
SectionBuffer buffer = new SectionBuffer(32);
|
||||||
|
NativeBlockState first = mock(NativeBlockState.class);
|
||||||
|
NativeBlockState second = mock(NativeBlockState.class);
|
||||||
|
when(first.matches(second)).thenReturn(true);
|
||||||
|
when(second.matches(first)).thenReturn(true);
|
||||||
|
|
||||||
|
buffer.setRaw(0, 0, 0, first);
|
||||||
|
buffer.setRaw(1, 0, 0, second);
|
||||||
|
buffer.setRaw(2, 0, 0, first);
|
||||||
|
|
||||||
|
assertSame(first, buffer.getRaw(0, 0, 0));
|
||||||
|
assertSame(second, buffer.getRaw(1, 0, 0));
|
||||||
|
assertSame(first, buffer.getRaw(2, 0, 0));
|
||||||
|
assertEquals(buffer.paletteId(first), buffer.paletteId(first));
|
||||||
|
assertNotEquals(buffer.paletteId(first), buffer.paletteId(second));
|
||||||
|
assertEquals(0, buffer.paletteId(null));
|
||||||
|
NativeBlockState[] palette = buffer.palette();
|
||||||
|
assertEquals(3, palette.length);
|
||||||
|
assertNull(palette[0]);
|
||||||
|
assertSame(first, palette[buffer.paletteId(first)]);
|
||||||
|
assertSame(second, palette[buffer.paletteId(second)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sectionsUseTheYzxLayoutAndStayUnallocatedUntilWritten() {
|
||||||
|
SectionBuffer buffer = new SectionBuffer(64);
|
||||||
|
NativeBlockState stone = mock(NativeBlockState.class);
|
||||||
|
NativeBlockState dirt = mock(NativeBlockState.class);
|
||||||
|
buffer.setRaw(5, 37, 11, stone);
|
||||||
|
buffer.setRaw(15, 47, 15, dirt);
|
||||||
|
|
||||||
|
assertEquals(4, buffer.sectionCount());
|
||||||
|
assertNull(buffer.section(0));
|
||||||
|
assertNull(buffer.section(1));
|
||||||
|
assertNull(buffer.section(3));
|
||||||
|
short[] section = buffer.section(2);
|
||||||
|
assertEquals(4096, section.length);
|
||||||
|
assertEquals(buffer.paletteId(stone), section[(5 << 8) | (11 << 4) | 5]);
|
||||||
|
assertEquals(buffer.paletteId(dirt), section[(15 << 8) | (15 << 4) | 15]);
|
||||||
|
int written = 0;
|
||||||
|
for (short id : section) {
|
||||||
|
written += id == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
assertEquals(2, written);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void columnRunsMatchRepeatedSingleWrites() {
|
||||||
|
Random random = new Random(41L);
|
||||||
|
List<NativeBlockState> pool = new ArrayList<>();
|
||||||
|
for (int index = 0; index < 9; index++) {
|
||||||
|
pool.add(mock(NativeBlockState.class));
|
||||||
|
}
|
||||||
|
pool.add(null);
|
||||||
|
for (int trial = 0; trial < 40; trial++) {
|
||||||
|
int height = 16 * (1 + random.nextInt(12));
|
||||||
|
SectionBuffer runs = new SectionBuffer(height);
|
||||||
|
SectionBuffer cells = new SectionBuffer(height);
|
||||||
|
for (int write = 0; write < 300; write++) {
|
||||||
|
int x = random.nextInt(16);
|
||||||
|
int z = random.nextInt(16);
|
||||||
|
int from = random.nextInt(height);
|
||||||
|
int to = Math.min(height - 1, from + random.nextInt(70));
|
||||||
|
NativeBlockState state = pool.get(random.nextInt(pool.size()));
|
||||||
|
switch (random.nextInt(3)) {
|
||||||
|
case 0 -> runs.fillColumnRun(x, z, from, to, state);
|
||||||
|
case 1 -> runs.fillColumnRun(x, z, from, to, runs.paletteId(state));
|
||||||
|
default -> ColumnRunHunk.fillColumnRun(runs, x, z, from, to, state);
|
||||||
|
}
|
||||||
|
for (int y = to; y >= from; y--) {
|
||||||
|
cells.setRaw(x, y, z, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSameContents(cells, runs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void runsWithAnEmptyRangeOrNoStateWriteNothing() {
|
||||||
|
SectionBuffer buffer = new SectionBuffer(32);
|
||||||
|
NativeBlockState stone = mock(NativeBlockState.class);
|
||||||
|
buffer.fillColumnRun(1, 1, 10, 9, stone);
|
||||||
|
buffer.fillColumnRun(1, 1, 3, 9, (NativeBlockState) null);
|
||||||
|
buffer.fillColumnRun(1, 1, 3, 9, 0);
|
||||||
|
|
||||||
|
assertEquals(-1, buffer.highestStoredY(1, 1));
|
||||||
|
assertNull(buffer.section(0));
|
||||||
|
assertEquals(1, buffer.palette().length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void concurrentWritersNeverLoseTheColumnMaximum() throws InterruptedException {
|
||||||
|
for (int trial = 0; trial < 20; trial++) {
|
||||||
|
SectionBuffer buffer = new SectionBuffer(768);
|
||||||
|
NativeBlockState stone = mock(NativeBlockState.class);
|
||||||
|
int threads = 8;
|
||||||
|
runConcurrently(threads, thread -> {
|
||||||
|
for (int y = thread; y < 768; y += threads) {
|
||||||
|
buffer.setRaw(9, y, 4, stone);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertEquals(767, buffer.highestStoredY(9, 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void concurrentPaletteGrowthKeepsOneIdPerInstance() throws InterruptedException {
|
||||||
|
int threads = 8;
|
||||||
|
int statesPerThread = 300;
|
||||||
|
NativeBlockState[][] states = new NativeBlockState[threads][statesPerThread];
|
||||||
|
NativeBlockState[] shared = new NativeBlockState[40];
|
||||||
|
for (int index = 0; index < shared.length; index++) {
|
||||||
|
shared[index] = mock(NativeBlockState.class);
|
||||||
|
}
|
||||||
|
for (int thread = 0; thread < threads; thread++) {
|
||||||
|
for (int index = 0; index < statesPerThread; index++) {
|
||||||
|
states[thread][index] = mock(NativeBlockState.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int trial = 0; trial < 10; trial++) {
|
||||||
|
SectionBuffer buffer = new SectionBuffer(384);
|
||||||
|
runConcurrently(threads, thread -> {
|
||||||
|
for (int index = 0; index < statesPerThread; index++) {
|
||||||
|
int x = thread * 2 + (index & 1);
|
||||||
|
int z = (index >> 1) & 15;
|
||||||
|
int y = index;
|
||||||
|
buffer.setRaw(x, y, z, states[thread][index]);
|
||||||
|
buffer.setRaw(x, y + 1, z, shared[index % shared.length]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (int thread = 0; thread < threads; thread++) {
|
||||||
|
for (int index = 0; index < statesPerThread; index++) {
|
||||||
|
int x = thread * 2 + (index & 1);
|
||||||
|
int z = (index >> 1) & 15;
|
||||||
|
assertSame(states[thread][index], buffer.getStoredRaw(x, index, z));
|
||||||
|
assertSame(shared[index % shared.length], buffer.getRaw(x, index + 1, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(1 + threads * statesPerThread + shared.length, buffer.palette().length);
|
||||||
|
for (NativeBlockState state : shared) {
|
||||||
|
assertSame(state, buffer.palette()[buffer.paletteId(state)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listeningHunksForwardRunsInTopDownOrder() {
|
||||||
|
SectionBuffer buffer = new SectionBuffer(32);
|
||||||
|
NativeBlockState stone = mock(NativeBlockState.class);
|
||||||
|
List<Integer> heard = new ArrayList<>();
|
||||||
|
ColumnExtentListeningHunk<NativeBlockState> listening = new ColumnExtentListeningHunk<>(buffer,
|
||||||
|
(x, y, z, state) -> heard.add(y));
|
||||||
|
|
||||||
|
ColumnRunHunk.fillColumnRun(listening, 4, 6, 10, 13, stone);
|
||||||
|
|
||||||
|
assertEquals(List.of(13, 12, 11, 10), heard);
|
||||||
|
for (int y = 10; y <= 13; y++) {
|
||||||
|
assertSame(stone, buffer.getStoredRaw(4, y, 6));
|
||||||
|
}
|
||||||
|
assertNull(buffer.getStoredRaw(4, 9, 6));
|
||||||
|
assertEquals(13, buffer.highestStoredY(4, 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void plainHunksReceiveRunsAsTopDownSingleWrites() {
|
||||||
|
Hunk<NativeBlockState> array = Hunk.newArrayHunk(16, 32, 16);
|
||||||
|
NativeBlockState stone = mock(NativeBlockState.class);
|
||||||
|
array.setRaw(2, 5, 2, stone);
|
||||||
|
List<Integer> order = new ArrayList<>();
|
||||||
|
Hunk<NativeBlockState> recording = new ColumnExtentListeningHunk<>(array, (x, y, z, state) -> order.add(y));
|
||||||
|
|
||||||
|
ColumnRunHunk.fillColumnRun(Hunk.newArrayHunk(16, 32, 16), 0, 0, 0, 3, stone);
|
||||||
|
ColumnRunHunk.fillColumnRun(array, 2, 2, 4, 6, null);
|
||||||
|
ColumnRunHunk.fillColumnRun(recording, 1, 1, 7, 9, stone);
|
||||||
|
|
||||||
|
assertNull(array.getRaw(2, 5, 2));
|
||||||
|
assertSame(stone, array.getRaw(1, 8, 1));
|
||||||
|
assertEquals(List.of(9, 8, 7), order);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertSameContents(SectionBuffer expected, SectionBuffer actual) {
|
||||||
|
assertEquals(expected.sectionCount(), actual.sectionCount());
|
||||||
|
NativeBlockState[] expectedPalette = expected.palette();
|
||||||
|
NativeBlockState[] actualPalette = actual.palette();
|
||||||
|
for (int sectionIndex = 0; sectionIndex < expected.sectionCount(); sectionIndex++) {
|
||||||
|
short[] expectedSection = expected.section(sectionIndex);
|
||||||
|
short[] actualSection = actual.section(sectionIndex);
|
||||||
|
if (expectedSection == null) {
|
||||||
|
assertNull(actualSection);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
NativeBlockState[] expectedStates = new NativeBlockState[4096];
|
||||||
|
NativeBlockState[] actualStates = new NativeBlockState[4096];
|
||||||
|
for (int index = 0; index < 4096; index++) {
|
||||||
|
expectedStates[index] = expectedPalette[expectedSection[index] & 0xFFFF];
|
||||||
|
actualStates[index] = actualPalette[actualSection[index] & 0xFFFF];
|
||||||
|
}
|
||||||
|
assertArrayEquals(expectedStates, actualStates);
|
||||||
|
}
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
assertEquals(expected.highestStoredY(x, z), actual.highestStoredY(x, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(Arrays.stream(expectedPalette).filter(state -> state != null).distinct().count(),
|
||||||
|
Arrays.stream(actualPalette).filter(state -> state != null).distinct().count());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void runConcurrently(int threads, ThreadTask task) throws InterruptedException {
|
||||||
|
CountDownLatch start = new CountDownLatch(1);
|
||||||
|
List<Thread> workers = new ArrayList<>();
|
||||||
|
for (int thread = 0; thread < threads; thread++) {
|
||||||
|
int index = thread;
|
||||||
|
Thread worker = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
start.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
task.run(index);
|
||||||
|
});
|
||||||
|
worker.start();
|
||||||
|
workers.add(worker);
|
||||||
|
}
|
||||||
|
start.countDown();
|
||||||
|
for (Thread worker : workers) {
|
||||||
|
worker.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
private interface ThreadTask {
|
||||||
|
void run(int thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,11 +19,10 @@ 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.SectionBuffer;
|
||||||
import art.arcane.iris.generation.chunk.ColumnExtentListeningHunk;
|
import art.arcane.iris.generation.chunk.ColumnExtentListeningHunk;
|
||||||
import art.arcane.iris.spi.IrisPlatform;
|
import art.arcane.iris.spi.IrisPlatform;
|
||||||
import art.arcane.iris.spi.IrisPlatforms;
|
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;
|
||||||
@@ -165,10 +164,7 @@ public class IrisNaturalCaveScanTest {
|
|||||||
fullOutput.listen((x, y, z, state) -> fullWrites.add(x + ":" + y + ":" + z + ":" + state.key())), null);
|
fullOutput.listen((x, y, z, state) -> fullWrites.add(x + ":" + y + ":" + z + ":" + state.key())), null);
|
||||||
|
|
||||||
Fixture bounded = new Fixture(true);
|
Fixture bounded = new Fixture(true);
|
||||||
ChunkGenerator.ChunkData chunkData = mock(ChunkGenerator.ChunkData.class);
|
SectionBuffer holder = new SectionBuffer(32);
|
||||||
doReturn(0).when(chunkData).getMinHeight();
|
|
||||||
doReturn(32).when(chunkData).getMaxHeight();
|
|
||||||
ChunkDataHunkHolder holder = new ChunkDataHunkHolder(chunkData);
|
|
||||||
bounded.sparseOutput(holder);
|
bounded.sparseOutput(holder);
|
||||||
List<String> boundedWrites = new ArrayList<>();
|
List<String> boundedWrites = new ArrayList<>();
|
||||||
bounded.modifier.decorateNaturalCaves(-32, 48, new ColumnExtentListeningHunk<>(holder,
|
bounded.modifier.decorateNaturalCaves(-32, 48, new ColumnExtentListeningHunk<>(holder,
|
||||||
|
|||||||
+3
-11
@@ -1,7 +1,7 @@
|
|||||||
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.SectionBuffer;
|
||||||
import art.arcane.iris.generation.chunk.ColumnExtentListeningHunk;
|
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;
|
||||||
@@ -15,7 +15,6 @@ 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;
|
||||||
|
|
||||||
@@ -65,8 +64,8 @@ public class IrisPerfectionModifierColumnTest {
|
|||||||
NativeBlockState[] palette = {null, fixture.air, fixture.water, fixture.stone,
|
NativeBlockState[] palette = {null, fixture.air, fixture.water, fixture.stone,
|
||||||
fixture.decorant, fixture.half, fixture.spike(true, false), fixture.spike(false, true)};
|
fixture.decorant, fixture.half, fixture.spike(true, false), fixture.spike(false, true)};
|
||||||
for (int trial = 0; trial < 8; trial++) {
|
for (int trial = 0; trial < 8; trial++) {
|
||||||
ChunkDataHunkHolder bounded = holder(64);
|
SectionBuffer bounded = new SectionBuffer(64);
|
||||||
ChunkDataHunkHolder full = holder(64);
|
SectionBuffer full = new SectionBuffer(64);
|
||||||
for (int x = 0; x < 16; x++) {
|
for (int x = 0; x < 16; x++) {
|
||||||
for (int z = 0; z < 16; z++) {
|
for (int z = 0; z < 16; z++) {
|
||||||
int top = random.nextInt(64);
|
int top = random.nextInt(64);
|
||||||
@@ -91,13 +90,6 @@ public class IrisPerfectionModifierColumnTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package art.arcane.iris.probe;
|
package art.arcane.iris.probe;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.chunk.SectionBuffer;
|
||||||
import art.arcane.iris.generation.context.IrisContext;
|
import art.arcane.iris.generation.context.IrisContext;
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
import art.arcane.iris.generation.runtime.GenerationSessionLease;
|
import art.arcane.iris.generation.runtime.GenerationSessionLease;
|
||||||
@@ -70,7 +71,7 @@ final class ProbeChunkGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Hunk<NativeBlockState> newBlocks(int height) {
|
private Hunk<NativeBlockState> newBlocks(int height) {
|
||||||
return productionBuffers ? new ProductionBlockHunk(height) : Hunk.newArrayHunk(16, height, 16);
|
return productionBuffers ? new SectionBuffer(height) : Hunk.newArrayHunk(16, height, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Hunk<NativeBiome> newBiomes(int height) {
|
private Hunk<NativeBiome> newBiomes(int height) {
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
* Iris is a World Generator for Minecraft 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.probe;
|
|
||||||
|
|
||||||
import art.arcane.iris.generation.block.BoundBlockState;
|
|
||||||
import art.arcane.iris.generation.chunk.ColumnExtent;
|
|
||||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
|
||||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockVolume;
|
|
||||||
import art.arcane.volmlib.util.hunk.storage.StorageHunk;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.lang.invoke.VarHandle;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
final class ProductionBlockHunk 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 NativeBlockState[] data;
|
|
||||||
private final int[] columnTops;
|
|
||||||
private final int zStride;
|
|
||||||
|
|
||||||
ProductionBlockHunk(int height) {
|
|
||||||
super(16, height, 16);
|
|
||||||
this.zStride = 16 * height;
|
|
||||||
this.data = new NativeBlockState[zStride * 16];
|
|
||||||
this.columnTops = new int[256];
|
|
||||||
Arrays.fill(columnTops, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRaw(int x, int y, int z, NativeBlockState t) {
|
|
||||||
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 state = data[index(x, y, z)];
|
|
||||||
return state != null ? state : 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package art.arcane.iris.probe;
|
package art.arcane.iris.probe;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.chunk.SectionBuffer;
|
||||||
import art.arcane.volmlib.nativelib.terrain.NativeBiome;
|
import art.arcane.volmlib.nativelib.terrain.NativeBiome;
|
||||||
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;
|
||||||
@@ -19,7 +20,7 @@ public final class ProductionHunkTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void blockHunkIgnoresNullWritesAndTracksTheHighestStoredCellPerColumn() {
|
public void blockHunkIgnoresNullWritesAndTracksTheHighestStoredCellPerColumn() {
|
||||||
ProductionBlockHunk blocks = new ProductionBlockHunk(HEIGHT);
|
SectionBuffer blocks = new SectionBuffer(HEIGHT);
|
||||||
NativeBlockState stone = StubPlatform.blockStateForTest("minecraft:stone");
|
NativeBlockState stone = StubPlatform.blockStateForTest("minecraft:stone");
|
||||||
NativeBlockState dirt = StubPlatform.blockStateForTest("minecraft:dirt");
|
NativeBlockState dirt = StubPlatform.blockStateForTest("minecraft:dirt");
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ public final class ProductionHunkTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void dumpCaptureOfTheBlockHunkMatchesAnArrayHunkHoldingTheSameWrites() {
|
public void dumpCaptureOfTheBlockHunkMatchesAnArrayHunkHoldingTheSameWrites() {
|
||||||
ProductionBlockHunk production = new ProductionBlockHunk(HEIGHT);
|
SectionBuffer production = new SectionBuffer(HEIGHT);
|
||||||
Hunk<NativeBlockState> array = Hunk.newArrayHunk(16, HEIGHT, 16);
|
Hunk<NativeBlockState> array = Hunk.newArrayHunk(16, HEIGHT, 16);
|
||||||
Hunk<NativeBiome> biomes = Hunk.newArrayHunk(16, HEIGHT, 16);
|
Hunk<NativeBiome> biomes = Hunk.newArrayHunk(16, HEIGHT, 16);
|
||||||
NativeBlockState stone = StubPlatform.blockStateForTest("minecraft:stone");
|
NativeBlockState stone = StubPlatform.blockStateForTest("minecraft:stone");
|
||||||
|
|||||||
Reference in new issue
Block a user