mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +00:00
Merge L5-output
This commit is contained in:
commit
c4dcb32334
33 files changed
+1055
-432
No files matched your search
+1
-5
@@ -48,7 +48,7 @@ public final class FloatingIslandBoundarySampler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable IrisBiome parent(int x, int z) {
|
public @Nullable IrisBiome parent(int x, int z) {
|
||||||
return parentField(x, z, EDGE_TAPER_WIDTH).parent(x, z);
|
return source.sample(x, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
public double edgeFade(IrisBiome parent, int x, int z) {
|
public double edgeFade(IrisBiome parent, int x, int z) {
|
||||||
@@ -230,10 +230,6 @@ public final class FloatingIslandBoundarySampler {
|
|||||||
build(source);
|
build(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable IrisBiome parent(int x, int z) {
|
|
||||||
return parents[geometry.index(x - minX, z - minZ)];
|
|
||||||
}
|
|
||||||
|
|
||||||
private int edgeDistance(IrisBiome expected, int x, int z) {
|
private int edgeDistance(IrisBiome expected, int x, int z) {
|
||||||
int index = geometry.index(x - minX, z - minZ);
|
int index = geometry.index(x - minX, z - minZ);
|
||||||
if (!sameBiome(expected, parents[index]) || !viable[index]) {
|
if (!sameBiome(expected, parents[index]) || !viable[index]) {
|
||||||
|
|||||||
@@ -23,19 +23,35 @@ import art.arcane.volmlib.nativelib.terrain.NativeBlockVolume;
|
|||||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
import art.arcane.iris.generation.block.BoundBlockState;
|
import art.arcane.iris.generation.block.BoundBlockState;
|
||||||
import art.arcane.iris.generation.block.IrisCustomData;
|
import art.arcane.iris.generation.block.IrisCustomData;
|
||||||
import art.arcane.volmlib.util.hunk.storage.AtomicHunk;
|
import art.arcane.volmlib.util.hunk.storage.StorageHunk;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
import org.bukkit.generator.ChunkGenerator.ChunkData;
|
import org.bukkit.generator.ChunkGenerator.ChunkData;
|
||||||
|
|
||||||
@SuppressWarnings("ClassCanBeRecord")
|
import java.lang.invoke.MethodHandles;
|
||||||
public class ChunkDataHunkHolder extends AtomicHunk<NativeBlockState> implements NativeBlockVolume {
|
import java.lang.invoke.VarHandle;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plain-array block buffer for one chunk. Stages write disjoint columns when they fan out and every hand-off
|
||||||
|
* between threads is a join, so the cells need no per-access fences. Each column tracks its highest stored y;
|
||||||
|
* a raise is a CAS so concurrent writers to one column can never lose the maximum.
|
||||||
|
*/
|
||||||
|
public class ChunkDataHunkHolder extends StorageHunk<NativeBlockState> implements NativeBlockVolume, ColumnExtent {
|
||||||
private static final BoundBlockState AIR = BoundBlockState.of("AIR");
|
private static final BoundBlockState AIR = BoundBlockState.of("AIR");
|
||||||
|
private static final VarHandle COLUMN_TOPS = MethodHandles.arrayElementVarHandle(int[].class);
|
||||||
|
|
||||||
private final ChunkData chunk;
|
private final ChunkData chunk;
|
||||||
|
private final NativeBlockState[] data;
|
||||||
|
private final int[] columnTops;
|
||||||
|
private final int zStride;
|
||||||
|
|
||||||
public ChunkDataHunkHolder(ChunkData chunk) {
|
public ChunkDataHunkHolder(ChunkData chunk) {
|
||||||
super(16, chunk.getMaxHeight() - chunk.getMinHeight(), 16);
|
super(16, chunk.getMaxHeight() - chunk.getMinHeight(), 16);
|
||||||
this.chunk = chunk;
|
this.chunk = chunk;
|
||||||
|
this.zStride = 16 * getHeight();
|
||||||
|
this.data = new NativeBlockState[zStride * 16];
|
||||||
|
this.columnTops = new int[256];
|
||||||
|
Arrays.fill(columnTops, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -56,7 +72,16 @@ public class ChunkDataHunkHolder extends AtomicHunk<NativeBlockState> implements
|
|||||||
if (t == null) {
|
if (t == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
super.setRaw(x, y, z, t);
|
data[index(x, y, z)] = t;
|
||||||
|
int column = (z << 4) | x;
|
||||||
|
int top = (int) COLUMN_TOPS.getOpaque(columnTops, column);
|
||||||
|
while (y > top) {
|
||||||
|
int witness = (int) COLUMN_TOPS.compareAndExchange(columnTops, column, top, y);
|
||||||
|
if (witness == top) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
top = witness;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -65,13 +90,23 @@ public class ChunkDataHunkHolder extends AtomicHunk<NativeBlockState> implements
|
|||||||
return AIR.get();
|
return AIR.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeBlockState b = super.getRaw(x, y, z);
|
NativeBlockState b = data[index(x, y, z)];
|
||||||
|
|
||||||
return b != null ? b : AIR.get();
|
return b != null ? b : AIR.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public NativeBlockState getStoredRaw(int x, int y, int z) {
|
public NativeBlockState getStoredRaw(int x, int y, int z) {
|
||||||
return super.getRaw(x, y, z);
|
return data[index(x, y, z)];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int highestStoredY(int x, int z) {
|
||||||
|
return (int) COLUMN_TOPS.getOpaque(columnTops, (z << 4) | x);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int index(int x, int y, int z) {
|
||||||
|
return (z * zStride) + (y << 4) + x;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void apply() {
|
public void apply() {
|
||||||
@@ -90,7 +125,7 @@ public class ChunkDataHunkHolder extends AtomicHunk<NativeBlockState> implements
|
|||||||
int runStart = -1;
|
int runStart = -1;
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
NativeBlockState state = super.getRaw(x, y, z);
|
NativeBlockState state = getStoredRaw(x, y, z);
|
||||||
BlockData block = state == null ? null : (BlockData) state.nativeHandle();
|
BlockData block = state == null ? null : (BlockData) state.nativeHandle();
|
||||||
// Custom wrappers are not real Bukkit data; write the vanilla base like the
|
// Custom wrappers are not real Bukkit data; write the vanilla base like the
|
||||||
// NMS fast path (NMSBinding.applyChunkDataBlocks) does.
|
// NMS fast path (NMSBinding.applyChunkDataBlocks) does.
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* 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.platform.bukkit.BukkitBiome;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBiome;
|
||||||
|
import art.arcane.volmlib.util.hunk.HunkMutationSupport;
|
||||||
|
import art.arcane.volmlib.util.hunk.storage.StorageHunk;
|
||||||
|
import org.bukkit.block.Biome;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Biome output for one Bukkit chunk, read back only by the generation stages. The biome actuator writes whole
|
||||||
|
* columns, so storage stays at one entry per column until a partial column write arrives; that write switches
|
||||||
|
* the hunk to full resolution. Reads, clamping and the plains fallback match {@link LinkedTerrainChunk}.
|
||||||
|
*/
|
||||||
|
public final class ColumnBiomeHunk extends StorageHunk<NativeBiome> {
|
||||||
|
private static final int CHUNK_SIZE = 16;
|
||||||
|
|
||||||
|
private final NativeBiome[] columns = new NativeBiome[CHUNK_SIZE * CHUNK_SIZE];
|
||||||
|
private NativeBiome[] cells;
|
||||||
|
private volatile NativeBiome defaultBiome;
|
||||||
|
|
||||||
|
public ColumnBiomeHunk(int height) {
|
||||||
|
super(CHUNK_SIZE, height, CHUNK_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(int x1, int y1, int z1, int x2, int y2, int z2, NativeBiome biome) {
|
||||||
|
if (x1 == x2 && z1 == z2 && y1 == 0 && y2 == getHeight() - 1) {
|
||||||
|
fillColumn(x1, z1, LinkedTerrainChunk.canonicalBiome(biome));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HunkMutationSupport.setRangeInclusive(this, x1, y1, z1, x2, y2, z2, biome);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRaw(int x, int y, int z, NativeBiome biome) {
|
||||||
|
NativeBiome[] storage = cells == null ? expand() : cells;
|
||||||
|
storage[cellIndex(x, y, z)] = LinkedTerrainChunk.canonicalBiome(biome);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NativeBiome getRaw(int x, int y, int z) {
|
||||||
|
NativeBiome[] storage = cells;
|
||||||
|
NativeBiome biome = storage == null ? columns[columnIndex(x, z)] : storage[cellIndex(x, y, z)];
|
||||||
|
if (biome != null) {
|
||||||
|
return biome;
|
||||||
|
}
|
||||||
|
NativeBiome fallback = defaultBiome;
|
||||||
|
if (fallback == null) {
|
||||||
|
fallback = BukkitBiome.of(Biome.PLAINS);
|
||||||
|
defaultBiome = fallback;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillColumn(int x, int z, NativeBiome biome) {
|
||||||
|
NativeBiome[] storage = cells;
|
||||||
|
if (storage == null) {
|
||||||
|
columns[columnIndex(x, z)] = biome;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int stride = CHUNK_SIZE * CHUNK_SIZE;
|
||||||
|
int index = columnIndex(x, z);
|
||||||
|
for (int y = 0; y < getHeight(); y++) {
|
||||||
|
storage[index] = biome;
|
||||||
|
index += stride;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private NativeBiome[] expand() {
|
||||||
|
NativeBiome[] storage = new NativeBiome[CHUNK_SIZE * getHeight() * CHUNK_SIZE];
|
||||||
|
int stride = CHUNK_SIZE * CHUNK_SIZE;
|
||||||
|
for (int column = 0; column < stride; column++) {
|
||||||
|
NativeBiome biome = columns[column];
|
||||||
|
if (biome == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int index = column; index < storage.length; index += stride) {
|
||||||
|
storage[index] = biome;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cells = storage;
|
||||||
|
return storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int columnIndex(int x, int z) {
|
||||||
|
return ((z & (CHUNK_SIZE - 1)) * CHUNK_SIZE) + (x & (CHUNK_SIZE - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int cellIndex(int x, int y, int z) {
|
||||||
|
int clampedY = Math.max(0, Math.min(getHeight() - 1, y));
|
||||||
|
return (clampedY * CHUNK_SIZE * CHUNK_SIZE) + columnIndex(x, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||||
|
* Copyright (c) 2026 Arcane Arts (Volmit Software)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package art.arcane.iris.generation.chunk;
|
||||||
|
|
||||||
|
import art.arcane.volmlib.util.hunk.Hunk;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A block volume that knows, per column, the highest y it holds a value for. Every cell above that y reads as
|
||||||
|
* unwritten, so column scans can stop there without changing what they observe.
|
||||||
|
*/
|
||||||
|
public interface ColumnExtent {
|
||||||
|
int highestStoredY(int x, int z);
|
||||||
|
|
||||||
|
static int highestStoredY(Hunk<?> hunk, int x, int z) {
|
||||||
|
return hunk instanceof ColumnExtent extent ? extent.highestStoredY(x, z) : hunk.getHeight() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||||
|
* Copyright (c) 2026 Arcane Arts (Volmit Software)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package art.arcane.iris.generation.chunk;
|
||||||
|
|
||||||
|
import art.arcane.volmlib.util.function.Consumer4;
|
||||||
|
import art.arcane.volmlib.util.hunk.Hunk;
|
||||||
|
import art.arcane.volmlib.util.hunk.view.ListeningHunk;
|
||||||
|
|
||||||
|
public final class ColumnExtentListeningHunk<T> extends ListeningHunk<T> implements ColumnExtent {
|
||||||
|
private final Hunk<T> source;
|
||||||
|
|
||||||
|
public ColumnExtentListeningHunk(Hunk<T> source, Consumer4<Integer, Integer, Integer, T> listener) {
|
||||||
|
super(source, listener);
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int highestStoredY(int x, int z) {
|
||||||
|
return ColumnExtent.highestStoredY(source, x, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -130,7 +130,7 @@ public class LinkedTerrainChunk implements TerrainChunk {
|
|||||||
return (clampedY * CHUNK_SIZE + clampedZ) * CHUNK_SIZE + clampedX;
|
return (clampedY * CHUNK_SIZE + clampedZ) * CHUNK_SIZE + clampedX;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static NativeBiome canonicalBiome(NativeBiome biome) {
|
static NativeBiome canonicalBiome(NativeBiome biome) {
|
||||||
if (biome instanceof BukkitBiome) {
|
if (biome instanceof BukkitBiome) {
|
||||||
return biome;
|
return biome;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,6 +230,10 @@ public class ChunkContext {
|
|||||||
return region;
|
return region;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasDimensionStack() {
|
||||||
|
return dimensionStackContext != null;
|
||||||
|
}
|
||||||
|
|
||||||
public DimensionStackLayout getDimensionStackLayout(int localX, int localZ) {
|
public DimensionStackLayout getDimensionStackLayout(int localX, int localZ) {
|
||||||
if (dimensionStackContext == null) {
|
if (dimensionStackContext == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ final class EngineHotloader {
|
|||||||
previousGeneration.transitionPlan());
|
previousGeneration.transitionPlan());
|
||||||
PreparedHydrologyCacheIdentity cacheIdentity = PreparedHydrologyCacheIdentity.capture(
|
PreparedHydrologyCacheIdentity cacheIdentity = PreparedHydrologyCacheIdentity.capture(
|
||||||
assembly.target, assembly.runtimeKernel, assembly.transitionPlan, engine.isStudio());
|
assembly.target, assembly.runtimeKernel, assembly.transitionPlan, engine.isStudio());
|
||||||
engine.runtimeAssembly.set(assembly);
|
engine.threadState.setAssembly(assembly);
|
||||||
EngineRuntime next;
|
EngineRuntime next;
|
||||||
try (IrisContext.Scope ignored = IrisContext.open(engine, engine.getGenerationSessions().currentSessionId(), null)) {
|
try (IrisContext.Scope ignored = IrisContext.open(engine, engine.getGenerationSessions().currentSessionId(), null)) {
|
||||||
assembly.complex = assembly.runtimeKernel.createComplex(engine, assembly.transitionPlan, false);
|
assembly.complex = assembly.runtimeKernel.createComplex(engine, assembly.transitionPlan, false);
|
||||||
@@ -97,7 +97,7 @@ final class EngineHotloader {
|
|||||||
biomeMaxes);
|
biomeMaxes);
|
||||||
next = previous.withGeneration(nextGeneration);
|
next = previous.withGeneration(nextGeneration);
|
||||||
} finally {
|
} finally {
|
||||||
engine.runtimeAssembly.remove();
|
engine.threadState.setAssembly(null);
|
||||||
}
|
}
|
||||||
Throwable retirementFailure = runCleanup(null, previousGeneration.complex()::close);
|
Throwable retirementFailure = runCleanup(null, previousGeneration.complex()::close);
|
||||||
if (retirementFailure != null) {
|
if (retirementFailure != null) {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import art.arcane.iris.generation.concurrent.MultiBurst;
|
|||||||
import art.arcane.iris.generation.context.ChunkContext;
|
import art.arcane.iris.generation.context.ChunkContext;
|
||||||
import art.arcane.iris.generation.context.IrisContext;
|
import art.arcane.iris.generation.context.IrisContext;
|
||||||
import art.arcane.iris.generation.mantle.EngineMantle;
|
import art.arcane.iris.generation.mantle.EngineMantle;
|
||||||
import art.arcane.iris.generation.stage.IrisBiomeActuator;
|
import art.arcane.iris.generation.stage.IrisDimensionStackActuator;
|
||||||
import art.arcane.iris.generation.stage.IrisTransitionGeometryActuator;
|
import art.arcane.iris.generation.stage.IrisTransitionGeometryActuator;
|
||||||
import art.arcane.iris.integration.Identifier;
|
import art.arcane.iris.integration.Identifier;
|
||||||
import art.arcane.iris.generation.decoration.tree.TreeBlockMaterial;
|
import art.arcane.iris.generation.decoration.tree.TreeBlockMaterial;
|
||||||
@@ -215,7 +215,7 @@ public interface EngineMode extends Staged {
|
|||||||
try (IrisContext.Scope chunkScope = IrisContext.open(getEngine(), generationSessionId, ctx)) {
|
try (IrisContext.Scope chunkScope = IrisContext.open(getEngine(), generationSessionId, ctx)) {
|
||||||
generateTerrain(x, z, blocks, biomes, multicore, ctx);
|
generateTerrain(x, z, blocks, biomes, multicore, ctx);
|
||||||
recordNaturalTerrain(x, z, blocks, biomes, ctx);
|
recordNaturalTerrain(x, z, blocks, biomes, ctx);
|
||||||
IrisBiomeActuator.publishNaturalMetadata(getEngine(), x, z, biomes, ctx);
|
IrisDimensionStackActuator.clearHostNaturalMetadata(getEngine(), x, z, biomes.getHeight(), ctx);
|
||||||
ctx.beginContent();
|
ctx.beginContent();
|
||||||
List<Matter> continuations = restoreObjectContinuations(x, z);
|
List<Matter> continuations = restoreObjectContinuations(x, z);
|
||||||
for (EngineStage i : stages) {
|
for (EngineStage i : stages) {
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ final class EngineRuntimeBuilder {
|
|||||||
mantleStorageDirectory,
|
mantleStorageDirectory,
|
||||||
selectRuntimeKernel(kernelVersion),
|
selectRuntimeKernel(kernelVersion),
|
||||||
transitionPlan);
|
transitionPlan);
|
||||||
engine.runtimeAssembly.set(assembly);
|
engine.threadState.setAssembly(assembly);
|
||||||
try (IrisContext.Scope ignored = IrisContext.open(engine, engine.getGenerationSessions().currentSessionId(), null)) {
|
try (IrisContext.Scope ignored = IrisContext.open(engine, engine.getGenerationSessions().currentSessionId(), null)) {
|
||||||
IrisLogging.debug("Setup Engine " + assembly.cacheId);
|
IrisLogging.debug("Setup Engine " + assembly.cacheId);
|
||||||
long started = M.ms();
|
long started = M.ms();
|
||||||
@@ -148,7 +148,7 @@ final class EngineRuntimeBuilder {
|
|||||||
}
|
}
|
||||||
throw new IllegalStateException("Failed to build a complete Iris engine runtime.", e);
|
throw new IllegalStateException("Failed to build a complete Iris engine runtime.", e);
|
||||||
} finally {
|
} finally {
|
||||||
engine.runtimeAssembly.remove();
|
engine.threadState.setAssembly(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ final class EngineRuntimeBuilder {
|
|||||||
transitionPlan);
|
transitionPlan);
|
||||||
PreparedHydrologyCacheIdentity cacheIdentity = PreparedHydrologyCacheIdentity.capture(
|
PreparedHydrologyCacheIdentity cacheIdentity = PreparedHydrologyCacheIdentity.capture(
|
||||||
runtimeTarget, assembly.runtimeKernel, transitionPlan, engine.isStudio());
|
runtimeTarget, assembly.runtimeKernel, transitionPlan, engine.isStudio());
|
||||||
engine.runtimeAssembly.set(assembly);
|
engine.threadState.setAssembly(assembly);
|
||||||
try (IrisContext.Scope ignored = IrisContext.open(engine, engine.getGenerationSessions().currentSessionId(), null)) {
|
try (IrisContext.Scope ignored = IrisContext.open(engine, engine.getGenerationSessions().currentSessionId(), null)) {
|
||||||
IrisLogging.debug("Setup Detached Generation Runtime " + assembly.cacheId);
|
IrisLogging.debug("Setup Detached Generation Runtime " + assembly.cacheId);
|
||||||
assembly.mantle = assembly.runtimeKernel.createMantle(engine, assembly.mantleStorageDirectory);
|
assembly.mantle = assembly.runtimeKernel.createMantle(engine, assembly.mantleStorageDirectory);
|
||||||
@@ -201,7 +201,7 @@ final class EngineRuntimeBuilder {
|
|||||||
}
|
}
|
||||||
throw new IllegalStateException("Failed to build a detached Iris generation runtime.", e);
|
throw new IllegalStateException("Failed to build a detached Iris generation runtime.", e);
|
||||||
} finally {
|
} finally {
|
||||||
engine.runtimeAssembly.remove();
|
engine.threadState.setAssembly(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -264,20 +264,16 @@ final class EngineShutdownSequence {
|
|||||||
return failure;
|
return failure;
|
||||||
}
|
}
|
||||||
incompleteAssemblies.add(assembly);
|
incompleteAssemblies.add(assembly);
|
||||||
RuntimeAssembly previous = engine == null ? null : engine.runtimeAssembly.get();
|
RuntimeAssembly previous = engine == null ? null : engine.threadState.assembly();
|
||||||
if (engine != null) {
|
if (engine != null) {
|
||||||
engine.runtimeAssembly.set(assembly);
|
engine.threadState.setAssembly(assembly);
|
||||||
}
|
}
|
||||||
Throwable assemblyFailure;
|
Throwable assemblyFailure;
|
||||||
try {
|
try {
|
||||||
assemblyFailure = closeAssemblyResources(assembly);
|
assemblyFailure = closeAssemblyResources(assembly);
|
||||||
} finally {
|
} finally {
|
||||||
if (engine != null) {
|
if (engine != null) {
|
||||||
if (previous == null) {
|
engine.threadState.setAssembly(previous);
|
||||||
engine.runtimeAssembly.remove();
|
|
||||||
} else {
|
|
||||||
engine.runtimeAssembly.set(previous);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (assemblyFailure == null) {
|
if (assemblyFailure == null) {
|
||||||
@@ -401,7 +397,7 @@ final class EngineShutdownSequence {
|
|||||||
engine,
|
engine,
|
||||||
generationRuntime);
|
generationRuntime);
|
||||||
Throwable runtimeFailure;
|
Throwable runtimeFailure;
|
||||||
try (IrisEngine.GenerationRuntimeScope ignored = engine.generationRuntimeScopes.open(binding)) {
|
try (IrisEngine.GenerationRuntimeScope ignored = engine.threadState.open(binding)) {
|
||||||
runtimeFailure = closeGenerationRuntime(generationRuntime, null);
|
runtimeFailure = closeGenerationRuntime(generationRuntime, null);
|
||||||
} catch (Throwable scopeFailure) {
|
} catch (Throwable scopeFailure) {
|
||||||
runtimeFailure = scopeFailure;
|
runtimeFailure = scopeFailure;
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* 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.runtime;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.runtime.EngineRuntimeBuilder.RuntimeAssembly;
|
||||||
|
import art.arcane.iris.generation.runtime.IrisEngine.BiomeEnvironmentBinding;
|
||||||
|
import art.arcane.iris.generation.runtime.IrisEngine.GenerationRuntimeBinding;
|
||||||
|
import art.arcane.iris.generation.runtime.IrisEngine.GenerationRuntimeScope;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReferenceArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every per-thread engine binding (runtime assembly, biome environment, generation runtime scope) in one frame.
|
||||||
|
* The engine getters run on generation hot paths, where a ThreadLocal lookup probes long collision chains in the
|
||||||
|
* workers' crowded thread-local maps. Frames therefore live in a table indexed by thread id and owned by their
|
||||||
|
* thread; a frame whose slot is taken by another thread falls back to a ThreadLocal.
|
||||||
|
*/
|
||||||
|
final class EngineThreadState {
|
||||||
|
private static final int SLOTS = 256;
|
||||||
|
|
||||||
|
private final AtomicReferenceArray<Frame> slots = new AtomicReferenceArray<>(SLOTS);
|
||||||
|
private final ThreadLocal<Frame> overflow = new ThreadLocal<>();
|
||||||
|
private final AtomicInteger overflowFrames = new AtomicInteger();
|
||||||
|
|
||||||
|
Frame current() {
|
||||||
|
Thread thread = Thread.currentThread();
|
||||||
|
Frame frame = slots.getPlain(slot(thread));
|
||||||
|
if (frame != null && frame.owner == thread) {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
return overflowFrames.get() == 0 ? null : overflow.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
GenerationRuntimeBinding binding() {
|
||||||
|
Frame frame = current();
|
||||||
|
return frame == null ? null : frame.binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeAssembly assembly() {
|
||||||
|
Frame frame = current();
|
||||||
|
return frame == null ? null : frame.assembly;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAssembly(RuntimeAssembly assembly) {
|
||||||
|
if (assembly != null) {
|
||||||
|
frame().assembly = assembly;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Frame frame = current();
|
||||||
|
if (frame != null) {
|
||||||
|
frame.assembly = null;
|
||||||
|
releaseIfEmpty(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BiomeEnvironmentBinding environment() {
|
||||||
|
Frame frame = current();
|
||||||
|
return frame == null ? null : frame.environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setEnvironment(BiomeEnvironmentBinding environment) {
|
||||||
|
if (environment != null) {
|
||||||
|
frame().environment = environment;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Frame frame = current();
|
||||||
|
if (frame != null) {
|
||||||
|
frame.environment = null;
|
||||||
|
releaseIfEmpty(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GenerationRuntimeScope open(GenerationRuntimeBinding binding) {
|
||||||
|
Thread owner = Thread.currentThread();
|
||||||
|
Frame frame = frame();
|
||||||
|
GenerationRuntimeBinding previous = frame.binding;
|
||||||
|
frame.binding = binding;
|
||||||
|
return new GenerationRuntimeScope(this, owner, previous, binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
void close(
|
||||||
|
Thread owner,
|
||||||
|
GenerationRuntimeBinding previous,
|
||||||
|
GenerationRuntimeBinding installed
|
||||||
|
) {
|
||||||
|
if (Thread.currentThread() != owner) {
|
||||||
|
throw new IllegalStateException("Iris generation runtime scope closed from a different thread.");
|
||||||
|
}
|
||||||
|
Frame frame = current();
|
||||||
|
if ((frame == null ? null : frame.binding) != installed) {
|
||||||
|
throw new IllegalStateException("Iris generation runtime scopes must close in LIFO order.");
|
||||||
|
}
|
||||||
|
if (previous != null) {
|
||||||
|
frame().binding = previous;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (frame != null) {
|
||||||
|
frame.binding = null;
|
||||||
|
releaseIfEmpty(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Frame frame() {
|
||||||
|
Frame frame = current();
|
||||||
|
if (frame != null) {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
Thread thread = Thread.currentThread();
|
||||||
|
frame = new Frame(thread);
|
||||||
|
if (!slots.compareAndSet(slot(thread), null, frame)) {
|
||||||
|
frame.overflow = true;
|
||||||
|
overflow.set(frame);
|
||||||
|
overflowFrames.incrementAndGet();
|
||||||
|
}
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseIfEmpty(Frame frame) {
|
||||||
|
if (frame.assembly != null || frame.environment != null || frame.binding != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (frame.overflow) {
|
||||||
|
overflow.remove();
|
||||||
|
overflowFrames.decrementAndGet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
slots.compareAndSet(slot(frame.owner), frame, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int slot(Thread thread) {
|
||||||
|
return (int) thread.threadId() & (SLOTS - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class Frame {
|
||||||
|
private final Thread owner;
|
||||||
|
private boolean overflow;
|
||||||
|
RuntimeAssembly assembly;
|
||||||
|
BiomeEnvironmentBinding environment;
|
||||||
|
GenerationRuntimeBinding binding;
|
||||||
|
|
||||||
|
private Frame(Thread owner) {
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-55
@@ -1,55 +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.runtime;
|
|
||||||
|
|
||||||
import art.arcane.iris.generation.runtime.IrisEngine.GenerationRuntimeBinding;
|
|
||||||
import art.arcane.iris.generation.runtime.IrisEngine.GenerationRuntimeScope;
|
|
||||||
|
|
||||||
final class GenerationRuntimeScopeState {
|
|
||||||
private final ThreadLocal<GenerationRuntimeBinding> current = new ThreadLocal<>();
|
|
||||||
|
|
||||||
GenerationRuntimeBinding current() {
|
|
||||||
return current.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
GenerationRuntimeScope open(GenerationRuntimeBinding binding) {
|
|
||||||
Thread owner = Thread.currentThread();
|
|
||||||
GenerationRuntimeBinding previous = current.get();
|
|
||||||
current.set(binding);
|
|
||||||
return new GenerationRuntimeScope(this, owner, previous, binding);
|
|
||||||
}
|
|
||||||
|
|
||||||
void close(
|
|
||||||
Thread owner,
|
|
||||||
GenerationRuntimeBinding previous,
|
|
||||||
GenerationRuntimeBinding installed
|
|
||||||
) {
|
|
||||||
if (Thread.currentThread() != owner) {
|
|
||||||
throw new IllegalStateException("Iris generation runtime scope closed from a different thread.");
|
|
||||||
}
|
|
||||||
if (current.get() != installed) {
|
|
||||||
throw new IllegalStateException("Iris generation runtime scopes must close in LIFO order.");
|
|
||||||
}
|
|
||||||
if (previous == null) {
|
|
||||||
current.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
current.set(previous);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -48,6 +48,7 @@ import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
|||||||
import art.arcane.volmlib.util.atomics.AtomicRollingSequence;
|
import art.arcane.volmlib.util.atomics.AtomicRollingSequence;
|
||||||
import art.arcane.iris.generation.context.ChunkContext;
|
import art.arcane.iris.generation.context.ChunkContext;
|
||||||
import art.arcane.iris.generation.context.IrisContext;
|
import art.arcane.iris.generation.context.IrisContext;
|
||||||
|
import art.arcane.iris.generation.chunk.ColumnExtentListeningHunk;
|
||||||
import art.arcane.volmlib.util.documentation.BlockCoordinates;
|
import art.arcane.volmlib.util.documentation.BlockCoordinates;
|
||||||
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||||
import art.arcane.volmlib.util.hunk.Hunk;
|
import art.arcane.volmlib.util.hunk.Hunk;
|
||||||
@@ -116,13 +117,7 @@ public class IrisEngine implements Engine {
|
|||||||
final Object generationHistoryRuntimeRouterLock = new Object();
|
final Object generationHistoryRuntimeRouterLock = new Object();
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
@Setter(AccessLevel.NONE)
|
@Setter(AccessLevel.NONE)
|
||||||
final ThreadLocal<RuntimeAssembly> runtimeAssembly = new ThreadLocal<>();
|
final EngineThreadState threadState = new EngineThreadState();
|
||||||
@Getter(AccessLevel.NONE)
|
|
||||||
@Setter(AccessLevel.NONE)
|
|
||||||
final ThreadLocal<BiomeEnvironmentBinding> biomeEnvironmentScopes = new ThreadLocal<>();
|
|
||||||
@Getter(AccessLevel.NONE)
|
|
||||||
@Setter(AccessLevel.NONE)
|
|
||||||
final GenerationRuntimeScopeState generationRuntimeScopes = new GenerationRuntimeScopeState();
|
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
@Setter(AccessLevel.NONE)
|
@Setter(AccessLevel.NONE)
|
||||||
final Set<GenerationRuntime> detachedGenerationRuntimes = Collections.synchronizedSet(
|
final Set<GenerationRuntime> detachedGenerationRuntimes = Collections.synchronizedSet(
|
||||||
@@ -382,7 +377,7 @@ public class IrisEngine implements Engine {
|
|||||||
throw new IllegalStateException("World entry hydrology preparation could not acquire its generation session.", failure);
|
throw new IllegalStateException("World entry hydrology preparation could not acquire its generation session.", failure);
|
||||||
}
|
}
|
||||||
try (lease;
|
try (lease;
|
||||||
GenerationRuntimeScope runtimeScope = generationRuntimeScopes.open(binding);
|
GenerationRuntimeScope runtimeScope = threadState.open(binding);
|
||||||
IrisContext.Scope context = IrisContext.open(this, lease.sessionId(), null)) {
|
IrisContext.Scope context = IrisContext.open(this, lease.sessionId(), null)) {
|
||||||
getComplex().getHydrologyRuntime().prepareChunkColumns(blockX, blockZ);
|
getComplex().getHydrologyRuntime().prepareChunkColumns(blockX, blockZ);
|
||||||
} catch (Exception failure) {
|
} catch (Exception failure) {
|
||||||
@@ -572,8 +567,8 @@ public class IrisEngine implements Engine {
|
|||||||
@Override
|
@Override
|
||||||
public BiomeEnvironment.Scope openBiomeEnvironmentScope(BiomeEnvironment environment) {
|
public BiomeEnvironment.Scope openBiomeEnvironmentScope(BiomeEnvironment environment) {
|
||||||
BiomeEnvironmentBinding binding = new BiomeEnvironmentBinding(
|
BiomeEnvironmentBinding binding = new BiomeEnvironmentBinding(
|
||||||
Objects.requireNonNull(environment, "environment"), biomeEnvironmentScopes.get());
|
Objects.requireNonNull(environment, "environment"), threadState.environment());
|
||||||
biomeEnvironmentScopes.set(binding);
|
threadState.setEnvironment(binding);
|
||||||
return binding;
|
return binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -979,7 +974,7 @@ public class IrisEngine implements Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasGenerationRuntimeScope() {
|
public boolean hasGenerationRuntimeScope() {
|
||||||
return generationRuntimeScopes.current() != null;
|
return threadState.binding() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean usesScopedNaturalTerrain() {
|
private boolean usesScopedNaturalTerrain() {
|
||||||
@@ -1058,7 +1053,7 @@ public class IrisEngine implements Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public GenerationRuntimeBinding captureGenerationRuntimeBinding() {
|
public GenerationRuntimeBinding captureGenerationRuntimeBinding() {
|
||||||
GenerationRuntimeBinding scoped = generationRuntimeScopes.current();
|
GenerationRuntimeBinding scoped = threadState.binding();
|
||||||
return scoped == null ? getActiveGenerationRuntimeBinding() : scoped;
|
return scoped == null ? getActiveGenerationRuntimeBinding() : scoped;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1074,7 +1069,7 @@ public class IrisEngine implements Engine {
|
|||||||
if (!isGenerationRuntimeBindingLive(required)) {
|
if (!isGenerationRuntimeBindingLive(required)) {
|
||||||
throw new IllegalStateException("Iris generation runtime binding is closed or no longer owned.");
|
throw new IllegalStateException("Iris generation runtime binding is closed or no longer owned.");
|
||||||
}
|
}
|
||||||
return generationRuntimeScopes.open(required);
|
return threadState.open(required);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1265,75 +1260,85 @@ public class IrisEngine implements Engine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SeedManager getSeedManager() {
|
public SeedManager getSeedManager() {
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
EngineThreadState.Frame frame = threadState.current();
|
||||||
|
RuntimeAssembly assembly = frame == null ? null : frame.assembly;
|
||||||
if (assembly != null) {
|
if (assembly != null) {
|
||||||
return assembly.seedManager;
|
return assembly.seedManager;
|
||||||
}
|
}
|
||||||
GenerationRuntime current = selectedGenerationRuntime();
|
GenerationRuntime current = selectedGenerationRuntime(frame);
|
||||||
return current == null ? seedManager : current.seedManager();
|
return current == null ? seedManager : current.seedManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IrisComplex getComplex() {
|
public IrisComplex getComplex() {
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
EngineThreadState.Frame frame = threadState.current();
|
||||||
|
RuntimeAssembly assembly = frame == null ? null : frame.assembly;
|
||||||
if (assembly != null && assembly.complex != null) {
|
if (assembly != null && assembly.complex != null) {
|
||||||
return assembly.complex;
|
return assembly.complex;
|
||||||
}
|
}
|
||||||
GenerationRuntime current = selectedGenerationRuntime();
|
GenerationRuntime current = selectedGenerationRuntime(frame);
|
||||||
return current == null ? null : current.complex();
|
return current == null ? null : current.complex();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EngineTarget getTarget() {
|
public EngineTarget getTarget() {
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
EngineThreadState.Frame frame = threadState.current();
|
||||||
|
RuntimeAssembly assembly = frame == null ? null : frame.assembly;
|
||||||
if (assembly != null) {
|
if (assembly != null) {
|
||||||
return assembly.target;
|
return assembly.target;
|
||||||
}
|
}
|
||||||
GenerationRuntime current = selectedGenerationRuntime();
|
GenerationRuntime current = selectedGenerationRuntime(frame);
|
||||||
return current == null ? publishedTarget : current.target();
|
return current == null ? publishedTarget : current.target();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IrisData getData() {
|
public IrisData getData() {
|
||||||
BiomeEnvironmentBinding environment = biomeEnvironmentScopes.get();
|
EngineThreadState.Frame frame = threadState.current();
|
||||||
if (environment != null && !hasGenerationRuntimeScope()) {
|
if (frame != null) {
|
||||||
return environment.environment.data();
|
BiomeEnvironmentBinding environment = frame.environment;
|
||||||
|
if (environment != null && frame.binding == null) {
|
||||||
|
return environment.environment.data();
|
||||||
|
}
|
||||||
|
RuntimeAssembly assembly = frame.assembly;
|
||||||
|
if (assembly != null) {
|
||||||
|
return assembly.target.getData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
GenerationRuntime current = selectedGenerationRuntime(frame);
|
||||||
if (assembly != null) {
|
|
||||||
return assembly.target.getData();
|
|
||||||
}
|
|
||||||
GenerationRuntime current = selectedGenerationRuntime();
|
|
||||||
return current == null ? publishedTarget.getData() : current.data();
|
return current == null ? publishedTarget.getData() : current.data();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IrisDimension getDimension() {
|
public IrisDimension getDimension() {
|
||||||
BiomeEnvironmentBinding environment = biomeEnvironmentScopes.get();
|
EngineThreadState.Frame frame = threadState.current();
|
||||||
if (environment != null && !hasGenerationRuntimeScope()) {
|
if (frame != null) {
|
||||||
return environment.environment.dimension();
|
BiomeEnvironmentBinding environment = frame.environment;
|
||||||
|
if (environment != null && frame.binding == null) {
|
||||||
|
return environment.environment.dimension();
|
||||||
|
}
|
||||||
|
RuntimeAssembly assembly = frame.assembly;
|
||||||
|
if (assembly != null) {
|
||||||
|
return assembly.target.getDimension();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
GenerationRuntime current = selectedGenerationRuntime(frame);
|
||||||
if (assembly != null) {
|
|
||||||
return assembly.target.getDimension();
|
|
||||||
}
|
|
||||||
GenerationRuntime current = selectedGenerationRuntime();
|
|
||||||
return current == null ? publishedTarget.getDimension() : current.dimension();
|
return current == null ? publishedTarget.getDimension() : current.dimension();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EngineMantle getMantle() {
|
public EngineMantle getMantle() {
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
EngineThreadState.Frame frame = threadState.current();
|
||||||
|
RuntimeAssembly assembly = frame == null ? null : frame.assembly;
|
||||||
if (assembly != null && assembly.mantle != null) {
|
if (assembly != null && assembly.mantle != null) {
|
||||||
return assembly.mantle;
|
return assembly.mantle;
|
||||||
}
|
}
|
||||||
GenerationRuntime current = selectedGenerationRuntime();
|
GenerationRuntime current = selectedGenerationRuntime(frame);
|
||||||
return current == null ? null : current.mantle();
|
return current == null ? null : current.mantle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EngineMode getMode() {
|
public EngineMode getMode() {
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
RuntimeAssembly assembly = threadState.assembly();
|
||||||
if (assembly != null && assembly.mode != null) {
|
if (assembly != null && assembly.mode != null) {
|
||||||
return assembly.mode;
|
return assembly.mode;
|
||||||
}
|
}
|
||||||
@@ -1343,7 +1348,7 @@ public class IrisEngine implements Engine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EngineEffects getEffects() {
|
public EngineEffects getEffects() {
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
RuntimeAssembly assembly = threadState.assembly();
|
||||||
if (assembly != null && assembly.effects != null) {
|
if (assembly != null && assembly.effects != null) {
|
||||||
return assembly.effects;
|
return assembly.effects;
|
||||||
}
|
}
|
||||||
@@ -1353,7 +1358,7 @@ public class IrisEngine implements Engine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EngineWorldManager getWorldManager() {
|
public EngineWorldManager getWorldManager() {
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
RuntimeAssembly assembly = threadState.assembly();
|
||||||
if (assembly != null && assembly.worldManager != null) {
|
if (assembly != null && assembly.worldManager != null) {
|
||||||
return assembly.worldManager;
|
return assembly.worldManager;
|
||||||
}
|
}
|
||||||
@@ -1363,27 +1368,29 @@ public class IrisEngine implements Engine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UpperDimensionContext getUpperContext() {
|
public UpperDimensionContext getUpperContext() {
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
EngineThreadState.Frame frame = threadState.current();
|
||||||
|
RuntimeAssembly assembly = frame == null ? null : frame.assembly;
|
||||||
if (assembly != null) {
|
if (assembly != null) {
|
||||||
return assembly.upperContext;
|
return assembly.upperContext;
|
||||||
}
|
}
|
||||||
GenerationRuntime current = selectedGenerationRuntime();
|
GenerationRuntime current = selectedGenerationRuntime(frame);
|
||||||
return current == null ? null : current.upperContext();
|
return current == null ? null : current.upperContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DimensionStackContext getDimensionStackContext() {
|
public DimensionStackContext getDimensionStackContext() {
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
EngineThreadState.Frame frame = threadState.current();
|
||||||
|
RuntimeAssembly assembly = frame == null ? null : frame.assembly;
|
||||||
if (assembly != null) {
|
if (assembly != null) {
|
||||||
return assembly.dimensionStackContext;
|
return assembly.dimensionStackContext;
|
||||||
}
|
}
|
||||||
GenerationRuntime current = selectedGenerationRuntime();
|
GenerationRuntime current = selectedGenerationRuntime(frame);
|
||||||
return current == null ? null : current.dimensionStackContext();
|
return current == null ? null : current.dimensionStackContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Long> getHash32() {
|
public CompletableFuture<Long> getHash32() {
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
RuntimeAssembly assembly = threadState.assembly();
|
||||||
if (assembly != null && assembly.hash32 != null) {
|
if (assembly != null && assembly.hash32 != null) {
|
||||||
return assembly.hash32;
|
return assembly.hash32;
|
||||||
}
|
}
|
||||||
@@ -1465,7 +1472,7 @@ public class IrisEngine implements Engine {
|
|||||||
IrisContext.Scope generationScope = IrisContext.open(this, lease.sessionId(), null)) {
|
IrisContext.Scope generationScope = IrisContext.open(this, lease.sessionId(), null)) {
|
||||||
getEngineData().getStatistics().generatedChunk();
|
getEngineData().getStatistics().generatedChunk();
|
||||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||||
Hunk<NativeBlockState> blocks = vblocks.listen((xx, y, zz, t) -> catchBlockUpdates(x + xx, y, z + zz, t));
|
Hunk<NativeBlockState> blocks = new ColumnExtentListeningHunk<>(vblocks, (xx, y, zz, t) -> catchBlockUpdates(x + xx, y, z + zz, t));
|
||||||
|
|
||||||
if (getDimension().isDebugChunkCrossSections() && ((x >> 4) % getDimension().getDebugCrossSectionsMod() == 0 || (z >> 4) % getDimension().getDebugCrossSectionsMod() == 0)) {
|
if (getDimension().isDebugChunkCrossSections() && ((x >> 4) % getDimension().getDebugCrossSectionsMod() == 0 || (z >> 4) % getDimension().getDebugCrossSectionsMod() == 0)) {
|
||||||
NativeBlockState crossSection = B.getState("CRYING_OBSIDIAN");
|
NativeBlockState crossSection = B.getState("CRYING_OBSIDIAN");
|
||||||
@@ -1593,16 +1600,21 @@ public class IrisEngine implements Engine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCacheID() {
|
public int getCacheID() {
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
EngineThreadState.Frame frame = threadState.current();
|
||||||
|
RuntimeAssembly assembly = frame == null ? null : frame.assembly;
|
||||||
if (assembly != null) {
|
if (assembly != null) {
|
||||||
return assembly.cacheId;
|
return assembly.cacheId;
|
||||||
}
|
}
|
||||||
GenerationRuntime current = selectedGenerationRuntime();
|
GenerationRuntime current = selectedGenerationRuntime(frame);
|
||||||
return current == null ? -1 : current.cacheId();
|
return current == null ? -1 : current.cacheId();
|
||||||
}
|
}
|
||||||
|
|
||||||
private GenerationRuntime selectedGenerationRuntime() {
|
private GenerationRuntime selectedGenerationRuntime() {
|
||||||
GenerationRuntimeBinding scoped = generationRuntimeScopes.current();
|
return selectedGenerationRuntime(threadState.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
private GenerationRuntime selectedGenerationRuntime(EngineThreadState.Frame frame) {
|
||||||
|
GenerationRuntimeBinding scoped = frame == null ? null : frame.binding;
|
||||||
if (scoped != null && isGenerationRuntimeBindingLive(scoped)) {
|
if (scoped != null && isGenerationRuntimeBindingLive(scoped)) {
|
||||||
return scoped.runtime;
|
return scoped.runtime;
|
||||||
}
|
}
|
||||||
@@ -1611,7 +1623,7 @@ public class IrisEngine implements Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public GenerationKernelRegistry.Version getGenerationKernelVersion() {
|
public GenerationKernelRegistry.Version getGenerationKernelVersion() {
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
RuntimeAssembly assembly = threadState.assembly();
|
||||||
if (assembly != null) {
|
if (assembly != null) {
|
||||||
return assembly.kernelVersion;
|
return assembly.kernelVersion;
|
||||||
}
|
}
|
||||||
@@ -1619,7 +1631,7 @@ public class IrisEngine implements Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public GenerationKernelRegistry.RuntimeKernel getGenerationRuntimeKernel() {
|
public GenerationKernelRegistry.RuntimeKernel getGenerationRuntimeKernel() {
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
RuntimeAssembly assembly = threadState.assembly();
|
||||||
if (assembly != null) {
|
if (assembly != null) {
|
||||||
return assembly.runtimeKernel;
|
return assembly.runtimeKernel;
|
||||||
}
|
}
|
||||||
@@ -1627,7 +1639,7 @@ public class IrisEngine implements Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TransitionGenerationPlan getTransitionGenerationPlan() {
|
public TransitionGenerationPlan getTransitionGenerationPlan() {
|
||||||
RuntimeAssembly assembly = runtimeAssembly.get();
|
RuntimeAssembly assembly = threadState.assembly();
|
||||||
if (assembly != null) {
|
if (assembly != null) {
|
||||||
return assembly.transitionPlan;
|
return assembly.transitionPlan;
|
||||||
}
|
}
|
||||||
@@ -1751,14 +1763,14 @@ public class IrisEngine implements Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final class GenerationRuntimeScope implements AutoCloseable {
|
public static final class GenerationRuntimeScope implements AutoCloseable {
|
||||||
private final GenerationRuntimeScopeState state;
|
private final EngineThreadState state;
|
||||||
private final Thread owner;
|
private final Thread owner;
|
||||||
private final GenerationRuntimeBinding previous;
|
private final GenerationRuntimeBinding previous;
|
||||||
private final GenerationRuntimeBinding installed;
|
private final GenerationRuntimeBinding installed;
|
||||||
private boolean closed;
|
private boolean closed;
|
||||||
|
|
||||||
GenerationRuntimeScope(
|
GenerationRuntimeScope(
|
||||||
GenerationRuntimeScopeState state,
|
EngineThreadState state,
|
||||||
Thread owner,
|
Thread owner,
|
||||||
GenerationRuntimeBinding previous,
|
GenerationRuntimeBinding previous,
|
||||||
GenerationRuntimeBinding installed
|
GenerationRuntimeBinding installed
|
||||||
@@ -1780,7 +1792,7 @@ public class IrisEngine implements Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private final class BiomeEnvironmentBinding implements BiomeEnvironment.Scope {
|
final class BiomeEnvironmentBinding implements BiomeEnvironment.Scope {
|
||||||
private final BiomeEnvironment environment;
|
private final BiomeEnvironment environment;
|
||||||
private final BiomeEnvironmentBinding previous;
|
private final BiomeEnvironmentBinding previous;
|
||||||
private final Thread owner = Thread.currentThread();
|
private final Thread owner = Thread.currentThread();
|
||||||
@@ -1799,14 +1811,10 @@ public class IrisEngine implements Engine {
|
|||||||
if (closed) {
|
if (closed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (biomeEnvironmentScopes.get() != this) {
|
if (threadState.environment() != this) {
|
||||||
throw new IllegalStateException("Biome environment scopes must close in reverse order.");
|
throw new IllegalStateException("Biome environment scopes must close in reverse order.");
|
||||||
}
|
}
|
||||||
if (previous == null) {
|
threadState.setEnvironment(previous);
|
||||||
biomeEnvironmentScopes.remove();
|
|
||||||
} else {
|
|
||||||
biomeEnvironmentScopes.set(previous);
|
|
||||||
}
|
|
||||||
closed = true;
|
closed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import art.arcane.iris.world.history.TransitionGenerationPlan;
|
|||||||
import art.arcane.iris.generation.biome.IrisBiome;
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
import art.arcane.iris.generation.biome.IrisBiomeCustom;
|
import art.arcane.iris.generation.biome.IrisBiomeCustom;
|
||||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||||
import art.arcane.iris.spi.IrisPlatform;
|
|
||||||
import art.arcane.iris.spi.IrisPlatforms;
|
import art.arcane.iris.spi.IrisPlatforms;
|
||||||
import art.arcane.volmlib.nativelib.terrain.NativeBiome;
|
import art.arcane.volmlib.nativelib.terrain.NativeBiome;
|
||||||
import art.arcane.iris.generation.context.ChunkContext;
|
import art.arcane.iris.generation.context.ChunkContext;
|
||||||
@@ -35,12 +34,7 @@ import art.arcane.iris.generation.context.ChunkedDataCache;
|
|||||||
import art.arcane.volmlib.util.hunk.Hunk;
|
import art.arcane.volmlib.util.hunk.Hunk;
|
||||||
import art.arcane.volmlib.util.collection.KMap;
|
import art.arcane.volmlib.util.collection.KMap;
|
||||||
import art.arcane.volmlib.util.documentation.BlockCoordinates;
|
import art.arcane.volmlib.util.documentation.BlockCoordinates;
|
||||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
|
||||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
import art.arcane.volmlib.util.matter.Matter;
|
|
||||||
import art.arcane.volmlib.util.matter.MatterBiomeInject;
|
|
||||||
import art.arcane.volmlib.util.matter.slices.BiomeInjectMatter;
|
|
||||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -48,8 +42,8 @@ import java.util.Objects;
|
|||||||
|
|
||||||
public class IrisBiomeActuator extends EngineAssignedActuator<NativeBiome> {
|
public class IrisBiomeActuator extends EngineAssignedActuator<NativeBiome> {
|
||||||
private final RNG rng;
|
private final RNG rng;
|
||||||
private final KMap<String, ResolvedBiome> resolvedBiomes = new KMap<>();
|
private final KMap<String, NativeBiome> resolvedBiomes = new KMap<>();
|
||||||
private final KMap<String, ResolvedBiome> resolvedPhysicalBiomes = new KMap<>();
|
private final KMap<String, NativeBiome> resolvedPhysicalBiomes = new KMap<>();
|
||||||
|
|
||||||
public IrisBiomeActuator(Engine engine) {
|
public IrisBiomeActuator(Engine engine) {
|
||||||
super(engine, "Biome");
|
super(engine, "Biome");
|
||||||
@@ -64,7 +58,6 @@ public class IrisBiomeActuator extends EngineAssignedActuator<NativeBiome> {
|
|||||||
int depth = h.getDepth();
|
int depth = h.getDepth();
|
||||||
int height = h.getHeight();
|
int height = h.getHeight();
|
||||||
Engine engine = getEngine();
|
Engine engine = getEngine();
|
||||||
Mantle<Matter> mantle = engine.getMantle().getMantle();
|
|
||||||
ChunkedDataCache<IrisBiome> biomeCache = context.getBiome();
|
ChunkedDataCache<IrisBiome> biomeCache = context.getBiome();
|
||||||
IrisComplex complex = context.getComplex();
|
IrisComplex complex = context.getComplex();
|
||||||
DimensionStackContext dimensionStackContext = engine.getDimensionStackContext();
|
DimensionStackContext dimensionStackContext = engine.getDimensionStackContext();
|
||||||
@@ -74,7 +67,7 @@ public class IrisBiomeActuator extends EngineAssignedActuator<NativeBiome> {
|
|||||||
int worldX = x + xf;
|
int worldX = x + xf;
|
||||||
int worldZ = z + zf;
|
int worldZ = z + zf;
|
||||||
IrisBiome biome = biomeCache.get(xf, zf);
|
IrisBiome biome = biomeCache.get(xf, zf);
|
||||||
ResolvedBiome resolved = resolve(biomeKey(
|
NativeBiome platformBiome = resolve(biomeKey(
|
||||||
biome,
|
biome,
|
||||||
getDimension(),
|
getDimension(),
|
||||||
engine,
|
engine,
|
||||||
@@ -82,8 +75,9 @@ public class IrisBiomeActuator extends EngineAssignedActuator<NativeBiome> {
|
|||||||
0,
|
0,
|
||||||
worldZ
|
worldZ
|
||||||
));
|
));
|
||||||
NativeBiome platformBiome = resolved.biome();
|
if (platformBiome != null) {
|
||||||
writeColumn(h, mantle, xf, zf, worldX, worldZ, height, resolved);
|
h.set(xf, 0, zf, xf, height - 1, zf, platformBiome);
|
||||||
|
}
|
||||||
|
|
||||||
if (dimensionStackContext != null) {
|
if (dimensionStackContext != null) {
|
||||||
NativeBiome bottomBiome = platformBiome == null
|
NativeBiome bottomBiome = platformBiome == null
|
||||||
@@ -95,48 +89,19 @@ public class IrisBiomeActuator extends EngineAssignedActuator<NativeBiome> {
|
|||||||
worldX,
|
worldX,
|
||||||
worldZ,
|
worldZ,
|
||||||
h,
|
h,
|
||||||
mantle,
|
|
||||||
engine,
|
engine,
|
||||||
new ResolvedBiome(bottomBiome, resolved.matter()),
|
bottomBiome,
|
||||||
context.getDimensionStackLayout(xf, zf)
|
context.getDimensionStackLayout(xf, zf)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
writeHistoricalColumn(h, mantle, xf, zf, worldX, worldZ, height, engine, complex);
|
writeHistoricalColumn(h, xf, zf, worldX, worldZ, height, engine, complex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
engine.getMetrics().getBiome().put(p.getMilliseconds());
|
engine.getMetrics().getBiome().put(p.getMilliseconds());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void publishNaturalMetadata(Engine engine, int x, int z, Hunk<NativeBiome> biomes,
|
|
||||||
ChunkContext context) {
|
|
||||||
if (!context.getComplex().allowsMantleChunkWrite(x >> 4, z >> 4)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Mantle<Matter> mantle = engine.getMantle().getMantle();
|
|
||||||
MantleChunk<Matter> chunk = mantle.getChunk(x >> 4, z >> 4).use();
|
|
||||||
try {
|
|
||||||
synchronized (chunk) {
|
|
||||||
IrisDimensionStackActuator.clearNaturalMetadata(chunk, context, biomes.getHeight());
|
|
||||||
chunk.deleteSlices(MatterBiomeInject.class);
|
|
||||||
for (int localX = 0; localX < biomes.getWidth(); localX += 4) {
|
|
||||||
for (int localZ = 0; localZ < biomes.getDepth(); localZ += 4) {
|
|
||||||
for (int y = 0; y < biomes.getHeight(); y += 4) {
|
|
||||||
NativeBiome biome = biomes.getRaw(localX, y, localZ);
|
|
||||||
if (biome != null) {
|
|
||||||
mantle.set(x + localX, y, z + localZ, BiomeInjectMatter.get(biome.key()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
chunk.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeHistoricalColumn(
|
private void writeHistoricalColumn(
|
||||||
Hunk<NativeBiome> output,
|
Hunk<NativeBiome> output,
|
||||||
Mantle<Matter> mantle,
|
|
||||||
int localX,
|
int localX,
|
||||||
int localZ,
|
int localZ,
|
||||||
int worldX,
|
int worldX,
|
||||||
@@ -167,12 +132,9 @@ public class IrisBiomeActuator extends EngineAssignedActuator<NativeBiome> {
|
|||||||
applyBiomeRange(
|
applyBiomeRange(
|
||||||
localX,
|
localX,
|
||||||
localZ,
|
localZ,
|
||||||
worldX,
|
|
||||||
worldZ,
|
|
||||||
rangeStart,
|
rangeStart,
|
||||||
internalY - 1,
|
internalY - 1,
|
||||||
output,
|
output,
|
||||||
mantle,
|
|
||||||
resolvePhysicalKey(activeKey)
|
resolvePhysicalKey(activeKey)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -181,33 +143,14 @@ public class IrisBiomeActuator extends EngineAssignedActuator<NativeBiome> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeColumn(
|
|
||||||
Hunk<NativeBiome> output,
|
|
||||||
Mantle<Matter> mantle,
|
|
||||||
int localX,
|
|
||||||
int localZ,
|
|
||||||
int worldX,
|
|
||||||
int worldZ,
|
|
||||||
int height,
|
|
||||||
ResolvedBiome resolved
|
|
||||||
) {
|
|
||||||
if (resolved.biome() != null) {
|
|
||||||
output.set(localX, 0, localZ, localX, height - 1, localZ, resolved.biome());
|
|
||||||
}
|
|
||||||
if (mantle != null) {
|
|
||||||
mantle.set(worldX, 0, worldZ, resolved.matter());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyDimensionStackBiomes(
|
private void applyDimensionStackBiomes(
|
||||||
int localX,
|
int localX,
|
||||||
int localZ,
|
int localZ,
|
||||||
int worldX,
|
int worldX,
|
||||||
int worldZ,
|
int worldZ,
|
||||||
Hunk<NativeBiome> output,
|
Hunk<NativeBiome> output,
|
||||||
Mantle<Matter> mantle,
|
|
||||||
Engine engine,
|
Engine engine,
|
||||||
ResolvedBiome bottom,
|
NativeBiome bottom,
|
||||||
DimensionStackLayout layout
|
DimensionStackLayout layout
|
||||||
) {
|
) {
|
||||||
List<DimensionStackLayout.Layer> layers = layout.layersBottomToTop();
|
List<DimensionStackLayout.Layer> layers = layout.layersBottomToTop();
|
||||||
@@ -222,18 +165,15 @@ public class IrisBiomeActuator extends EngineAssignedActuator<NativeBiome> {
|
|||||||
applyBiomeRange(
|
applyBiomeRange(
|
||||||
localX,
|
localX,
|
||||||
localZ,
|
localZ,
|
||||||
worldX,
|
|
||||||
worldZ,
|
|
||||||
gapMinY,
|
gapMinY,
|
||||||
gapMaxY,
|
gapMaxY,
|
||||||
output,
|
output,
|
||||||
mantle,
|
|
||||||
bottom
|
bottom
|
||||||
);
|
);
|
||||||
if (!layer.visible()) {
|
if (!layer.visible()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ResolvedBiome resolved = bottom;
|
NativeBiome resolved = bottom;
|
||||||
if (layer.biome() != null) {
|
if (layer.biome() != null) {
|
||||||
IrisDimension dimension = layer.terrainContext().getDimension();
|
IrisDimension dimension = layer.terrainContext().getDimension();
|
||||||
resolved = resolve(biomeKey(
|
resolved = resolve(biomeKey(
|
||||||
@@ -248,12 +188,9 @@ public class IrisBiomeActuator extends EngineAssignedActuator<NativeBiome> {
|
|||||||
applyBiomeRange(
|
applyBiomeRange(
|
||||||
localX,
|
localX,
|
||||||
localZ,
|
localZ,
|
||||||
worldX,
|
|
||||||
worldZ,
|
|
||||||
layer.renderMinY(),
|
layer.renderMinY(),
|
||||||
layer.renderMaxY(),
|
layer.renderMaxY(),
|
||||||
output,
|
output,
|
||||||
mantle,
|
|
||||||
resolved
|
resolved
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -262,46 +199,15 @@ public class IrisBiomeActuator extends EngineAssignedActuator<NativeBiome> {
|
|||||||
private void applyBiomeRange(
|
private void applyBiomeRange(
|
||||||
int localX,
|
int localX,
|
||||||
int localZ,
|
int localZ,
|
||||||
int worldX,
|
|
||||||
int worldZ,
|
|
||||||
int minimumY,
|
int minimumY,
|
||||||
int maximumY,
|
int maximumY,
|
||||||
Hunk<NativeBiome> output,
|
Hunk<NativeBiome> output,
|
||||||
Mantle<Matter> mantle,
|
NativeBiome biome
|
||||||
ResolvedBiome resolved
|
|
||||||
) {
|
) {
|
||||||
if (minimumY > maximumY) {
|
if (minimumY > maximumY || biome == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mantle != null) {
|
output.set(localX, minimumY, localZ, localX, maximumY, localZ, biome);
|
||||||
clearBiomeMatterRange(mantle, worldX, worldZ, minimumY, maximumY);
|
|
||||||
}
|
|
||||||
if (resolved.biome() != null) {
|
|
||||||
output.set(
|
|
||||||
localX,
|
|
||||||
minimumY,
|
|
||||||
localZ,
|
|
||||||
localX,
|
|
||||||
maximumY,
|
|
||||||
localZ,
|
|
||||||
resolved.biome()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (mantle != null && resolved.matter() != null) {
|
|
||||||
mantle.set(worldX, minimumY, worldZ, resolved.matter());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void clearBiomeMatterRange(
|
|
||||||
Mantle<Matter> mantle,
|
|
||||||
int worldX,
|
|
||||||
int worldZ,
|
|
||||||
int minimumY,
|
|
||||||
int maximumY
|
|
||||||
) {
|
|
||||||
for (int y = minimumY; y <= maximumY; y++) {
|
|
||||||
mantle.remove(worldX, y, worldZ, MatterBiomeInject.class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String biomeKey(
|
private String biomeKey(
|
||||||
@@ -319,37 +225,28 @@ public class IrisBiomeActuator extends EngineAssignedActuator<NativeBiome> {
|
|||||||
return biome.getSkyBiomeKey(rng, engine, x, y, z);
|
return biome.getSkyBiomeKey(rng, engine, x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResolvedBiome resolve(String key) {
|
private NativeBiome resolve(String key) {
|
||||||
ResolvedBiome cached = key == null ? null : resolvedBiomes.get(key);
|
NativeBiome cached = key == null ? null : resolvedBiomes.get(key);
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
IrisPlatform platform = IrisPlatforms.get();
|
NativeBiome biome = IrisPlatforms.get().registries().biome(key);
|
||||||
NativeBiome biome = platform.registries().biome(key);
|
|
||||||
ResolvedBiome resolved = new ResolvedBiome(
|
|
||||||
biome,
|
|
||||||
BiomeInjectMatter.get(platform.biomeWriter().biomeIdFor(key))
|
|
||||||
);
|
|
||||||
if (key != null && biome != null) {
|
if (key != null && biome != null) {
|
||||||
resolvedBiomes.put(key, resolved);
|
resolvedBiomes.put(key, biome);
|
||||||
}
|
}
|
||||||
return resolved;
|
return biome;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResolvedBiome resolvePhysicalKey(String key) {
|
private NativeBiome resolvePhysicalKey(String key) {
|
||||||
ResolvedBiome cached = resolvedPhysicalBiomes.get(key);
|
NativeBiome cached = resolvedPhysicalBiomes.get(key);
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
NativeBiome biome = IrisPlatforms.get().registries().biome(key);
|
NativeBiome biome = IrisPlatforms.get().registries().biome(key);
|
||||||
ResolvedBiome resolved = new ResolvedBiome(biome, BiomeInjectMatter.get(key));
|
|
||||||
if (biome != null) {
|
if (biome != null) {
|
||||||
resolvedPhysicalBiomes.put(key, resolved);
|
resolvedPhysicalBiomes.put(key, biome);
|
||||||
}
|
}
|
||||||
return resolved;
|
return biome;
|
||||||
}
|
|
||||||
|
|
||||||
private record ResolvedBiome(NativeBiome biome, MatterBiomeInject matter) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,6 +40,7 @@ import art.arcane.iris.generation.decoration.IrisProceduralBlocks;
|
|||||||
import art.arcane.iris.generation.hydrology.IrisRiverMaterialConfig;
|
import art.arcane.iris.generation.hydrology.IrisRiverMaterialConfig;
|
||||||
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveAction;
|
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveAction;
|
||||||
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveCell;
|
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveCell;
|
||||||
|
import art.arcane.iris.generation.chunk.ColumnExtent;
|
||||||
import art.arcane.iris.generation.context.ChunkContext;
|
import art.arcane.iris.generation.context.ChunkContext;
|
||||||
import art.arcane.iris.generation.context.IrisContext;
|
import art.arcane.iris.generation.context.IrisContext;
|
||||||
import art.arcane.iris.generation.block.B;
|
import art.arcane.iris.generation.block.B;
|
||||||
@@ -896,12 +897,17 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
int width = output.getWidth();
|
int width = output.getWidth();
|
||||||
int depth = output.getDepth();
|
int depth = output.getDepth();
|
||||||
int height = output.getHeight();
|
int height = output.getHeight();
|
||||||
|
ColumnExtent extent = output instanceof ColumnExtent columnExtent ? columnExtent : null;
|
||||||
for (int localX = 0; localX < width; localX++) {
|
for (int localX = 0; localX < width; localX++) {
|
||||||
for (int localZ = 0; localZ < depth; localZ++) {
|
for (int localZ = 0; localZ < depth; localZ++) {
|
||||||
int worldX = blockX + localX;
|
int worldX = blockX + localX;
|
||||||
int worldZ = blockZ + localZ;
|
int worldZ = blockZ + localZ;
|
||||||
int floor = -1;
|
int floor = -1;
|
||||||
|
int top = extent == null ? height - 1 : extent.highestStoredY(localX, localZ);
|
||||||
for (int y = 1; y < height; y++) {
|
for (int y = 1; y < height; y++) {
|
||||||
|
if (y > top) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
NativeBlockState state = output.getRaw(localX, y, localZ);
|
NativeBlockState state = output.getRaw(localX, y, localZ);
|
||||||
if (B.isSolid(state)) {
|
if (B.isSolid(state)) {
|
||||||
if (floor >= 0) {
|
if (floor >= 0) {
|
||||||
@@ -922,6 +928,9 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
worldX, worldZ, resolver, caveBiomes, customBiomes);
|
worldX, worldZ, resolver, caveBiomes, customBiomes);
|
||||||
decorateZone(output, zone, localX, localZ, worldX, worldZ,
|
decorateZone(output, zone, localX, localZ, worldX, worldZ,
|
||||||
floorBiome, ceilingBiome);
|
floorBiome, ceilingBiome);
|
||||||
|
if (extent != null) {
|
||||||
|
top = extent.highestStoredY(localX, localZ);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
floor = -1;
|
floor = -1;
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-1
@@ -106,7 +106,21 @@ public final class IrisDimensionStackActuator extends EngineAssignedActuator<Nat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void clearNaturalMetadata(MantleChunk<Matter> chunk, ChunkContext context, int height) {
|
public static void clearHostNaturalMetadata(Engine engine, int x, int z, int height, ChunkContext context) {
|
||||||
|
if (!context.hasDimensionStack() || !context.getComplex().allowsMantleChunkWrite(x >> 4, z >> 4)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MantleChunk<Matter> chunk = engine.getMantle().getMantle().getChunk(x >> 4, z >> 4).use();
|
||||||
|
try {
|
||||||
|
synchronized (chunk) {
|
||||||
|
clearNaturalMetadata(chunk, context, height);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
chunk.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearNaturalMetadata(MantleChunk<Matter> chunk, ChunkContext context, int height) {
|
||||||
MetadataCleaner metadata = new MetadataCleaner(chunk, height);
|
MetadataCleaner metadata = new MetadataCleaner(chunk, height);
|
||||||
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++) {
|
||||||
|
|||||||
-2
@@ -44,7 +44,6 @@ import art.arcane.volmlib.util.hunk.Hunk;
|
|||||||
import art.arcane.volmlib.util.noise.CNG;
|
import art.arcane.volmlib.util.noise.CNG;
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
import art.arcane.volmlib.util.matter.slices.BiomeInjectMatter;
|
|
||||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||||
import art.arcane.iris.spi.IrisPlatforms;
|
import art.arcane.iris.spi.IrisPlatforms;
|
||||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
@@ -431,7 +430,6 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Nativ
|
|||||||
}
|
}
|
||||||
context.setNaturalBiome(wx & 15, y, wz & 15, selected);
|
context.setNaturalBiome(wx & 15, y, wz & 15, selected);
|
||||||
context.floatingBiomes(chunkHeight).record(wx & 15, y, wz & 15, matter.identity());
|
context.floatingBiomes(chunkHeight).record(wx & 15, y, wz & 15, matter.identity());
|
||||||
getEngine().getMantle().getMantle().set(wx, y, wz, BiomeInjectMatter.get(selected.key()));
|
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package art.arcane.iris.generation.stage;
|
package art.arcane.iris.generation.stage;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.chunk.ColumnExtent;
|
||||||
import art.arcane.iris.generation.decoration.IrisSpeleothems;
|
import art.arcane.iris.generation.decoration.IrisSpeleothems;
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
import art.arcane.iris.generation.runtime.EngineAssignedModifier;
|
import art.arcane.iris.generation.runtime.EngineAssignedModifier;
|
||||||
@@ -203,7 +204,7 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<NativeBlockSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int getHeight(Hunk<NativeBlockState> output, int x, int z) {
|
private int getHeight(Hunk<NativeBlockState> output, int x, int z) {
|
||||||
for (int i = output.getHeight() - 1; i >= 0; i--) {
|
for (int i = Math.min(output.getHeight() - 1, ColumnExtent.highestStoredY(output, x, z)); i >= 0; i--) {
|
||||||
NativeBlockState b = output.get(x, i, z);
|
NativeBlockState b = output.get(x, i, z);
|
||||||
|
|
||||||
if (b != null) {
|
if (b != null) {
|
||||||
|
|||||||
@@ -57,29 +57,32 @@ public final class BukkitBlockState implements NativeBlockState {
|
|||||||
// the canonical NMS state): a hit skips getAsString(), which built the full property
|
// the canonical NMS state): a hit skips getAsString(), which built the full property
|
||||||
// string on EVERY of() call — the dominant cost of the 99.9% hit case.
|
// string on EVERY of() call — the dominant cost of the 99.9% hit case.
|
||||||
private static final ConcurrentHashMap<BlockData, BukkitBlockState> DATA_CACHE = new ConcurrentHashMap<>();
|
private static final ConcurrentHashMap<BlockData, BukkitBlockState> DATA_CACHE = new ConcurrentHashMap<>();
|
||||||
|
private static final byte UNKNOWN = 0;
|
||||||
|
private static final byte FALSE = 1;
|
||||||
|
private static final byte TRUE = 2;
|
||||||
|
|
||||||
private final BlockData data;
|
private final BlockData data;
|
||||||
private final String key;
|
private final String key;
|
||||||
private final String namespace;
|
private final String namespace;
|
||||||
private volatile String materialKey;
|
private String materialKey;
|
||||||
private volatile Boolean air;
|
private byte air;
|
||||||
private volatile Boolean solid;
|
private byte solid;
|
||||||
private volatile Boolean occluding;
|
private byte occluding;
|
||||||
private volatile Boolean fluid;
|
private byte fluid;
|
||||||
private volatile Boolean water;
|
private byte water;
|
||||||
private volatile Boolean waterLogged;
|
private byte waterLogged;
|
||||||
private volatile Boolean lit;
|
private byte lit;
|
||||||
private volatile Boolean updatable;
|
private byte updatable;
|
||||||
private volatile Boolean foliage;
|
private byte foliage;
|
||||||
private volatile Boolean treeBlock;
|
private byte treeBlock;
|
||||||
private volatile Boolean foliagePlantable;
|
private byte foliagePlantable;
|
||||||
private volatile Boolean decorant;
|
private byte decorant;
|
||||||
private volatile Boolean storage;
|
private byte storage;
|
||||||
private volatile Boolean storageChest;
|
private byte storageChest;
|
||||||
private volatile Boolean ore;
|
private byte ore;
|
||||||
private volatile Boolean deepSlate;
|
private byte deepSlate;
|
||||||
private volatile Boolean vineBlock;
|
private byte vineBlock;
|
||||||
private volatile Boolean tileEntity;
|
private byte tileEntity;
|
||||||
|
|
||||||
private BukkitBlockState(BlockData data, String key) {
|
private BukkitBlockState(BlockData data, String key) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
@@ -87,6 +90,10 @@ public final class BukkitBlockState implements NativeBlockState {
|
|||||||
this.namespace = BlockStateKey.namespace(key);
|
this.namespace = BlockStateKey.namespace(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static byte flag(boolean value) {
|
||||||
|
return value ? TRUE : FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
public static BukkitBlockState of(BlockData data) {
|
public static BukkitBlockState of(BlockData data) {
|
||||||
if (data instanceof IrisCustomData custom) {
|
if (data instanceof IrisCustomData custom) {
|
||||||
return new BukkitBlockState(data, custom.getCustom().toString());
|
return new BukkitBlockState(data, custom.getCustom().toString());
|
||||||
@@ -198,32 +205,32 @@ public final class BukkitBlockState implements NativeBlockState {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAir() {
|
public boolean isAir() {
|
||||||
Boolean cached = air;
|
byte cached = air;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = BukkitBlockResolution.isAir(data);
|
cached = flag(BukkitBlockResolution.isAir(data));
|
||||||
air = cached;
|
air = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSolid() {
|
public boolean isSolid() {
|
||||||
Boolean cached = solid;
|
byte cached = solid;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = BukkitBlockResolution.isSolid(data);
|
cached = flag(BukkitBlockResolution.isSolid(data));
|
||||||
solid = cached;
|
solid = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOccluding() {
|
public boolean isOccluding() {
|
||||||
Boolean cached = occluding;
|
byte cached = occluding;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = data.getMaterial().isOccluding();
|
cached = flag(data.getMaterial().isOccluding());
|
||||||
occluding = cached;
|
occluding = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -248,142 +255,142 @@ public final class BukkitBlockState implements NativeBlockState {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFluid() {
|
public boolean isFluid() {
|
||||||
Boolean cached = fluid;
|
byte cached = fluid;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = BukkitBlockResolution.isFluid(data);
|
cached = flag(BukkitBlockResolution.isFluid(data));
|
||||||
fluid = cached;
|
fluid = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isWater() {
|
public boolean isWater() {
|
||||||
Boolean cached = water;
|
byte cached = water;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = BukkitBlockResolution.isWater(data);
|
cached = flag(BukkitBlockResolution.isWater(data));
|
||||||
water = cached;
|
water = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isWaterLogged() {
|
public boolean isWaterLogged() {
|
||||||
Boolean cached = waterLogged;
|
byte cached = waterLogged;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = BukkitBlockResolution.isWaterLogged(data);
|
cached = flag(BukkitBlockResolution.isWaterLogged(data));
|
||||||
waterLogged = cached;
|
waterLogged = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLit() {
|
public boolean isLit() {
|
||||||
Boolean cached = lit;
|
byte cached = lit;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = BukkitBlockResolution.isLit(data);
|
cached = flag(BukkitBlockResolution.isLit(data));
|
||||||
lit = cached;
|
lit = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isUpdatable() {
|
public boolean isUpdatable() {
|
||||||
Boolean cached = updatable;
|
byte cached = updatable;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = BukkitBlockResolution.isUpdatable(data);
|
cached = flag(BukkitBlockResolution.isUpdatable(data));
|
||||||
updatable = cached;
|
updatable = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFoliage() {
|
public boolean isFoliage() {
|
||||||
Boolean cached = foliage;
|
byte cached = foliage;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = BukkitBlockResolution.isFoliage(data);
|
cached = flag(BukkitBlockResolution.isFoliage(data));
|
||||||
foliage = cached;
|
foliage = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isTreeBlock() {
|
public boolean isTreeBlock() {
|
||||||
Boolean cached = treeBlock;
|
byte cached = treeBlock;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = Tag.LOGS.isTagged(data.getMaterial()) || Tag.LEAVES.isTagged(data.getMaterial());
|
cached = flag(Tag.LOGS.isTagged(data.getMaterial()) || Tag.LEAVES.isTagged(data.getMaterial()));
|
||||||
treeBlock = cached;
|
treeBlock = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFoliagePlantable() {
|
public boolean isFoliagePlantable() {
|
||||||
Boolean cached = foliagePlantable;
|
byte cached = foliagePlantable;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = BukkitBlockResolution.isFoliagePlantable(data);
|
cached = flag(BukkitBlockResolution.isFoliagePlantable(data));
|
||||||
foliagePlantable = cached;
|
foliagePlantable = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDecorant() {
|
public boolean isDecorant() {
|
||||||
Boolean cached = decorant;
|
byte cached = decorant;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = BukkitBlockResolution.isDecorant(data);
|
cached = flag(BukkitBlockResolution.isDecorant(data));
|
||||||
decorant = cached;
|
decorant = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isStorage() {
|
public boolean isStorage() {
|
||||||
Boolean cached = storage;
|
byte cached = storage;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = BukkitBlockResolution.isStorage(data);
|
cached = flag(BukkitBlockResolution.isStorage(data));
|
||||||
storage = cached;
|
storage = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isStorageChest() {
|
public boolean isStorageChest() {
|
||||||
Boolean cached = storageChest;
|
byte cached = storageChest;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = BukkitBlockResolution.isStorageChest(data);
|
cached = flag(BukkitBlockResolution.isStorageChest(data));
|
||||||
storageChest = cached;
|
storageChest = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOre() {
|
public boolean isOre() {
|
||||||
Boolean cached = ore;
|
byte cached = ore;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = BukkitBlockResolution.isOre(data);
|
cached = flag(BukkitBlockResolution.isOre(data));
|
||||||
ore = cached;
|
ore = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDeepSlate() {
|
public boolean isDeepSlate() {
|
||||||
Boolean cached = deepSlate;
|
byte cached = deepSlate;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = BukkitBlockResolution.isDeepSlate(data);
|
cached = flag(BukkitBlockResolution.isDeepSlate(data));
|
||||||
deepSlate = cached;
|
deepSlate = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isVineBlock() {
|
public boolean isVineBlock() {
|
||||||
Boolean cached = vineBlock;
|
byte cached = vineBlock;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = BukkitBlockResolution.isVineBlock(data);
|
cached = flag(BukkitBlockResolution.isVineBlock(data));
|
||||||
vineBlock = cached;
|
vineBlock = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -398,12 +405,12 @@ public final class BukkitBlockState implements NativeBlockState {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasTileEntity() {
|
public boolean hasTileEntity() {
|
||||||
Boolean cached = tileEntity;
|
byte cached = tileEntity;
|
||||||
if (cached == null) {
|
if (cached == UNKNOWN) {
|
||||||
cached = INMS.get().hasTile(data.getMaterial());
|
cached = flag(INMS.get().hasTile(data.getMaterial()));
|
||||||
tileEntity = cached;
|
tileEntity = cached;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -633,7 +633,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
return existingHolder;
|
return existingHolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noiseBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
|
if ((y & 63) == 0 && noiseBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
|
||||||
noiseBiomeCache.clear();
|
noiseBiomeCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ import art.arcane.volmlib.util.bukkit.WorldIdentity;
|
|||||||
import art.arcane.volmlib.util.collection.KList;
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
import art.arcane.volmlib.util.math.M;
|
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.TerrainChunkBiomeHunkView;
|
import art.arcane.iris.generation.chunk.ColumnBiomeHunk;
|
||||||
import art.arcane.iris.generation.chunk.ChunkDataHunkHolder;
|
import art.arcane.iris.generation.chunk.ChunkDataHunkHolder;
|
||||||
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;
|
||||||
@@ -1298,12 +1298,11 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
try {
|
try {
|
||||||
Engine engine = getEngine(world);
|
Engine engine = getEngine(world);
|
||||||
StudioGenerator selected = computeStudioGenerator();
|
StudioGenerator selected = computeStudioGenerator();
|
||||||
TerrainChunk tc = TerrainChunk.create(d);
|
|
||||||
if (selected != null) {
|
if (selected != null) {
|
||||||
selected.generateChunk(engine, tc, x, z);
|
selected.generateChunk(engine, TerrainChunk.create(d), x, z);
|
||||||
} else {
|
} else {
|
||||||
ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(d);
|
ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(d);
|
||||||
Hunk<NativeBiome> biomes = new TerrainChunkBiomeHunkView(tc);
|
Hunk<NativeBiome> biomes = new ColumnBiomeHunk(d.getMaxHeight() - d.getMinHeight());
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope =
|
||||||
openGenerationHistoryCoordinateScope(engine, x << 4, z << 4);
|
openGenerationHistoryCoordinateScope(engine, x << 4, z << 4);
|
||||||
GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_terrain_stage");
|
GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_terrain_stage");
|
||||||
|
|||||||
+5
-7
@@ -103,20 +103,18 @@ public class FloatingIslandBoundarySamplerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parent_cachesResolvedBiomeColumns() {
|
public void parent_readsOnlyTheQueriedColumnWithoutBuildingTheBoundaryField() {
|
||||||
IrisBiome parent = new IrisBiome();
|
IrisBiome parent = new IrisBiome();
|
||||||
|
IrisBiome other = new IrisBiome();
|
||||||
AtomicInteger calls = new AtomicInteger();
|
AtomicInteger calls = new AtomicInteger();
|
||||||
FloatingIslandBoundarySampler sampler = new FloatingIslandBoundarySampler((x, z) -> {
|
FloatingIslandBoundarySampler sampler = new FloatingIslandBoundarySampler((x, z) -> {
|
||||||
calls.incrementAndGet();
|
calls.incrementAndGet();
|
||||||
return parent;
|
return x == 12 && z == -9 ? parent : other;
|
||||||
});
|
});
|
||||||
|
|
||||||
assertSame(parent, sampler.parent(12, -9));
|
assertSame(parent, sampler.parent(12, -9));
|
||||||
int firstReadCount = calls.get();
|
assertSame(other, sampler.parent(13, -9));
|
||||||
assertSame(parent, sampler.parent(12, -9));
|
assertEquals(2, calls.get());
|
||||||
assertEquals(firstReadCount, calls.get());
|
|
||||||
int rawWidth = 16 + (FloatingIslandBoundarySampler.EDGE_FADE_RADIUS * 2) + 2;
|
|
||||||
assertEquals(rawWidth * rawWidth, firstReadCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package art.arcane.iris.generation.chunk;
|
||||||
|
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class ChunkDataHunkHolderTest {
|
||||||
|
@Test
|
||||||
|
public void columnTopTracksTheHighestStoredValueAndIgnoresNulls() {
|
||||||
|
ChunkDataHunkHolder holder = holder(64);
|
||||||
|
NativeBlockState stone = mock(NativeBlockState.class);
|
||||||
|
|
||||||
|
assertEquals(-1, holder.highestStoredY(3, 7));
|
||||||
|
holder.setRaw(3, 40, 7, null);
|
||||||
|
assertEquals(-1, holder.highestStoredY(3, 7));
|
||||||
|
holder.setRaw(3, 12, 7, stone);
|
||||||
|
holder.setRaw(3, 30, 7, stone);
|
||||||
|
holder.setRaw(3, 5, 7, stone);
|
||||||
|
|
||||||
|
assertEquals(30, holder.highestStoredY(3, 7));
|
||||||
|
assertEquals(-1, holder.highestStoredY(7, 3));
|
||||||
|
assertSame(stone, holder.getStoredRaw(3, 30, 7));
|
||||||
|
assertNull(holder.getStoredRaw(3, 31, 7));
|
||||||
|
assertEquals(30, ColumnExtent.highestStoredY(new ColumnExtentListeningHunk<>(holder, (x, y, z, state) -> {
|
||||||
|
}), 3, 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cellsKeepTheZMajorLayout() {
|
||||||
|
ChunkDataHunkHolder holder = holder(32);
|
||||||
|
List<NativeBlockState> states = new ArrayList<>();
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
for (int y = 0; y < 32; y++) {
|
||||||
|
NativeBlockState state = mock(NativeBlockState.class);
|
||||||
|
states.add(state);
|
||||||
|
holder.setRaw(x, y, z, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int index = 0;
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
for (int y = 0; y < 32; y++) {
|
||||||
|
assertSame(states.get(index++), holder.getStoredRaw(x, y, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void concurrentWritersNeverLoseTheColumnMaximum() throws InterruptedException {
|
||||||
|
for (int trial = 0; trial < 20; trial++) {
|
||||||
|
ChunkDataHunkHolder holder = holder(768);
|
||||||
|
NativeBlockState stone = mock(NativeBlockState.class);
|
||||||
|
int threads = 8;
|
||||||
|
CountDownLatch start = new CountDownLatch(1);
|
||||||
|
List<Thread> writers = new ArrayList<>();
|
||||||
|
for (int thread = 0; thread < threads; thread++) {
|
||||||
|
int offset = thread;
|
||||||
|
Thread writer = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
start.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int y = offset; y < 768; y += threads) {
|
||||||
|
holder.setRaw(9, y, 4, stone);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
writer.start();
|
||||||
|
writers.add(writer);
|
||||||
|
}
|
||||||
|
start.countDown();
|
||||||
|
for (Thread writer : writers) {
|
||||||
|
writer.join();
|
||||||
|
}
|
||||||
|
assertEquals(767, holder.highestStoredY(9, 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ChunkDataHunkHolder holder(int height) {
|
||||||
|
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
|
||||||
|
when(chunk.getMinHeight()).thenReturn(0);
|
||||||
|
when(chunk.getMaxHeight()).thenReturn(height);
|
||||||
|
return new ChunkDataHunkHolder(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import org.junit.Test;
|
|||||||
import org.mockito.MockedStatic;
|
import org.mockito.MockedStatic;
|
||||||
|
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
@@ -110,6 +111,48 @@ public class LinkedTerrainChunkTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void columnBiomeHunkMatchesTheLinkedChunkView() {
|
||||||
|
NativeBiome plains = BukkitBiome.of(Biome.PLAINS);
|
||||||
|
NativeBiome desert = BukkitBiome.of(Biome.DESERT);
|
||||||
|
NativeBiome alternate = mock(NativeBiome.class);
|
||||||
|
when(alternate.nativeHandle()).thenReturn(Biome.DESERT);
|
||||||
|
NativeBiome[] palette = {plains, desert, alternate};
|
||||||
|
for (int trial = 0; trial < 12; trial++) {
|
||||||
|
Random random = new Random(9127L + trial);
|
||||||
|
TerrainChunkBiomeHunkView expected = new TerrainChunkBiomeHunkView(chunk());
|
||||||
|
ColumnBiomeHunk actual = new ColumnBiomeHunk(16);
|
||||||
|
int operations = 1 + random.nextInt(80);
|
||||||
|
for (int operation = 0; operation < operations; operation++) {
|
||||||
|
NativeBiome biome = palette[random.nextInt(palette.length)];
|
||||||
|
int x = random.nextInt(16);
|
||||||
|
int z = random.nextInt(16);
|
||||||
|
int kind = random.nextInt(trial % 3 == 0 ? 1 : 3);
|
||||||
|
if (kind == 0) {
|
||||||
|
expected.set(x, 0, z, x, 15, z, biome);
|
||||||
|
actual.set(x, 0, z, x, 15, z, biome);
|
||||||
|
} else if (kind == 1) {
|
||||||
|
int minimumY = random.nextInt(16);
|
||||||
|
int maximumY = minimumY + random.nextInt(16 - minimumY);
|
||||||
|
expected.set(x, minimumY, z, x, maximumY, z, biome);
|
||||||
|
actual.set(x, minimumY, z, x, maximumY, z, biome);
|
||||||
|
} else {
|
||||||
|
int y = random.nextInt(20) - 2;
|
||||||
|
int rawX = x + (random.nextBoolean() ? 16 : 0);
|
||||||
|
expected.setRaw(rawX, y, z, biome);
|
||||||
|
actual.setRaw(rawX, y, z, biome);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
for (int y = -2; y < 18; y++) {
|
||||||
|
assertSame("biome " + x + "," + y + "," + z, expected.getRaw(x, y, z), actual.getRaw(x, y, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static LinkedTerrainChunk chunk() {
|
private static LinkedTerrainChunk chunk() {
|
||||||
ChunkData data = mock(ChunkData.class);
|
ChunkData data = mock(ChunkData.class);
|
||||||
when(data.getMinHeight()).thenReturn(-16);
|
when(data.getMinHeight()).thenReturn(-16);
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ public class EngineShutdownDrainTest {
|
|||||||
doAnswer(invocation -> {
|
doAnswer(invocation -> {
|
||||||
Future<?> continuation = worker.submit(() -> {
|
Future<?> continuation = worker.submit(() -> {
|
||||||
try (IrisEngine.GenerationRuntimeScope ignored = fixture.engine.openGenerationRuntimeScope(binding)) {
|
try (IrisEngine.GenerationRuntimeScope ignored = fixture.engine.openGenerationRuntimeScope(binding)) {
|
||||||
assertSame(binding, fixture.engine.generationRuntimeScopes.current());
|
assertSame(binding, fixture.engine.threadState.binding());
|
||||||
assertSame(fixture.runtime, fixture.engine.runtime);
|
assertSame(fixture.runtime, fixture.engine.runtime);
|
||||||
assertTrue(fixture.engine.getClosing().get());
|
assertTrue(fixture.engine.getClosing().get());
|
||||||
assertThrows(IllegalStateException.class, () -> fixture.engine.requireRunning("start generation"));
|
assertThrows(IllegalStateException.class, () -> fixture.engine.requireRunning("start generation"));
|
||||||
@@ -513,16 +513,16 @@ public class EngineShutdownDrainTest {
|
|||||||
assembly.mantle = mock(EngineMantle.class);
|
assembly.mantle = mock(EngineMantle.class);
|
||||||
assembly.ownsMantle = true;
|
assembly.ownsMantle = true;
|
||||||
RuntimeAssembly previous = mock(RuntimeAssembly.class);
|
RuntimeAssembly previous = mock(RuntimeAssembly.class);
|
||||||
fixture.engine.runtimeAssembly.set(previous);
|
fixture.engine.threadState.setAssembly(previous);
|
||||||
IllegalStateException failure = new IllegalStateException("Planner still active");
|
IllegalStateException failure = new IllegalStateException("Planner still active");
|
||||||
doAnswer(invocation -> {
|
doAnswer(invocation -> {
|
||||||
assertSame(assembly, fixture.engine.runtimeAssembly.get());
|
assertSame(assembly, fixture.engine.threadState.assembly());
|
||||||
throw failure;
|
throw failure;
|
||||||
}).when(assembly.complex).close();
|
}).when(assembly.complex).close();
|
||||||
|
|
||||||
assertSame(failure, fixture.shutdown.closeAssembly(assembly, null));
|
assertSame(failure, fixture.shutdown.closeAssembly(assembly, null));
|
||||||
assertSame(previous, fixture.engine.runtimeAssembly.get());
|
assertSame(previous, fixture.engine.threadState.assembly());
|
||||||
fixture.engine.runtimeAssembly.remove();
|
fixture.engine.threadState.setAssembly(null);
|
||||||
assertTrue(fixture.shutdown.retainsData(detachedTarget.getData()));
|
assertTrue(fixture.shutdown.retainsData(detachedTarget.getData()));
|
||||||
assertTrue(fixture.shutdown.closeDetachedTarget(detachedTarget, null) instanceof IllegalStateException);
|
assertTrue(fixture.shutdown.closeDetachedTarget(detachedTarget, null) instanceof IllegalStateException);
|
||||||
verify(detachedTarget, never()).close();
|
verify(detachedTarget, never()).close();
|
||||||
@@ -531,13 +531,13 @@ public class EngineShutdownDrainTest {
|
|||||||
verify(fixture.target, never()).close();
|
verify(fixture.target, never()).close();
|
||||||
verifyNoInteractions(assembly.mantle);
|
verifyNoInteractions(assembly.mantle);
|
||||||
doAnswer(invocation -> {
|
doAnswer(invocation -> {
|
||||||
assertSame(assembly, fixture.engine.runtimeAssembly.get());
|
assertSame(assembly, fixture.engine.threadState.assembly());
|
||||||
return null;
|
return null;
|
||||||
}).when(assembly.complex).close();
|
}).when(assembly.complex).close();
|
||||||
fixture.shutdown.close();
|
fixture.shutdown.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
assertNull(fixture.engine.runtimeAssembly.get());
|
assertNull(fixture.engine.threadState.assembly());
|
||||||
assertFalse(assembly.ownsMantle);
|
assertFalse(assembly.ownsMantle);
|
||||||
assertFalse(fixture.shutdown.retainsData(detachedTarget.getData()));
|
assertFalse(fixture.shutdown.retainsData(detachedTarget.getData()));
|
||||||
verify(assembly.mantle).close();
|
verify(assembly.mantle).close();
|
||||||
@@ -851,10 +851,9 @@ public class EngineShutdownDrainTest {
|
|||||||
setField(engine, "shutdownSequence", shutdown);
|
setField(engine, "shutdownSequence", shutdown);
|
||||||
setField(engine, "engineDataStore", mock(EngineDataStore.class));
|
setField(engine, "engineDataStore", mock(EngineDataStore.class));
|
||||||
setField(engine, "lifecycleLock", new Object());
|
setField(engine, "lifecycleLock", new Object());
|
||||||
setField(engine, "runtimeAssembly", new ThreadLocal<RuntimeAssembly>());
|
setField(engine, "threadState", new EngineThreadState());
|
||||||
setField(engine, "detachedGenerationRuntimes", ConcurrentHashMap.newKeySet());
|
setField(engine, "detachedGenerationRuntimes", ConcurrentHashMap.newKeySet());
|
||||||
setField(engine, "retiringGenerationRuntimes", ConcurrentHashMap.newKeySet());
|
setField(engine, "retiringGenerationRuntimes", ConcurrentHashMap.newKeySet());
|
||||||
setField(engine, "generationRuntimeScopes", new GenerationRuntimeScopeState());
|
|
||||||
AtomicBoolean closing = new AtomicBoolean();
|
AtomicBoolean closing = new AtomicBoolean();
|
||||||
setField(engine, "closing", closing);
|
setField(engine, "closing", closing);
|
||||||
when(engine.getClosing()).thenReturn(closing);
|
when(engine.getClosing()).thenReturn(closing);
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.runtime.EngineRuntimeBuilder.RuntimeAssembly;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
public class EngineThreadStateTest {
|
||||||
|
@Test
|
||||||
|
public void bindingsShareOneFrameAndReleaseItWhenAllAreCleared() {
|
||||||
|
EngineThreadState state = new EngineThreadState();
|
||||||
|
RuntimeAssembly assembly = mock(RuntimeAssembly.class);
|
||||||
|
IrisEngine.GenerationRuntimeBinding binding = new IrisEngine.GenerationRuntimeBinding(
|
||||||
|
mock(IrisEngine.class), mock(GenerationRuntime.class));
|
||||||
|
|
||||||
|
assertNull(state.current());
|
||||||
|
state.setAssembly(assembly);
|
||||||
|
IrisEngine.GenerationRuntimeScope scope = state.open(binding);
|
||||||
|
EngineThreadState.Frame frame = state.current();
|
||||||
|
assertSame(assembly, frame.assembly);
|
||||||
|
assertSame(binding, frame.binding);
|
||||||
|
|
||||||
|
state.setAssembly(null);
|
||||||
|
assertSame(frame, state.current());
|
||||||
|
assertSame(binding, state.binding());
|
||||||
|
scope.close();
|
||||||
|
assertNull(state.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nestedScopesRestoreThePreviousBindingInOrder() {
|
||||||
|
EngineThreadState state = new EngineThreadState();
|
||||||
|
IrisEngine.GenerationRuntimeBinding outer = new IrisEngine.GenerationRuntimeBinding(
|
||||||
|
mock(IrisEngine.class), mock(GenerationRuntime.class));
|
||||||
|
IrisEngine.GenerationRuntimeBinding inner = new IrisEngine.GenerationRuntimeBinding(
|
||||||
|
mock(IrisEngine.class), mock(GenerationRuntime.class));
|
||||||
|
|
||||||
|
IrisEngine.GenerationRuntimeScope outerScope = state.open(outer);
|
||||||
|
IrisEngine.GenerationRuntimeScope innerScope = state.open(inner);
|
||||||
|
assertThrows(IllegalStateException.class, outerScope::close);
|
||||||
|
innerScope.close();
|
||||||
|
assertSame(outer, state.binding());
|
||||||
|
outerScope.close();
|
||||||
|
assertNull(state.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void threadsSharingATableSlotKeepSeparateFrames() throws InterruptedException {
|
||||||
|
EngineThreadState state = new EngineThreadState();
|
||||||
|
IrisEngine.GenerationRuntimeBinding mainBinding = new IrisEngine.GenerationRuntimeBinding(
|
||||||
|
mock(IrisEngine.class), mock(GenerationRuntime.class));
|
||||||
|
IrisEngine.GenerationRuntimeBinding otherBinding = new IrisEngine.GenerationRuntimeBinding(
|
||||||
|
mock(IrisEngine.class), mock(GenerationRuntime.class));
|
||||||
|
IrisEngine.GenerationRuntimeScope mainScope = state.open(mainBinding);
|
||||||
|
long slot = Thread.currentThread().threadId() & 255;
|
||||||
|
AtomicReference<Object> otherSeen = new AtomicReference<>();
|
||||||
|
AtomicReference<Object> otherAfterClose = new AtomicReference<>(otherBinding);
|
||||||
|
Thread colliding = null;
|
||||||
|
while (colliding == null) {
|
||||||
|
Thread candidate = new Thread(() -> {
|
||||||
|
IrisEngine.GenerationRuntimeScope scope = state.open(otherBinding);
|
||||||
|
otherSeen.set(state.binding());
|
||||||
|
scope.close();
|
||||||
|
otherAfterClose.set(state.current());
|
||||||
|
});
|
||||||
|
if ((candidate.threadId() & 255) == slot) {
|
||||||
|
colliding = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
colliding.start();
|
||||||
|
colliding.join();
|
||||||
|
|
||||||
|
assertSame(otherBinding, otherSeen.get());
|
||||||
|
assertNull(otherAfterClose.get());
|
||||||
|
assertSame(mainBinding, state.binding());
|
||||||
|
mainScope.close();
|
||||||
|
assertNull(state.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void framesAreThreadConfinedAndScopesRejectForeignClose() throws InterruptedException {
|
||||||
|
EngineThreadState state = new EngineThreadState();
|
||||||
|
IrisEngine.GenerationRuntimeBinding binding = new IrisEngine.GenerationRuntimeBinding(
|
||||||
|
mock(IrisEngine.class), mock(GenerationRuntime.class));
|
||||||
|
IrisEngine.GenerationRuntimeScope scope = state.open(binding);
|
||||||
|
AtomicReference<Object> seen = new AtomicReference<>(binding);
|
||||||
|
AtomicReference<Throwable> foreignClose = new AtomicReference<>();
|
||||||
|
Thread other = new Thread(() -> {
|
||||||
|
seen.set(state.current());
|
||||||
|
try {
|
||||||
|
scope.close();
|
||||||
|
} catch (Throwable failure) {
|
||||||
|
foreignClose.set(failure);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
other.start();
|
||||||
|
other.join();
|
||||||
|
|
||||||
|
assertNull(seen.get());
|
||||||
|
assertSame(IllegalStateException.class, foreignClose.get().getClass());
|
||||||
|
scope.close();
|
||||||
|
assertNull(state.current());
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-6
@@ -103,7 +103,7 @@ public class IrisCarveWallPaletteScopeTest {
|
|||||||
IrisContext.Scope context = IrisContext.open(fixture.engine, 71L, fixture.context);
|
IrisContext.Scope context = IrisContext.open(fixture.engine, 71L, fixture.context);
|
||||||
MockedStatic<TerrainMatterView> terrain = mockStatic(TerrainMatterView.class)) {
|
MockedStatic<TerrainMatterView> terrain = mockStatic(TerrainMatterView.class)) {
|
||||||
if (mode == Mode.ASSEMBLY) {
|
if (mode == Mode.ASSEMBLY) {
|
||||||
fixture.engine.runtimeAssembly.set(fixture.assembly);
|
fixture.engine.threadState.setAssembly(fixture.assembly);
|
||||||
}
|
}
|
||||||
terrain.when(() -> TerrainMatterView.iterate(eq(fixture.chunk), eq(MatterCavern.class), any()))
|
terrain.when(() -> TerrainMatterView.iterate(eq(fixture.chunk), eq(MatterCavern.class), any()))
|
||||||
.thenAnswer(call -> {
|
.thenAnswer(call -> {
|
||||||
@@ -137,7 +137,7 @@ public class IrisCarveWallPaletteScopeTest {
|
|||||||
verify(fixture.chunk).release();
|
verify(fixture.chunk).release();
|
||||||
assertEquals(1, fixture.dataLookups.get());
|
assertEquals(1, fixture.dataLookups.get());
|
||||||
} finally {
|
} finally {
|
||||||
fixture.engine.runtimeAssembly.remove();
|
fixture.engine.threadState.setAssembly(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertSame(fixture.active.data(), fixture.engine.getData());
|
assertSame(fixture.active.data(), fixture.engine.getData());
|
||||||
@@ -199,9 +199,7 @@ public class IrisCarveWallPaletteScopeTest {
|
|||||||
active = runtime(mock(IrisData.class), mock(IrisComplex.class), engineMantle);
|
active = runtime(mock(IrisData.class), mock(IrisComplex.class), engineMantle);
|
||||||
detached = runtime(mock(IrisData.class), mock(IrisComplex.class), engineMantle);
|
detached = runtime(mock(IrisData.class), mock(IrisComplex.class), engineMantle);
|
||||||
field(engine, IrisEngine.class, "lifecycleLock", new Object());
|
field(engine, IrisEngine.class, "lifecycleLock", new Object());
|
||||||
field(engine, IrisEngine.class, "runtimeAssembly", new ThreadLocal<EngineRuntimeBuilder.RuntimeAssembly>());
|
field(engine, IrisEngine.class, "threadState", new EngineThreadState());
|
||||||
field(engine, IrisEngine.class, "biomeEnvironmentScopes", new ThreadLocal<>());
|
|
||||||
field(engine, IrisEngine.class, "generationRuntimeScopes", new GenerationRuntimeScopeState());
|
|
||||||
field(engine, IrisEngine.class, "detachedGenerationRuntimes", Collections.newSetFromMap(new IdentityHashMap<>()));
|
field(engine, IrisEngine.class, "detachedGenerationRuntimes", Collections.newSetFromMap(new IdentityHashMap<>()));
|
||||||
field(engine, IrisEngine.class, "retiringGenerationRuntimes", Collections.newSetFromMap(new IdentityHashMap<>()));
|
field(engine, IrisEngine.class, "retiringGenerationRuntimes", Collections.newSetFromMap(new IdentityHashMap<>()));
|
||||||
engine.detachedGenerationRuntimes.add(detached);
|
engine.detachedGenerationRuntimes.add(detached);
|
||||||
@@ -255,7 +253,7 @@ public class IrisCarveWallPaletteScopeTest {
|
|||||||
private IrisBiome scopedBiome(IrisBiome biome) {
|
private IrisBiome scopedBiome(IrisBiome biome) {
|
||||||
try (IrisEngine.GenerationRuntimeScope ignored = engine.openGenerationRuntimeScope(
|
try (IrisEngine.GenerationRuntimeScope ignored = engine.openGenerationRuntimeScope(
|
||||||
new IrisEngine.GenerationRuntimeBinding(engine, active))) {
|
new IrisEngine.GenerationRuntimeBinding(engine, active))) {
|
||||||
assertSame(engine.runtimeAssembly.get() == null ? active.data() : assemblyData, engine.getData());
|
assertSame(engine.threadState.assembly() == null ? active.data() : assemblyData, engine.getData());
|
||||||
}
|
}
|
||||||
return biome;
|
return biome;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -62,7 +62,7 @@ public class IrisEngineEntryHydrologyTest {
|
|||||||
doAnswer(invocation -> {
|
doAnswer(invocation -> {
|
||||||
assertSame(engine, IrisContext.require().getEngine());
|
assertSame(engine, IrisContext.require().getEngine());
|
||||||
assertEquals(93L, IrisContext.require().getGenerationSessionId());
|
assertEquals(93L, IrisContext.require().getGenerationSessionId());
|
||||||
assertSame(binding, engine.generationRuntimeScopes.current());
|
assertSame(binding, engine.threadState.binding());
|
||||||
return null;
|
return null;
|
||||||
}).when(hydrology).prepareChunkColumns(0, 0);
|
}).when(hydrology).prepareChunkColumns(0, 0);
|
||||||
AtomicReference<Callable<Void>> queued = new AtomicReference<>();
|
AtomicReference<Callable<Void>> queued = new AtomicReference<>();
|
||||||
@@ -75,7 +75,7 @@ public class IrisEngineEntryHydrologyTest {
|
|||||||
verify(hydrology).prepareChunkColumns(0, 0);
|
verify(hydrology).prepareChunkColumns(0, 0);
|
||||||
verify(lease).close();
|
verify(lease).close();
|
||||||
assertNull(IrisContext.get());
|
assertNull(IrisContext.get());
|
||||||
assertNull(engine.generationRuntimeScopes.current());
|
assertNull(engine.threadState.binding());
|
||||||
tasks.drainBackgroundTasks("test").requireComplete("test");
|
tasks.drainBackgroundTasks("test").requireComplete("test");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -325,9 +325,9 @@ public class IrisEngineEntryHydrologyTest {
|
|||||||
Field closing = IrisEngine.class.getDeclaredField("closing");
|
Field closing = IrisEngine.class.getDeclaredField("closing");
|
||||||
closing.setAccessible(true);
|
closing.setAccessible(true);
|
||||||
closing.set(engine, new AtomicBoolean());
|
closing.set(engine, new AtomicBoolean());
|
||||||
Field scopes = IrisEngine.class.getDeclaredField("generationRuntimeScopes");
|
Field scopes = IrisEngine.class.getDeclaredField("threadState");
|
||||||
scopes.setAccessible(true);
|
scopes.setAccessible(true);
|
||||||
scopes.set(engine, new GenerationRuntimeScopeState());
|
scopes.set(engine, new EngineThreadState());
|
||||||
Field lifecycle = IrisEngine.class.getDeclaredField("lifecycleLock");
|
Field lifecycle = IrisEngine.class.getDeclaredField("lifecycleLock");
|
||||||
lifecycle.setAccessible(true);
|
lifecycle.setAccessible(true);
|
||||||
lifecycle.set(engine, new Object());
|
lifecycle.set(engine, new Object());
|
||||||
|
|||||||
+1
-3
@@ -923,9 +923,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
|
|||||||
IrisEngine engine = mock(IrisEngine.class, CALLS_REAL_METHODS);
|
IrisEngine engine = mock(IrisEngine.class, CALLS_REAL_METHODS);
|
||||||
setField(engine, "lifecycleLock", new Object());
|
setField(engine, "lifecycleLock", new Object());
|
||||||
setField(engine, "generationHistoryRuntimeRouterLock", new Object());
|
setField(engine, "generationHistoryRuntimeRouterLock", new Object());
|
||||||
setField(engine, "runtimeAssembly", new ThreadLocal<EngineRuntimeBuilder.RuntimeAssembly>());
|
setField(engine, "threadState", new EngineThreadState());
|
||||||
setField(engine, "biomeEnvironmentScopes", new ThreadLocal<Object>());
|
|
||||||
setField(engine, "generationRuntimeScopes", new GenerationRuntimeScopeState());
|
|
||||||
Set<GenerationRuntime> detached = Collections.synchronizedSet(
|
Set<GenerationRuntime> detached = Collections.synchronizedSet(
|
||||||
Collections.newSetFromMap(new IdentityHashMap<GenerationRuntime, Boolean>()));
|
Collections.newSetFromMap(new IdentityHashMap<GenerationRuntime, Boolean>()));
|
||||||
setField(engine, "detachedGenerationRuntimes", detached);
|
setField(engine, "detachedGenerationRuntimes", detached);
|
||||||
|
|||||||
+48
-32
@@ -20,7 +20,6 @@ import art.arcane.volmlib.util.hunk.Hunk;
|
|||||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
import art.arcane.volmlib.util.matter.Matter;
|
import art.arcane.volmlib.util.matter.Matter;
|
||||||
import art.arcane.volmlib.util.matter.MatterBiomeInject;
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -34,13 +33,13 @@ import static org.mockito.ArgumentMatchers.any;
|
|||||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.argThat;
|
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.ArgumentMatchers.same;
|
import static org.mockito.ArgumentMatchers.same;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@@ -54,19 +53,58 @@ public class IrisBiomeActuatorCoordinateTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
public void historicalStackChunkDoesNotClearPersistentMetadata() {
|
||||||
public void historicalChunkDoesNotPublishNewPersistentMetadata() {
|
|
||||||
Engine engine = mock(Engine.class);
|
Engine engine = mock(Engine.class);
|
||||||
ChunkContext context = mock(ChunkContext.class);
|
ChunkContext context = mock(ChunkContext.class);
|
||||||
IrisComplex complex = mock(IrisComplex.class);
|
IrisComplex complex = mock(IrisComplex.class);
|
||||||
|
when(context.hasDimensionStack()).thenReturn(true);
|
||||||
when(context.getComplex()).thenReturn(complex);
|
when(context.getComplex()).thenReturn(complex);
|
||||||
when(complex.allowsMantleChunkWrite(0, 0)).thenReturn(false);
|
when(complex.allowsMantleChunkWrite(0, 0)).thenReturn(false);
|
||||||
|
|
||||||
IrisBiomeActuator.publishNaturalMetadata(engine, 0, 0, mock(Hunk.class), context);
|
IrisDimensionStackActuator.clearHostNaturalMetadata(engine, 0, 0, 16, context);
|
||||||
|
|
||||||
verifyNoMoreInteractions(engine);
|
verifyNoMoreInteractions(engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void unstackedChunkNeverTouchesTheMantle() {
|
||||||
|
Engine engine = mock(Engine.class);
|
||||||
|
ChunkContext context = mock(ChunkContext.class);
|
||||||
|
when(context.hasDimensionStack()).thenReturn(false);
|
||||||
|
|
||||||
|
IrisDimensionStackActuator.clearHostNaturalMetadata(engine, 0, 0, 16, context);
|
||||||
|
|
||||||
|
verifyNoMoreInteractions(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void actuatorWritesColumnBiomesWithoutMantleMetadata() {
|
||||||
|
bindPlatform();
|
||||||
|
IrisComplex complex = mock(IrisComplex.class);
|
||||||
|
Engine engine = engine(complex);
|
||||||
|
IrisBiomeActuator actuator = new IrisBiomeActuator(engine);
|
||||||
|
Hunk<NativeBiome> output = mock(Hunk.class);
|
||||||
|
when(output.getWidth()).thenReturn(2);
|
||||||
|
when(output.getDepth()).thenReturn(2);
|
||||||
|
when(output.getHeight()).thenReturn(8);
|
||||||
|
ChunkedDataCache<IrisBiome> biomeCache = currentBiomeCache(engine);
|
||||||
|
ChunkContext context = mock(ChunkContext.class);
|
||||||
|
when(context.getComplex()).thenReturn(complex);
|
||||||
|
when(context.getBiome()).thenReturn(biomeCache);
|
||||||
|
|
||||||
|
actuator.onActuate(100, -200, output, false, context);
|
||||||
|
|
||||||
|
NativeBiome plains = IrisPlatforms.get().registries().biome("minecraft:plains");
|
||||||
|
for (int x = 0; x < 2; x++) {
|
||||||
|
for (int z = 0; z < 2; z++) {
|
||||||
|
verify(output).set(x, 0, z, x, 7, z, plains);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verifyNoInteractions(engine.getMantle().getMantle());
|
||||||
|
verify(IrisPlatforms.get().biomeWriter(), never()).biomeIdFor(anyString());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void derivativeScatterUsesEachWorldColumnCoordinate() {
|
public void derivativeScatterUsesEachWorldColumnCoordinate() {
|
||||||
@@ -101,19 +139,6 @@ public class IrisBiomeActuatorCoordinateTest {
|
|||||||
assertEquals(List.of("100,-200", "100,-199", "101,-200", "101,-199"), samples);
|
assertEquals(List.of("100,-200", "100,-199", "101,-200", "101,-199"), samples);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void stackedBiomeReplacementRemovesEveryExistingMarkerWithItsTypedSlice() {
|
|
||||||
Mantle<Matter> mantle = mock(Mantle.class);
|
|
||||||
|
|
||||||
IrisBiomeActuator.clearBiomeMatterRange(mantle, 100, -200, 4, 6);
|
|
||||||
|
|
||||||
verify(mantle).remove(100, 4, -200, MatterBiomeInject.class);
|
|
||||||
verify(mantle).remove(100, 5, -200, MatterBiomeInject.class);
|
|
||||||
verify(mantle).remove(100, 6, -200, MatterBiomeInject.class);
|
|
||||||
verifyNoMoreInteractions(mantle);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void transitionColumnOverlaysFrozenPhysicalKeyAfterCurrentBiome() {
|
public void transitionColumnOverlaysFrozenPhysicalKeyAfterCurrentBiome() {
|
||||||
@@ -143,11 +168,8 @@ public class IrisBiomeActuatorCoordinateTest {
|
|||||||
verify(biomeCache).get(0, 0);
|
verify(biomeCache).get(0, 0);
|
||||||
verify(IrisPlatforms.get().registries()).biome("iris:old-physical");
|
verify(IrisPlatforms.get().registries()).biome("iris:old-physical");
|
||||||
verify(IrisPlatforms.get().biomeWriter(), never()).biomeIdFor("iris:old-physical");
|
verify(IrisPlatforms.get().biomeWriter(), never()).biomeIdFor("iris:old-physical");
|
||||||
Mantle<Matter> mantle = engine.getMantle().getMantle();
|
verify(output, times(2)).set(0, 0, 0, 0, 0, 0, IrisPlatforms.get().registries().biome("iris:old-physical"));
|
||||||
verify(mantle).set(eq(100), eq(0), eq(-200), argThat(value -> {
|
verifyNoInteractions(engine.getMantle().getMantle());
|
||||||
MatterBiomeInject injection = (MatterBiomeInject) value;
|
|
||||||
return !injection.isCustom() && "iris:old-physical".equals(injection.getBiomeKey());
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -184,15 +206,9 @@ public class IrisBiomeActuatorCoordinateTest {
|
|||||||
verify(transitionPlan, never()).historicalPhysicalBiomeKeyAt(anyInt(), anyInt(), anyInt());
|
verify(transitionPlan, never()).historicalPhysicalBiomeKeyAt(anyInt(), anyInt(), anyInt());
|
||||||
verify(output).set(eq(0), eq(0), eq(0), eq(0), eq(3), eq(0), any(NativeBiome.class));
|
verify(output).set(eq(0), eq(0), eq(0), eq(0), eq(3), eq(0), any(NativeBiome.class));
|
||||||
verify(output).set(eq(0), eq(4), eq(0), eq(0), eq(7), eq(0), any(NativeBiome.class));
|
verify(output).set(eq(0), eq(4), eq(0), eq(0), eq(7), eq(0), any(NativeBiome.class));
|
||||||
Mantle<Matter> mantle = engine.getMantle().getMantle();
|
verify(IrisPlatforms.get().registries()).biome("iris:old-bottom");
|
||||||
verify(mantle).set(eq(100), eq(0), eq(-200), argThat(value -> {
|
verify(IrisPlatforms.get().registries()).biome("iris:old-top");
|
||||||
MatterBiomeInject injection = (MatterBiomeInject) value;
|
verifyNoInteractions(engine.getMantle().getMantle());
|
||||||
return "iris:old-bottom".equals(injection.getBiomeKey());
|
|
||||||
}));
|
|
||||||
verify(mantle).set(eq(100), eq(4), eq(-200), argThat(value -> {
|
|
||||||
MatterBiomeInject injection = (MatterBiomeInject) value;
|
|
||||||
return "iris:old-top".equals(injection.getBiomeKey());
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ import art.arcane.iris.generation.decoration.IrisDecorator;
|
|||||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
import art.arcane.volmlib.util.hunk.Hunk;
|
import art.arcane.volmlib.util.hunk.Hunk;
|
||||||
import art.arcane.iris.generation.block.B;
|
import art.arcane.iris.generation.block.B;
|
||||||
|
import art.arcane.iris.generation.chunk.ChunkDataHunkHolder;
|
||||||
|
import art.arcane.iris.generation.chunk.ColumnExtentListeningHunk;
|
||||||
|
import art.arcane.iris.spi.IrisPlatform;
|
||||||
|
import art.arcane.iris.spi.IrisPlatforms;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||||
import art.arcane.volmlib.util.matter.Matter;
|
import art.arcane.volmlib.util.matter.Matter;
|
||||||
@@ -91,6 +96,46 @@ public class IrisNaturalCaveScanTest {
|
|||||||
verify(fixture.chunk).release();
|
verify(fixture.chunk).release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void columnExtentScanMatchesFullScanWhenDecorationBuildsAboveTheColumnTop() throws Exception {
|
||||||
|
IrisPlatforms.bind(mock(IrisPlatform.class));
|
||||||
|
try {
|
||||||
|
Fixture full = new Fixture(true);
|
||||||
|
Hunk<NativeBlockState> fullOutput = full.sparseOutput(Hunk.newArrayHunk(16, 32, 16));
|
||||||
|
List<String> fullWrites = new ArrayList<>();
|
||||||
|
full.modifier.decorateNaturalCaves(-32, 48,
|
||||||
|
fullOutput.listen((x, y, z, state) -> fullWrites.add(x + ":" + y + ":" + z + ":" + state.key())));
|
||||||
|
|
||||||
|
Fixture bounded = new Fixture(true);
|
||||||
|
ChunkGenerator.ChunkData chunkData = mock(ChunkGenerator.ChunkData.class);
|
||||||
|
doReturn(0).when(chunkData).getMinHeight();
|
||||||
|
doReturn(32).when(chunkData).getMaxHeight();
|
||||||
|
ChunkDataHunkHolder holder = new ChunkDataHunkHolder(chunkData);
|
||||||
|
bounded.sparseOutput(holder);
|
||||||
|
List<String> boundedWrites = new ArrayList<>();
|
||||||
|
bounded.modifier.decorateNaturalCaves(-32, 48, new ColumnExtentListeningHunk<>(holder,
|
||||||
|
(x, y, z, state) -> boundedWrites.add(x + ":" + y + ":" + z + ":" + state.key())));
|
||||||
|
|
||||||
|
assertTrue("fixture must decorate a zone above the initial column top",
|
||||||
|
full.calls.contains("floor:0:0:27:1"));
|
||||||
|
assertEquals(full.calls, bounded.calls);
|
||||||
|
assertEquals(fullWrites, boundedWrites);
|
||||||
|
assertEquals(full.markers, bounded.markers);
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
for (int y = 0; y < 32; y++) {
|
||||||
|
NativeBlockState expected = fullOutput.getRaw(x, y, z);
|
||||||
|
NativeBlockState actual = holder.getStoredRaw(x, y, z);
|
||||||
|
assertEquals("block " + x + ":" + y + ":" + z,
|
||||||
|
expected == null ? null : expected.key(), actual == null ? null : actual.key());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
IrisPlatforms.unbind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void verifyScan(int width, int depth) throws Exception {
|
private static void verifyScan(int width, int depth) throws Exception {
|
||||||
Fixture fixture = new Fixture();
|
Fixture fixture = new Fixture();
|
||||||
Hunk<NativeBlockState> output = fixture.output(width, depth);
|
Hunk<NativeBlockState> output = fixture.output(width, depth);
|
||||||
@@ -169,8 +214,12 @@ public class IrisNaturalCaveScanTest {
|
|||||||
private final List<String> calls = new ArrayList<>();
|
private final List<String> calls = new ArrayList<>();
|
||||||
private final List<String> markers = new ArrayList<>();
|
private final List<String> markers = new ArrayList<>();
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private Fixture() throws Exception {
|
private Fixture() throws Exception {
|
||||||
|
this(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Fixture(boolean buildAboveTop) throws Exception {
|
||||||
Engine engine = mock(Engine.class);
|
Engine engine = mock(Engine.class);
|
||||||
EngineMantle engineMantle = mock(EngineMantle.class);
|
EngineMantle engineMantle = mock(EngineMantle.class);
|
||||||
Mantle<Matter> mantle = mock(Mantle.class);
|
Mantle<Matter> mantle = mock(Mantle.class);
|
||||||
@@ -211,6 +260,9 @@ public class IrisNaturalCaveScanTest {
|
|||||||
for (int height = 20; height <= 22; height++) {
|
for (int height = 20; height <= 22; height++) {
|
||||||
output.setRaw(x, height, z, air);
|
output.setRaw(x, height, z, air);
|
||||||
}
|
}
|
||||||
|
if (buildAboveTop) {
|
||||||
|
output.setRaw(x, 31, z, stone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}).when(surface).decorate(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), eq(biome), any(), anyInt(), anyInt());
|
}).when(surface).decorate(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), eq(biome), any(), anyInt(), anyInt());
|
||||||
@@ -225,6 +277,20 @@ public class IrisNaturalCaveScanTest {
|
|||||||
}).when(roof).decorate(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), eq(biome), any(), anyInt(), anyInt());
|
}).when(roof).decorate(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), eq(biome), any(), anyInt(), anyInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private <H extends Hunk<NativeBlockState>> H sparseOutput(H output) {
|
||||||
|
for (int x = 0; x < output.getWidth(); x++) {
|
||||||
|
for (int z = 0; z < output.getDepth(); z++) {
|
||||||
|
for (int y = 0; y < 32; y++) {
|
||||||
|
NativeBlockState state = initialState(this, y);
|
||||||
|
if (state != air) {
|
||||||
|
output.setRaw(x, y, z, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
private Hunk<NativeBlockState> output(int width, int depth) {
|
private Hunk<NativeBlockState> output(int width, int depth) {
|
||||||
Hunk<NativeBlockState> output = Hunk.newArrayHunk(width, 32, depth);
|
Hunk<NativeBlockState> output = Hunk.newArrayHunk(width, 32, depth);
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
|
|||||||
+44
@@ -1,6 +1,8 @@
|
|||||||
package art.arcane.iris.generation.stage;
|
package art.arcane.iris.generation.stage;
|
||||||
|
|
||||||
import art.arcane.iris.generation.block.B;
|
import art.arcane.iris.generation.block.B;
|
||||||
|
import art.arcane.iris.generation.chunk.ChunkDataHunkHolder;
|
||||||
|
import art.arcane.iris.generation.chunk.ColumnExtentListeningHunk;
|
||||||
import art.arcane.iris.generation.concurrent.BurstExecutor;
|
import art.arcane.iris.generation.concurrent.BurstExecutor;
|
||||||
import art.arcane.iris.generation.concurrent.MultiBurst;
|
import art.arcane.iris.generation.concurrent.MultiBurst;
|
||||||
import art.arcane.iris.generation.decoration.IrisProceduralBlocks;
|
import art.arcane.iris.generation.decoration.IrisProceduralBlocks;
|
||||||
@@ -13,6 +15,7 @@ import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
|||||||
import art.arcane.volmlib.util.hunk.Hunk;
|
import art.arcane.volmlib.util.hunk.Hunk;
|
||||||
import art.arcane.volmlib.util.hunk.storage.ArrayHunk;
|
import art.arcane.volmlib.util.hunk.storage.ArrayHunk;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -24,6 +27,7 @@ import java.util.Random;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
@@ -55,6 +59,46 @@ public class IrisPerfectionModifierColumnTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void columnExtentTopSearchMatchesFullHeightSearch() {
|
||||||
|
Fixture fixture = new Fixture();
|
||||||
|
Random random = new Random(55031L);
|
||||||
|
NativeBlockState[] palette = {null, fixture.air, fixture.water, fixture.stone,
|
||||||
|
fixture.decorant, fixture.half, fixture.spike(true, false), fixture.spike(false, true)};
|
||||||
|
for (int trial = 0; trial < 8; trial++) {
|
||||||
|
ChunkDataHunkHolder bounded = holder(64);
|
||||||
|
ChunkDataHunkHolder full = holder(64);
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
int top = random.nextInt(64);
|
||||||
|
for (int y = 0; y <= top; y++) {
|
||||||
|
NativeBlockState state = y == 0 ? fixture.stone : palette[random.nextInt(palette.length)];
|
||||||
|
bounded.setRaw(x, y, z, state);
|
||||||
|
full.setRaw(x, y, z, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fixture.modifier.onModify(0, 0, new ColumnExtentListeningHunk<>(bounded, (x, y, z, state) -> {
|
||||||
|
}), false, null);
|
||||||
|
fixture.modifier.onModify(0, 0, full.listen((x, y, z, state) -> {
|
||||||
|
}), false, null);
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
for (int y = 0; y < 64; y++) {
|
||||||
|
assertSame("voxel " + x + "," + y + "," + z, full.getStoredRaw(x, y, z), bounded.getStoredRaw(x, y, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ChunkDataHunkHolder holder(int height) {
|
||||||
|
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
|
||||||
|
when(chunk.getMinHeight()).thenReturn(0);
|
||||||
|
when(chunk.getMaxHeight()).thenReturn(height);
|
||||||
|
return new ChunkDataHunkHolder(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void unrelatedTallColumnsAreScannedOnceDuringRepeatedCleanup() {
|
public void unrelatedTallColumnsAreScannedOnceDuringRepeatedCleanup() {
|
||||||
Fixture fixture = new Fixture();
|
Fixture fixture = new Fixture();
|
||||||
|
|||||||
Reference in new issue
Block a user