mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-12 03:24:14 +00:00
Merge W2B-carving
This commit is contained in:
@@ -8,6 +8,7 @@ import art.arcane.iris.generation.runtime.EngineMetrics;
|
||||
import art.arcane.iris.generation.hydrology.runtime.IrisHydrologyRuntime;
|
||||
import art.arcane.iris.generation.biome.FloatingIslandBoundarySampler;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.mantle.CaveTerrainSnapshot;
|
||||
import art.arcane.iris.world.history.FloatingBiomeOverlay;
|
||||
import art.arcane.iris.generation.terrain.IrisRegion;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
@@ -23,6 +24,7 @@ public class ChunkContext {
|
||||
private boolean naturalTerrain = true;
|
||||
private Hunk<NativeBiome> terrainBiomes;
|
||||
private FloatingBiomeOverlay floatingBiomes;
|
||||
private CaveTerrainSnapshot caveTerrain;
|
||||
private final int x;
|
||||
private final int z;
|
||||
private final IrisComplex complex;
|
||||
@@ -157,6 +159,14 @@ public class ChunkContext {
|
||||
return floatingBiomes;
|
||||
}
|
||||
|
||||
public CaveTerrainSnapshot getCaveTerrain() {
|
||||
return caveTerrain;
|
||||
}
|
||||
|
||||
public void setCaveTerrain(CaveTerrainSnapshot caveTerrain) {
|
||||
this.caveTerrain = caveTerrain;
|
||||
}
|
||||
|
||||
public void setNaturalBiome(int localX, int y, int localZ, NativeBiome biome) {
|
||||
if (terrainBiomes != null) {
|
||||
terrainBiomes.setRaw(localX, y, localZ, biome);
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
package art.arcane.iris.generation.mantle;
|
||||
|
||||
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveCell;
|
||||
import art.arcane.iris.world.storage.matter.PreObjectMatterCell;
|
||||
import art.arcane.volmlib.util.hunk.bits.DataContainer;
|
||||
import art.arcane.volmlib.util.hunk.storage.MappedHunk;
|
||||
import art.arcane.volmlib.util.hunk.storage.PaletteOrHunk;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import art.arcane.volmlib.util.matter.MatterSlice;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
/**
|
||||
* The terrain view of one mantle chunk's cavern and hydrology cells (journaled pre-object values win),
|
||||
* copied once under the chunk monitor. Terrain components finish before any content write reaches a
|
||||
* chunk and every mantle-writer write journals the value it replaces, so the view holds while the chunk
|
||||
* generates; the raw writers that run after sCave (dimension stack clearing, continuation restore) drop
|
||||
* it from the chunk context instead.
|
||||
*/
|
||||
public final class CaveTerrainSnapshot {
|
||||
private static final int SECTION_VOLUME = 4096;
|
||||
|
||||
private final int chunkX;
|
||||
private final int chunkZ;
|
||||
private final MatterCavern[][] caverns;
|
||||
private final HydrologyCaveCell[][] hydrology;
|
||||
private boolean hydrologyPresent;
|
||||
|
||||
private CaveTerrainSnapshot(int chunkX, int chunkZ, int sections) {
|
||||
this.chunkX = chunkX;
|
||||
this.chunkZ = chunkZ;
|
||||
caverns = new MatterCavern[sections][];
|
||||
hydrology = new HydrologyCaveCell[sections][];
|
||||
}
|
||||
|
||||
public static CaveTerrainSnapshot capture(MantleChunk<Matter> chunk, int chunkX, int chunkZ) {
|
||||
Objects.requireNonNull(chunk, "Terrain mantle chunk");
|
||||
synchronized (chunk) {
|
||||
int sections = chunk.sectionCount();
|
||||
CaveTerrainSnapshot snapshot = new CaveTerrainSnapshot(chunkX, chunkZ, sections);
|
||||
for (int section = 0; section < sections; section++) {
|
||||
if (!chunk.exists(section)) {
|
||||
continue;
|
||||
}
|
||||
Matter matter = chunk.get(section);
|
||||
if (matter != null) {
|
||||
snapshot.captureSection(section, matter);
|
||||
}
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean covers(int chunkX, int chunkZ) {
|
||||
return this.chunkX == chunkX && this.chunkZ == chunkZ;
|
||||
}
|
||||
|
||||
public boolean hasHydrology() {
|
||||
return hydrologyPresent;
|
||||
}
|
||||
|
||||
public MatterCavern cavern(int x, int y, int z) {
|
||||
if (y < 0) {
|
||||
return null;
|
||||
}
|
||||
int section = y >> 4;
|
||||
if (section >= caverns.length) {
|
||||
return null;
|
||||
}
|
||||
MatterCavern[] values = caverns[section];
|
||||
return values == null ? null : values[index(x, y, z)];
|
||||
}
|
||||
|
||||
public HydrologyCaveCell hydrology(int x, int y, int z) {
|
||||
if (!hydrologyPresent || y < 0) {
|
||||
return null;
|
||||
}
|
||||
int section = y >> 4;
|
||||
if (section >= hydrology.length) {
|
||||
return null;
|
||||
}
|
||||
HydrologyCaveCell[] values = hydrology[section];
|
||||
return values == null ? null : values[index(x, y, z)];
|
||||
}
|
||||
|
||||
public MatterCavern composedCavern(int x, int y, int z) {
|
||||
HydrologyCaveCell cell = hydrology(x, y, z);
|
||||
return cell != null ? cell.asCavern() : cavern(x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits every cell holding a cavern or a hydrology cell, section by section in slice position order.
|
||||
*/
|
||||
public void forEachCell(CellConsumer consumer) {
|
||||
for (int section = 0; section < caverns.length; section++) {
|
||||
MatterCavern[] sectionCaverns = caverns[section];
|
||||
HydrologyCaveCell[] sectionHydrology = hydrologyPresent ? hydrology[section] : null;
|
||||
if (sectionCaverns == null && sectionHydrology == null) {
|
||||
continue;
|
||||
}
|
||||
int baseY = section << 4;
|
||||
for (int position = 0; position < SECTION_VOLUME; position++) {
|
||||
MatterCavern cavern = sectionCaverns == null ? null : sectionCaverns[position];
|
||||
HydrologyCaveCell cell = sectionHydrology == null ? null : sectionHydrology[position];
|
||||
if (cavern != null || cell != null) {
|
||||
consumer.accept(position & 15, baseY + ((position >> 4) & 15), position >> 8, cavern, cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MatterCavern[] composedFace(TerrainMatterView.Face face, int height) {
|
||||
Objects.requireNonNull(face, "Terrain face");
|
||||
if (height < 0) {
|
||||
throw new IllegalArgumentException("Terrain face height must be nonnegative");
|
||||
}
|
||||
MatterCavern[] result = new MatterCavern[Math.multiplyExact(height, 16)];
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int offset = 0; offset < 16; offset++) {
|
||||
result[y * 16 + offset] = composedCavern(face.x(offset), y, face.z(offset));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void captureSection(int section, Matter matter) {
|
||||
caverns[section] = copy(matter.getSlice(MatterCavern.class), MatterCavern[]::new);
|
||||
hydrology[section] = copy(matter.getSlice(HydrologyCaveCell.class), HydrologyCaveCell[]::new);
|
||||
MatterSlice<PreObjectMatterCell> journal = matter.getSlice(PreObjectMatterCell.class);
|
||||
if (journal instanceof MappedHunk<?> mapped) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Integer, PreObjectMatterCell> entries = (Map<Integer, PreObjectMatterCell>) mapped.getData();
|
||||
for (Map.Entry<Integer, PreObjectMatterCell> entry : entries.entrySet()) {
|
||||
restoreOriginals(section, entry.getKey(), entry.getValue());
|
||||
}
|
||||
} else if (journal != null) {
|
||||
for (int position = 0; position < SECTION_VOLUME; position++) {
|
||||
PreObjectMatterCell cell = journal.get(position & 15, (position >> 4) & 15, position >> 8);
|
||||
if (cell != null) {
|
||||
restoreOriginals(section, position, cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hydrology[section] != null && containsAny(hydrology[section])) {
|
||||
hydrologyPresent = true;
|
||||
} else {
|
||||
hydrology[section] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreOriginals(int section, int position, PreObjectMatterCell cell) {
|
||||
if (cell.cavernCaptured() && (caverns[section] != null || cell.cavern() != null)) {
|
||||
if (caverns[section] == null) {
|
||||
caverns[section] = new MatterCavern[SECTION_VOLUME];
|
||||
}
|
||||
caverns[section][position] = cell.cavern();
|
||||
}
|
||||
if (cell.hydrologyCaptured() && (hydrology[section] != null || cell.hydrology() != null)) {
|
||||
if (hydrology[section] == null) {
|
||||
hydrology[section] = new HydrologyCaveCell[SECTION_VOLUME];
|
||||
}
|
||||
hydrology[section][position] = cell.hydrology();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T[] copy(MatterSlice<T> slice, IntFunction<T[]> allocator) {
|
||||
if (slice == null) {
|
||||
return null;
|
||||
}
|
||||
T[] values = allocator.apply(SECTION_VOLUME);
|
||||
if (slice instanceof PaletteOrHunk<?> storage && storage.isPalette()
|
||||
&& slice.getWidth() == 16 && slice.getHeight() == 16 && slice.getDepth() == 16) {
|
||||
return ((DataContainer<T>) storage.palette()).copyPresent(values) == 0 ? null : values;
|
||||
}
|
||||
int width = Math.min(16, slice.getWidth());
|
||||
int height = Math.min(16, slice.getHeight());
|
||||
int depth = Math.min(16, slice.getDepth());
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int z = 0; z < depth; z++) {
|
||||
values[index(x, y, z)] = slice.get(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private static boolean containsAny(Object[] values) {
|
||||
for (Object value : values) {
|
||||
if (value != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int index(int x, int y, int z) {
|
||||
return ((z & 15) << 8) | ((y & 15) << 4) | (x & 15);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CellConsumer {
|
||||
void accept(int x, int y, int z, MatterCavern cavern, HydrologyCaveCell hydrology);
|
||||
}
|
||||
}
|
||||
@@ -55,8 +55,12 @@ public interface MantleComponent extends Comparable<MantleComponent> {
|
||||
return getInputRadius();
|
||||
}
|
||||
|
||||
default boolean isInputGenerationLazy() {
|
||||
return false;
|
||||
/**
|
||||
* The part of {@code inputRadius} generated before this component runs. Content writers generate the
|
||||
* terrain of any chunk read beyond it on first access, so this sizes the eager window, not what may be read.
|
||||
*/
|
||||
default int getEagerInputRadius(int inputRadius) {
|
||||
return inputRadius;
|
||||
}
|
||||
|
||||
default IrisData getData() {
|
||||
|
||||
@@ -67,8 +67,8 @@ public final class MantleHydrologyComponent extends IrisMantleComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInputGenerationLazy() {
|
||||
return true;
|
||||
public int getEagerInputRadius(int inputRadius) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -128,6 +128,11 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
return calculateInputRadius(getRadius(), hasCollisionRules());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEagerInputRadius(int inputRadius) {
|
||||
return Math.min(inputRadius, calculateEagerInputRadius(getRadius(), hasCollisionRules()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hotload() {
|
||||
super.hotload();
|
||||
@@ -272,6 +277,17 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
return radius > 0 ? Math.ceilDiv(radius, 16) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every replayed source chunk plus one chunk of placement footprint. Larger footprints read past it and
|
||||
* get their terrain generated on first access.
|
||||
*/
|
||||
static int calculateEagerInputRadius(int radius, boolean sourceAnchoredCollisions) {
|
||||
int normalizedRadius = Math.max(0, radius);
|
||||
long sourceLegs = sourceAnchoredCollisions ? 2L : 1L;
|
||||
long inputRadius = (sourceChunkRadius(normalizedRadius) * 16L * sourceLegs) + Math.min(normalizedRadius, 16) + 1L;
|
||||
return inputRadius >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) inputRadius;
|
||||
}
|
||||
|
||||
static int calculateInputRadius(int radius, boolean sourceAnchoredCollisions) {
|
||||
int normalizedRadius = Math.max(0, radius);
|
||||
long sourceChunkRadius = sourceChunkRadius(normalizedRadius);
|
||||
|
||||
@@ -35,10 +35,12 @@ import art.arcane.iris.world.history.TransitionGenerationPlan;
|
||||
import art.arcane.iris.generation.noise.IrisGeneratorStyle;
|
||||
import art.arcane.iris.pack.value.IrisPosition;
|
||||
import art.arcane.iris.generation.block.TileData;
|
||||
import art.arcane.iris.generation.context.ChunkContext;
|
||||
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveCell;
|
||||
import art.arcane.volmlib.util.collection.KSet;
|
||||
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||
import art.arcane.volmlib.util.function.Function3;
|
||||
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
@@ -90,6 +92,11 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
private final ThreadLocal<Integer> activeComponentPriority = new ThreadLocal<>();
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final ThreadLocal<ObjectPlacementCapture> objectPlacementCapture = new ThreadLocal<>();
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
@EqualsAndHashCode.Exclude
|
||||
@ToString.Exclude
|
||||
private TerrainAccess terrainAccess;
|
||||
|
||||
public MantleWriter(EngineMantle engineMantle, Mantle<Matter> mantle, int x, int z, int radius, boolean multicore) {
|
||||
this(engineMantle, mantle, x, z, radius, radius * 2, multicore);
|
||||
@@ -104,17 +111,7 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
int accessRadius,
|
||||
boolean multicore
|
||||
) {
|
||||
this.engineMantle = engineMantle;
|
||||
this.mantle = mantle;
|
||||
this.radius = accessRadius;
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
IrisComplex complex = engineMantle.getComplex();
|
||||
this.transitionGenerationPlan = complex == null ? null : complex.getTransitionGenerationPlan();
|
||||
// Every coordinate acquireChunk accepts lives in this window, so a flat array replaces the
|
||||
// boxed per-block map lookup on the placement and carve hot paths.
|
||||
this.windowSide = (this.radius * 2) + 1;
|
||||
this.window = new AtomicReferenceArray<>(windowSide * windowSide);
|
||||
this(engineMantle, mantle, x, z, accessRadius);
|
||||
|
||||
final boolean foliaMaintenance = J.isFolia()
|
||||
&& WorldMaintenance.isWorldMaintenanceActive(engineMantle.getEngine().getWorld().identity());
|
||||
@@ -142,6 +139,41 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
private MantleWriter(EngineMantle engineMantle, Mantle<Matter> mantle, int x, int z, int accessRadius) {
|
||||
this.engineMantle = engineMantle;
|
||||
this.mantle = mantle;
|
||||
this.radius = accessRadius;
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
IrisComplex complex = engineMantle.getComplex();
|
||||
this.transitionGenerationPlan = complex == null ? null : complex.getTransitionGenerationPlan();
|
||||
// Every coordinate acquireChunk accepts lives in this window, so a flat array replaces the
|
||||
// boxed per-block map lookup on the placement and carve hot paths.
|
||||
this.windowSide = (this.radius * 2) + 1;
|
||||
this.window = new AtomicReferenceArray<>(windowSide * windowSide);
|
||||
}
|
||||
|
||||
/**
|
||||
* A writer over one already loaded chunk, for generating that chunk's terrain components without a
|
||||
* prefetch.
|
||||
*/
|
||||
static MantleWriter forChunk(EngineMantle engineMantle, Mantle<Matter> mantle, MantleChunk<Matter> chunk,
|
||||
int chunkX, int chunkZ) {
|
||||
MantleWriter writer = new MantleWriter(engineMantle, mantle, chunkX, chunkZ, 0);
|
||||
writer.window.set(0, chunk.use());
|
||||
return writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* From now on every chunk this writer hands out has its terrain components generated first. Called once
|
||||
* the terrain passes of the window are done and before any content pass reads or writes.
|
||||
*/
|
||||
void requireTerrainOnAccess(List<MantleComponent> terrainComponents, ChunkContext context, IrisComplex complex) {
|
||||
terrainAccess = terrainComponents.isEmpty()
|
||||
? null
|
||||
: new TerrainAccess(terrainComponents.toArray(new MantleComponent[0]), context, complex);
|
||||
}
|
||||
|
||||
static int resolvePrefetchParallelism(boolean foliaMaintenance, boolean multicore, int availableProcessors) {
|
||||
if (foliaMaintenance) {
|
||||
return 1;
|
||||
@@ -844,20 +876,24 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
return null;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
MantleChunk<Matter> chunk = window.get(index);
|
||||
if (chunk != null) {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
MantleChunk<Matter> chunk = window.get(index);
|
||||
while (chunk == null) {
|
||||
// Losing this race must release our own use, never the winner's: the winner is already
|
||||
// writing through the chunk it published.
|
||||
MantleChunk<Matter> acquired = mantle.getChunk(cx, cz).use();
|
||||
if (window.compareAndSet(index, null, acquired)) {
|
||||
return acquired;
|
||||
chunk = acquired;
|
||||
} else {
|
||||
acquired.release();
|
||||
chunk = window.get(index);
|
||||
}
|
||||
acquired.release();
|
||||
}
|
||||
TerrainAccess access = terrainAccess;
|
||||
if (access != null && !access.ready[index]) {
|
||||
access.ensure(chunk, cx, cz);
|
||||
access.ready[index] = true;
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@ChunkCoordinates
|
||||
@@ -1502,6 +1538,9 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
@Override
|
||||
public void close() {
|
||||
for (int index = 0; index < window.length(); index++) {
|
||||
if (window.get(index) == null) {
|
||||
continue;
|
||||
}
|
||||
MantleChunk<Matter> chunk = window.getAndSet(index, null);
|
||||
if (chunk != null) {
|
||||
chunk.release();
|
||||
@@ -1509,6 +1548,45 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
private final class TerrainAccess {
|
||||
private final MantleComponent[] components;
|
||||
private final ChunkContext context;
|
||||
private final IrisComplex complex;
|
||||
private final boolean[] ready = new boolean[window.length()];
|
||||
|
||||
private TerrainAccess(MantleComponent[] components, ChunkContext context, IrisComplex complex) {
|
||||
this.components = components;
|
||||
this.context = context;
|
||||
this.complex = complex;
|
||||
}
|
||||
|
||||
private void ensure(MantleChunk<Matter> chunk, int chunkX, int chunkZ) {
|
||||
if (chunk.isFlagged(MantleFlag.PLANNED) || complex != null && !complex.allowsMantleChunkWrite(chunkX, chunkZ)) {
|
||||
return;
|
||||
}
|
||||
for (MantleComponent component : components) {
|
||||
if (chunk.isFlagged(component.getFlag()) || !hasPrerequisites(chunk, component)) {
|
||||
continue;
|
||||
}
|
||||
chunk.raiseFlagSuspend(component.getFlag(), () -> {
|
||||
try (MantleWriter terrainWriter = forChunk(engineMantle, mantle, chunk, chunkX, chunkZ)) {
|
||||
terrainWriter.withComponentPriority(component.getPriority(),
|
||||
() -> component.generateLayer(terrainWriter, chunkX, chunkZ, context));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasPrerequisites(MantleChunk<Matter> chunk, MantleComponent component) {
|
||||
for (MantleFlag prerequisite : component.getPrerequisiteFlags()) {
|
||||
if (!chunk.isFlagged(prerequisite)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public final class ObjectPlacementCapture implements AutoCloseable {
|
||||
private final ObjectContinuationBundle.PlacementKey key;
|
||||
private final Long2ObjectOpenHashMap<Matter> fragments = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
@@ -90,7 +90,7 @@ public interface MatterGenerator {
|
||||
int prefetchRadius = passPlans[0].passChunkRadius();
|
||||
LongOpenHashSet partialChunks = new LongOpenHashSet();
|
||||
List<MantleComponent> requiredComponents = enabledComponents();
|
||||
List<MantleFlag> terrainFlags = terrainFlags(requiredComponents);
|
||||
boolean terrainOnAccess = false;
|
||||
|
||||
try (MantleWriter writer = new MantleWriter(
|
||||
getEngine().getMantle(),
|
||||
@@ -129,6 +129,10 @@ public interface MatterGenerator {
|
||||
if (enabledComponentCount == 0) {
|
||||
continue;
|
||||
}
|
||||
if (!terrainOnAccess && includesContent(enabledComponents, enabledComponentCount)) {
|
||||
writer.requireTerrainOnAccess(terrainComponents(requiredComponents), context, complex);
|
||||
terrainOnAccess = true;
|
||||
}
|
||||
|
||||
// Every multicore generation claims its components through the in-flight map.
|
||||
// A caller outside the pool spreads its window across the pool; a pool thread
|
||||
@@ -185,9 +189,6 @@ public interface MatterGenerator {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (phase == MatterGenerationPhase.CONTENT) {
|
||||
requireTerrainPhase(chunk, passX, passZ, terrainFlags);
|
||||
}
|
||||
int eligibleComponentCount = 0;
|
||||
for (int componentIndex = 0; componentIndex < enabledComponentCount; componentIndex++) {
|
||||
MantleComponent component = enabledComponents[componentIndex];
|
||||
@@ -303,9 +304,8 @@ public interface MatterGenerator {
|
||||
: 0;
|
||||
int componentInputRadius = component.getInputRadius(x, z, invocationChunkRadius, context);
|
||||
passAccessInputRadius = Math.max(passAccessInputRadius, componentInputRadius);
|
||||
if (!component.isInputGenerationLazy()) {
|
||||
passGenerationInputRadius = Math.max(passGenerationInputRadius, componentInputRadius);
|
||||
}
|
||||
passGenerationInputRadius = Math.max(passGenerationInputRadius,
|
||||
component.getEagerInputRadius(componentInputRadius));
|
||||
}
|
||||
int accessInvocationRadius = accessDownstreamBlockRadius + passBlockRadius;
|
||||
int generationInvocationRadius = generationDownstreamBlockRadius + passBlockRadius;
|
||||
@@ -332,24 +332,23 @@ public interface MatterGenerator {
|
||||
return List.copyOf(components);
|
||||
}
|
||||
|
||||
private static List<MantleFlag> terrainFlags(List<MantleComponent> components) {
|
||||
List<MantleFlag> flags = new ArrayList<>();
|
||||
for (MantleComponent component : components) {
|
||||
if (component.getGenerationPhase() == MatterGenerationPhase.TERRAIN) {
|
||||
flags.add(component.getFlag());
|
||||
private static boolean includesContent(MantleComponent[] components, int count) {
|
||||
for (int index = 0; index < count; index++) {
|
||||
if (components[index].getGenerationPhase() == MatterGenerationPhase.CONTENT) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return List.copyOf(flags);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void requireTerrainPhase(MantleChunk<Matter> chunk, int chunkX, int chunkZ,
|
||||
List<MantleFlag> flags) {
|
||||
for (MantleFlag flag : flags) {
|
||||
if (!chunk.isFlagged(flag)) {
|
||||
throw new IllegalStateException("Content generation at " + chunkX + "," + chunkZ
|
||||
+ " requires completed terrain component " + flag.name());
|
||||
private static List<MantleComponent> terrainComponents(List<MantleComponent> components) {
|
||||
List<MantleComponent> terrain = new ArrayList<>();
|
||||
for (MantleComponent component : components) {
|
||||
if (component.getGenerationPhase() == MatterGenerationPhase.TERRAIN) {
|
||||
terrain.add(component);
|
||||
}
|
||||
}
|
||||
return terrain;
|
||||
}
|
||||
|
||||
private static boolean hasCompletedComponents(MantleChunk<Matter> chunk, List<MantleComponent> components) {
|
||||
|
||||
@@ -134,8 +134,12 @@ public final class TerrainMatterView {
|
||||
SOUTH(false, 15);
|
||||
|
||||
private final int[] positions = new int[256];
|
||||
private final boolean fixedX;
|
||||
private final int coordinate;
|
||||
|
||||
Face(boolean fixedX, int coordinate) {
|
||||
this.fixedX = fixedX;
|
||||
this.coordinate = coordinate;
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int offset = 0; offset < 16; offset++) {
|
||||
int x = fixedX ? coordinate : offset;
|
||||
@@ -144,6 +148,14 @@ public final class TerrainMatterView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int x(int offset) {
|
||||
return fixedX ? coordinate : offset;
|
||||
}
|
||||
|
||||
int z(int offset) {
|
||||
return fixedX ? offset : coordinate;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void iterate(MantleChunk<Matter> chunk, Class<T> type,
|
||||
|
||||
@@ -109,7 +109,7 @@ public class ModeOverworld extends IrisEngineMode implements EngineMode {
|
||||
registerTerrainStage(sCave);
|
||||
registerTerrainStage(sPost);
|
||||
registerTerrainStage(sFloatingTerrainSolid);
|
||||
registerStage((x, z, k, p, m, c) -> cave.decorateNaturalCaves(x, z, k));
|
||||
registerStage((x, z, k, p, m, c) -> cave.decorateNaturalCaves(x, z, k, c));
|
||||
// Never burst these three: all of them write the same block hunk (and sDecorant reads
|
||||
// the surface sInsertMatter writes), so parallel order is scheduler-dependent. The
|
||||
// production path already runs them inline in this order; sequential registration
|
||||
|
||||
@@ -218,6 +218,9 @@ public interface EngineMode extends Staged {
|
||||
IrisDimensionStackActuator.clearHostNaturalMetadata(getEngine(), x, z, biomes.getHeight(), ctx);
|
||||
ctx.beginContent();
|
||||
List<Matter> continuations = restoreObjectContinuations(x, z);
|
||||
if (!continuations.isEmpty()) {
|
||||
ctx.setCaveTerrain(null);
|
||||
}
|
||||
for (EngineStage i : stages) {
|
||||
i.generate(x, z, blocks, biomes, multicore, ctx);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||
import art.arcane.iris.generation.runtime.EngineAssignedModifier;
|
||||
import art.arcane.iris.generation.mantle.CaveTerrainSnapshot;
|
||||
import art.arcane.iris.generation.mantle.TerrainMatterView;
|
||||
import art.arcane.iris.generation.terrain.InferredType;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
@@ -126,6 +127,10 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
MantleChunk<Matter> mantleChunk = mantle.getChunk(x, z).use();
|
||||
try {
|
||||
PrecisionStopwatch resolveStopwatch = PrecisionStopwatch.start();
|
||||
CaveTerrainSnapshot terrain = CaveTerrainSnapshot.capture(mantleChunk, x, z);
|
||||
if (context != null) {
|
||||
context.setCaveTerrain(terrain);
|
||||
}
|
||||
int worldHeightSpan = getEngine().getWorld().maxHeight() - getEngine().getWorld().minHeight();
|
||||
int caveLavaHeight = getEngine().getDimension().getCaveLavaHeight();
|
||||
CarveResolutionContext resolutionContext = new CarveResolutionContext(
|
||||
@@ -142,32 +147,20 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
chunkBlockZ
|
||||
);
|
||||
CarveResolver carveResolver = new CarveResolver(resolutionContext);
|
||||
TerrainMatterView.iterate(mantleChunk, MatterCavern.class, (xx, yy, zz, cavern) -> carveResolver.apply(
|
||||
xx,
|
||||
yy,
|
||||
zz,
|
||||
cavern,
|
||||
dataIfPresent(mantleChunk, xx, yy, zz, HydrologyCaveCell.class)
|
||||
));
|
||||
TerrainMatterView.iterate(mantleChunk, HydrologyCaveCell.class, (xx, yy, zz, hydrology) -> {
|
||||
if (dataIfPresent(mantleChunk, xx, yy, zz, MatterCavern.class) == null) {
|
||||
carveResolver.apply(xx, yy, zz, null, hydrology);
|
||||
}
|
||||
});
|
||||
terrain.forEachCell(carveResolver::apply);
|
||||
if (scratch.customCaveBiomePresent) {
|
||||
addInternalWallsFromMantle(mantleChunk, walls, columnMasks);
|
||||
addInternalWallsFromMantle(terrain, walls, columnMasks);
|
||||
} else {
|
||||
addInternalWallsFromMasks(walls, columnMasks);
|
||||
}
|
||||
addCrossChunkBoundaryWalls(mantle, mantleChunk, walls, boundaryMasks, x, z, surfaceHeights);
|
||||
addCrossChunkBoundaryWalls(mantle, terrain, walls, boundaryMasks, x, z, surfaceHeights);
|
||||
getEngine().getMetrics().getCarveResolve().put(resolveStopwatch.getMilliseconds());
|
||||
|
||||
PrecisionStopwatch applyStopwatch = PrecisionStopwatch.start();
|
||||
try {
|
||||
IrisData wallData = getData();
|
||||
walls.forEach((rx, yy, rz, cavern) -> {
|
||||
HydrologyCaveCell hydrology = dataIfPresent(
|
||||
mantleChunk, rx, yy, rz, HydrologyCaveCell.class);
|
||||
HydrologyCaveCell hydrology = terrain.hydrology(rx, yy, rz);
|
||||
if (hydrology != null && hydrology.protectsPlacement()) {
|
||||
return;
|
||||
}
|
||||
@@ -191,8 +184,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||
processColumnFromMask(
|
||||
output,
|
||||
mantleChunk,
|
||||
mantle,
|
||||
terrain,
|
||||
columnMasks[columnIndex],
|
||||
columnIndex,
|
||||
x,
|
||||
@@ -209,7 +201,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
}
|
||||
processBoundaryColumnFromMask(
|
||||
output,
|
||||
mantleChunk,
|
||||
terrain,
|
||||
boundaryMasks[columnIndex],
|
||||
walls,
|
||||
columnIndex,
|
||||
@@ -472,7 +464,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
}
|
||||
}
|
||||
|
||||
private void addInternalWallsFromMantle(MantleChunk<Matter> mc, CarveWallBuffer walls, CarveColumnMask[] columnMasks) {
|
||||
private void addInternalWallsFromMantle(CaveTerrainSnapshot terrain, CarveWallBuffer walls, CarveColumnMask[] columnMasks) {
|
||||
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||
CarveColumnMask columnMask = columnMasks[columnIndex];
|
||||
if (columnMask.isEmpty()) {
|
||||
@@ -483,18 +475,18 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
int rz = columnIndex & 15;
|
||||
int yy = columnMask.nextSetBit(0);
|
||||
while (yy >= 0) {
|
||||
MatterCavern cavern = composedCavernAt(mc, rx, yy, rz);
|
||||
MatterCavern cavern = terrain.composedCavern(rx, yy, rz);
|
||||
if (cavern != null) {
|
||||
if (rz < 15 && composedCavernAt(mc, rx, yy, rz + 1) == null) {
|
||||
if (rz < 15 && terrain.composedCavern(rx, yy, rz + 1) == null) {
|
||||
walls.put(rx, yy, rz + 1, cavern);
|
||||
}
|
||||
if (rx < 15 && composedCavernAt(mc, rx + 1, yy, rz) == null) {
|
||||
if (rx < 15 && terrain.composedCavern(rx + 1, yy, rz) == null) {
|
||||
walls.put(rx + 1, yy, rz, cavern);
|
||||
}
|
||||
if (rz > 0 && composedCavernAt(mc, rx, yy, rz - 1) == null) {
|
||||
if (rz > 0 && terrain.composedCavern(rx, yy, rz - 1) == null) {
|
||||
walls.put(rx, yy, rz - 1, cavern);
|
||||
}
|
||||
if (rx > 0 && composedCavernAt(mc, rx - 1, yy, rz) == null) {
|
||||
if (rx > 0 && terrain.composedCavern(rx - 1, yy, rz) == null) {
|
||||
walls.put(rx - 1, yy, rz, cavern);
|
||||
}
|
||||
}
|
||||
@@ -505,7 +497,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
|
||||
private void addCrossChunkBoundaryWalls(
|
||||
Mantle<Matter> mantle,
|
||||
MantleChunk<Matter> mc,
|
||||
CaveTerrainSnapshot terrain,
|
||||
CarveWallBuffer walls,
|
||||
CarveColumnMask[] boundaryMasks,
|
||||
int chunkX,
|
||||
@@ -534,10 +526,10 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
}
|
||||
|
||||
int height = maxY + 1;
|
||||
MatterCavern[] ownWest = west == null ? null : TerrainMatterView.getComposedFace(mc, TerrainMatterView.Face.WEST, height);
|
||||
MatterCavern[] ownEast = east == null ? null : TerrainMatterView.getComposedFace(mc, TerrainMatterView.Face.EAST, height);
|
||||
MatterCavern[] ownNorth = north == null ? null : TerrainMatterView.getComposedFace(mc, TerrainMatterView.Face.NORTH, height);
|
||||
MatterCavern[] ownSouth = south == null ? null : TerrainMatterView.getComposedFace(mc, TerrainMatterView.Face.SOUTH, height);
|
||||
MatterCavern[] ownWest = west == null ? null : terrain.composedFace(TerrainMatterView.Face.WEST, height);
|
||||
MatterCavern[] ownEast = east == null ? null : terrain.composedFace(TerrainMatterView.Face.EAST, height);
|
||||
MatterCavern[] ownNorth = north == null ? null : terrain.composedFace(TerrainMatterView.Face.NORTH, height);
|
||||
MatterCavern[] ownSouth = south == null ? null : terrain.composedFace(TerrainMatterView.Face.SOUTH, height);
|
||||
MatterCavern[] fromWest = west == null ? null : TerrainMatterView.getComposedFace(west, TerrainMatterView.Face.EAST, height);
|
||||
MatterCavern[] fromEast = east == null ? null : TerrainMatterView.getComposedFace(east, TerrainMatterView.Face.WEST, height);
|
||||
MatterCavern[] fromNorth = north == null ? null : TerrainMatterView.getComposedFace(north, TerrainMatterView.Face.SOUTH, height);
|
||||
@@ -586,18 +578,9 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
return plate.get(chunkX & 31, chunkZ & 31);
|
||||
}
|
||||
|
||||
private MatterCavern composedCavernAt(MantleChunk<Matter> mantleChunk, int x, int y, int z) {
|
||||
return TerrainMatterView.getComposedCavern(mantleChunk, x, y, z);
|
||||
}
|
||||
|
||||
private static <T> T dataIfPresent(MantleChunk<Matter> mantleChunk, int x, int y, int z, Class<T> type) {
|
||||
return TerrainMatterView.get(mantleChunk, x, y, z, type);
|
||||
}
|
||||
|
||||
private void processColumnFromMask(
|
||||
Hunk<NativeBlockState> output,
|
||||
MantleChunk<Matter> mc,
|
||||
Mantle<Matter> mantle,
|
||||
CaveTerrainSnapshot terrain,
|
||||
CarveColumnMask columnMask,
|
||||
int columnIndex,
|
||||
int chunkX,
|
||||
@@ -631,7 +614,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
zone.ceiling = buf;
|
||||
} else {
|
||||
if (zone.isValid(resolverState.height)) {
|
||||
processZone(output, mc, mantle, zone, rx, rz, worldX, worldZ, resolverState,
|
||||
processZone(output, terrain, zone, rx, rz, worldX, worldZ, resolverState,
|
||||
caveBiomeCache, customBiomeCache);
|
||||
}
|
||||
zone = new CaveZone();
|
||||
@@ -644,14 +627,14 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
}
|
||||
|
||||
if (zone.isValid(resolverState.height)) {
|
||||
processZone(output, mc, mantle, zone, rx, rz, worldX, worldZ, resolverState,
|
||||
processZone(output, terrain, zone, rx, rz, worldX, worldZ, resolverState,
|
||||
caveBiomeCache, customBiomeCache);
|
||||
}
|
||||
}
|
||||
|
||||
private void processBoundaryColumnFromMask(
|
||||
Hunk<NativeBlockState> output,
|
||||
MantleChunk<Matter> mantleChunk,
|
||||
CaveTerrainSnapshot terrain,
|
||||
CarveColumnMask boundaryMask,
|
||||
CarveWallBuffer walls,
|
||||
int columnIndex,
|
||||
@@ -678,7 +661,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
if (y == zoneCeiling + 1) {
|
||||
zoneCeiling = y;
|
||||
} else {
|
||||
paintBoundaryZone(output, mantleChunk, walls, rx, rz, worldX, worldZ, zoneFloor, zoneCeiling,
|
||||
paintBoundaryZone(output, terrain, walls, rx, rz, worldX, worldZ, zoneFloor, zoneCeiling,
|
||||
resolverState, caveBiomeCache, customBiomeCache);
|
||||
zoneFloor = y;
|
||||
zoneCeiling = y;
|
||||
@@ -686,13 +669,13 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
y = boundaryMask.nextSetBit(y + 1);
|
||||
}
|
||||
|
||||
paintBoundaryZone(output, mantleChunk, walls, rx, rz, worldX, worldZ, zoneFloor, zoneCeiling,
|
||||
paintBoundaryZone(output, terrain, walls, rx, rz, worldX, worldZ, zoneFloor, zoneCeiling,
|
||||
resolverState, caveBiomeCache, customBiomeCache);
|
||||
}
|
||||
|
||||
private void paintBoundaryZone(
|
||||
Hunk<NativeBlockState> output,
|
||||
MantleChunk<Matter> mantleChunk,
|
||||
CaveTerrainSnapshot terrain,
|
||||
CarveWallBuffer walls,
|
||||
int rx,
|
||||
int rz,
|
||||
@@ -715,8 +698,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
}
|
||||
|
||||
if (floorBiome != null) {
|
||||
HydrologyCaveCell floorHydrology = dataIfPresent(
|
||||
mantleChunk, rx, zoneFloor, rz, HydrologyCaveCell.class);
|
||||
HydrologyCaveCell floorHydrology = terrain.hydrology(rx, zoneFloor, rz);
|
||||
IrisRiverMaterialConfig bedMaterial = undergroundBedMaterial();
|
||||
KList<NativeBlockState> floorLayers = floorBiome.generateLayers(
|
||||
resolverState.dimension, worldX, worldZ, rng, 3, zoneFloor, resolverState.data, resolverState.complex);
|
||||
@@ -728,8 +710,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
if (floorY < 0) {
|
||||
break;
|
||||
}
|
||||
HydrologyCaveCell hydrology = dataIfPresent(
|
||||
mantleChunk, rx, floorY, rz, HydrologyCaveCell.class);
|
||||
HydrologyCaveCell hydrology = terrain.hydrology(rx, floorY, rz);
|
||||
if (hydrology != null
|
||||
&& hydrology.protectsPlacement()
|
||||
&& hydrology.action() != HydrologyCaveAction.SEAL_GUARD) {
|
||||
@@ -764,8 +745,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
if (ceilingY >= worldMaxY) {
|
||||
break;
|
||||
}
|
||||
HydrologyCaveCell hydrology = dataIfPresent(
|
||||
mantleChunk, rx, ceilingY, rz, HydrologyCaveCell.class);
|
||||
HydrologyCaveCell hydrology = terrain.hydrology(rx, ceilingY, rz);
|
||||
if (hydrology != null
|
||||
&& hydrology.protectsPlacement()
|
||||
&& hydrology.action() != HydrologyCaveAction.SEAL_GUARD) {
|
||||
@@ -800,7 +780,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
return (h & 15L) == 0L;
|
||||
}
|
||||
|
||||
private void processZone(Hunk<NativeBlockState> output, MantleChunk<Matter> mc, Mantle<Matter> mantle,
|
||||
private void processZone(Hunk<NativeBlockState> output, CaveTerrainSnapshot terrain,
|
||||
CaveZone zone, int rx, int rz, int xx, int zz,
|
||||
CaveInputs resolverState,
|
||||
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache,
|
||||
@@ -815,16 +795,15 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
output.setRaw(rx, zone.ceiling, rz, AIR);
|
||||
}
|
||||
|
||||
IrisBiome floorBiome = resolveCaveBoundaryBiome(mc, rx, zone.floor, rz, xx, zz, resolverState, caveBiomeCache, customBiomeCache);
|
||||
IrisBiome ceilingBiome = resolveCaveBoundaryBiome(mc, rx, zone.ceiling, rz, xx, zz, resolverState, caveBiomeCache, customBiomeCache);
|
||||
IrisBiome floorBiome = resolveCaveBoundaryBiome(terrain, rx, zone.floor, rz, xx, zz, resolverState, caveBiomeCache, customBiomeCache);
|
||||
IrisBiome ceilingBiome = resolveCaveBoundaryBiome(terrain, rx, zone.ceiling, rz, xx, zz, resolverState, caveBiomeCache, customBiomeCache);
|
||||
if (floorBiome == null && ceilingBiome == null) {
|
||||
normalizeCaveZoneWaterlogging(output, mc, zone, rx, rz, xx, zz);
|
||||
normalizeCaveZoneWaterlogging(output, terrain, zone, rx, rz, xx, zz);
|
||||
return;
|
||||
}
|
||||
|
||||
if (floorBiome != null) {
|
||||
HydrologyCaveCell floorHydrology = dataIfPresent(
|
||||
mc, rx, zone.floor, rz, HydrologyCaveCell.class);
|
||||
HydrologyCaveCell floorHydrology = terrain.hydrology(rx, zone.floor, rz);
|
||||
IrisRiverMaterialConfig bedMaterial = undergroundBedMaterial();
|
||||
KList<NativeBlockState> floorBlocks = floorBiome.generateLayers(resolverState.dimension, xx, zz, rng, 3, zone.floor, resolverState.data, resolverState.complex);
|
||||
for (int i = 0; i < zone.floor - 1; i++) {
|
||||
@@ -832,7 +811,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
break;
|
||||
}
|
||||
int y = zone.floor - i - 1;
|
||||
HydrologyCaveCell hydrology = dataIfPresent(mc, rx, y, rz, HydrologyCaveCell.class);
|
||||
HydrologyCaveCell hydrology = terrain.hydrology(rx, y, rz);
|
||||
if (hydrology != null
|
||||
&& hydrology.protectsPlacement()
|
||||
&& hydrology.action() != HydrologyCaveAction.SEAL_GUARD) {
|
||||
@@ -865,7 +844,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
if (cy >= maxY) {
|
||||
break;
|
||||
}
|
||||
HydrologyCaveCell hydrology = dataIfPresent(mc, rx, cy, rz, HydrologyCaveCell.class);
|
||||
HydrologyCaveCell hydrology = terrain.hydrology(rx, cy, rz);
|
||||
if (hydrology != null
|
||||
&& hydrology.protectsPlacement()
|
||||
&& hydrology.action() != HydrologyCaveAction.SEAL_GUARD) {
|
||||
@@ -884,64 +863,71 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
}
|
||||
}
|
||||
|
||||
normalizeCaveZoneWaterlogging(output, mc, zone, rx, rz, xx, zz);
|
||||
normalizeCaveZoneWaterlogging(output, terrain, zone, rx, rz, xx, zz);
|
||||
}
|
||||
|
||||
public void decorateNaturalCaves(int blockX, int blockZ, Hunk<NativeBlockState> output) {
|
||||
public void decorateNaturalCaves(int blockX, int blockZ, Hunk<NativeBlockState> output, ChunkContext context) {
|
||||
Mantle<Matter> mantle = getEngine().getMantle().getMantle();
|
||||
MantleChunk<Matter> chunk = mantle.getChunk(blockX >> 4, blockZ >> 4).use();
|
||||
CaveInputs resolver = new CaveInputs(getEngine());
|
||||
CaveTerrainSnapshot terrain = context == null ? null : context.getCaveTerrain();
|
||||
if (context != null) {
|
||||
context.setCaveTerrain(null);
|
||||
}
|
||||
if (terrain == null || !terrain.covers(blockX >> 4, blockZ >> 4)) {
|
||||
MantleChunk<Matter> chunk = mantle.getChunk(blockX >> 4, blockZ >> 4).use();
|
||||
try {
|
||||
terrain = CaveTerrainSnapshot.capture(chunk, blockX >> 4, blockZ >> 4);
|
||||
} finally {
|
||||
chunk.release();
|
||||
}
|
||||
}
|
||||
CaveInputs resolver = CaveInputs.forContentChunk(getEngine(), blockX >> 4, blockZ >> 4);
|
||||
Long2ObjectOpenHashMap<IrisBiome> caveBiomes = new Long2ObjectOpenHashMap<>(256);
|
||||
Map<String, IrisBiome> customBiomes = new HashMap<>();
|
||||
try {
|
||||
int width = output.getWidth();
|
||||
int depth = output.getDepth();
|
||||
int height = output.getHeight();
|
||||
ColumnExtent extent = output instanceof ColumnExtent columnExtent ? columnExtent : null;
|
||||
for (int localX = 0; localX < width; localX++) {
|
||||
for (int localZ = 0; localZ < depth; localZ++) {
|
||||
int worldX = blockX + localX;
|
||||
int worldZ = blockZ + localZ;
|
||||
int floor = -1;
|
||||
int top = extent == null ? height - 1 : extent.highestStoredY(localX, localZ);
|
||||
for (int y = 1; y < height; y++) {
|
||||
if (y > top) {
|
||||
break;
|
||||
}
|
||||
NativeBlockState state = output.getRaw(localX, y, localZ);
|
||||
if (B.isSolid(state)) {
|
||||
if (floor >= 0) {
|
||||
CaveZone zone = new CaveZone();
|
||||
zone.setFloor(floor);
|
||||
zone.setCeiling(y - 1);
|
||||
if (zone.isValid(resolver.height)
|
||||
&& !getComplex().isTerrain3DOpening(worldX, floor, worldZ)) {
|
||||
if (markerRoll(worldX, zone.ceiling, worldZ, 0x9E3779B97F4A7C15L)) {
|
||||
mantle.set(worldX, zone.ceiling, worldZ, MarkerMatter.CAVE_CEILING);
|
||||
}
|
||||
if (markerRoll(worldX, zone.floor, worldZ, 0xC2B2AE3D27D4EB4FL)) {
|
||||
mantle.set(worldX, zone.floor, worldZ, MarkerMatter.CAVE_FLOOR);
|
||||
}
|
||||
IrisBiome floorBiome = resolveCaveBoundaryBiome(chunk, localX, floor, localZ,
|
||||
worldX, worldZ, resolver, caveBiomes, customBiomes);
|
||||
IrisBiome ceilingBiome = resolveCaveBoundaryBiome(chunk, localX, y - 1, localZ,
|
||||
worldX, worldZ, resolver, caveBiomes, customBiomes);
|
||||
decorateZone(output, zone, localX, localZ, worldX, worldZ,
|
||||
floorBiome, ceilingBiome);
|
||||
if (extent != null) {
|
||||
top = extent.highestStoredY(localX, localZ);
|
||||
}
|
||||
int width = output.getWidth();
|
||||
int depth = output.getDepth();
|
||||
int height = output.getHeight();
|
||||
ColumnExtent extent = output instanceof ColumnExtent columnExtent ? columnExtent : null;
|
||||
for (int localX = 0; localX < width; localX++) {
|
||||
for (int localZ = 0; localZ < depth; localZ++) {
|
||||
int worldX = blockX + localX;
|
||||
int worldZ = blockZ + localZ;
|
||||
int floor = -1;
|
||||
int top = extent == null ? height - 1 : extent.highestStoredY(localX, localZ);
|
||||
for (int y = 1; y < height; y++) {
|
||||
if (y > top) {
|
||||
break;
|
||||
}
|
||||
NativeBlockState state = output.getRaw(localX, y, localZ);
|
||||
if (B.isSolid(state)) {
|
||||
if (floor >= 0) {
|
||||
CaveZone zone = new CaveZone();
|
||||
zone.setFloor(floor);
|
||||
zone.setCeiling(y - 1);
|
||||
if (zone.isValid(resolver.height)
|
||||
&& !getComplex().isTerrain3DOpening(worldX, floor, worldZ)) {
|
||||
if (markerRoll(worldX, zone.ceiling, worldZ, 0x9E3779B97F4A7C15L)) {
|
||||
mantle.set(worldX, zone.ceiling, worldZ, MarkerMatter.CAVE_CEILING);
|
||||
}
|
||||
if (markerRoll(worldX, zone.floor, worldZ, 0xC2B2AE3D27D4EB4FL)) {
|
||||
mantle.set(worldX, zone.floor, worldZ, MarkerMatter.CAVE_FLOOR);
|
||||
}
|
||||
IrisBiome floorBiome = resolveCaveBoundaryBiome(terrain, localX, floor, localZ,
|
||||
worldX, worldZ, resolver, caveBiomes, customBiomes);
|
||||
IrisBiome ceilingBiome = resolveCaveBoundaryBiome(terrain, localX, y - 1, localZ,
|
||||
worldX, worldZ, resolver, caveBiomes, customBiomes);
|
||||
decorateZone(output, zone, localX, localZ, worldX, worldZ,
|
||||
floorBiome, ceilingBiome);
|
||||
if (extent != null) {
|
||||
top = extent.highestStoredY(localX, localZ);
|
||||
}
|
||||
floor = -1;
|
||||
}
|
||||
} else if (floor < 0 && B.isSolid(output.getRaw(localX, y - 1, localZ))) {
|
||||
floor = y;
|
||||
floor = -1;
|
||||
}
|
||||
} else if (floor < 0 && B.isSolid(output.getRaw(localX, y - 1, localZ))) {
|
||||
floor = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
chunk.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -966,7 +952,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
|
||||
private void normalizeCaveZoneWaterlogging(
|
||||
Hunk<NativeBlockState> output,
|
||||
MantleChunk<Matter> mantleChunk,
|
||||
CaveTerrainSnapshot terrain,
|
||||
CaveZone zone,
|
||||
int localX,
|
||||
int localZ,
|
||||
@@ -976,13 +962,11 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
int minimumY = Math.max(0, zone.floor - 1);
|
||||
int maximumY = Math.min(output.getHeight() - 1, zone.ceiling + 1);
|
||||
for (int y = minimumY; y <= maximumY; y++) {
|
||||
HydrologyCaveCell hydrology = dataIfPresent(
|
||||
mantleChunk, localX, y, localZ, HydrologyCaveCell.class);
|
||||
HydrologyCaveCell hydrology = terrain.hydrology(localX, y, localZ);
|
||||
if (hydrology == null) {
|
||||
continue;
|
||||
}
|
||||
MatterCavern baseline = dataIfPresent(
|
||||
mantleChunk, localX, y, localZ, MatterCavern.class);
|
||||
MatterCavern baseline = terrain.cavern(localX, y, localZ);
|
||||
NativeBlockState current = output.getRaw(localX, y, localZ);
|
||||
NativeBlockState columnFluid = getComplex().resolveHydrologyFluid(
|
||||
hydrology.fluidProfileKey(),
|
||||
@@ -1001,8 +985,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
}
|
||||
}
|
||||
|
||||
IrisBiome resolveCaveBoundaryBiome(MantleChunk<Matter> mantleChunk, int x, int y, int z, int worldX, int worldZ, CaveInputs resolverState, Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache, Map<String, IrisBiome> customBiomeCache) {
|
||||
MatterCavern cavern = composedCavernAt(mantleChunk, x, y, z);
|
||||
IrisBiome resolveCaveBoundaryBiome(CaveTerrainSnapshot terrain, int x, int y, int z, int worldX, int worldZ, CaveInputs resolverState, Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache, Map<String, IrisBiome> customBiomeCache) {
|
||||
MatterCavern cavern = terrain == null ? null : terrain.composedCavern(x, y, z);
|
||||
return resolveCaveBoundaryBiome(
|
||||
cavern, worldX, y, worldZ, resolverState, caveBiomeCache, customBiomeCache);
|
||||
}
|
||||
@@ -1179,19 +1163,39 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
private final ProceduralStream<IrisBiome> caveBiomes;
|
||||
private final ProceduralStream<Double> heights;
|
||||
private final CaveColumn[] columns;
|
||||
private final boolean chunkLimited;
|
||||
private final int chunkX;
|
||||
private final int chunkZ;
|
||||
|
||||
CaveInputs(Engine engine) {
|
||||
this(engine, false, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inputs for the content stage of one chunk. A coordinate inside that chunk resolves under the chunk's
|
||||
* own generation route, exactly the binding the natural terrain stage samples with, so it takes the
|
||||
* captured column path; any other coordinate keeps the per-coordinate history dispatch.
|
||||
*/
|
||||
static CaveInputs forContentChunk(Engine engine, int chunkX, int chunkZ) {
|
||||
return new CaveInputs(engine, true, chunkX, chunkZ);
|
||||
}
|
||||
|
||||
private CaveInputs(Engine engine, boolean contentChunk, int chunkX, int chunkZ) {
|
||||
this.engine = engine;
|
||||
complex = engine.getComplex();
|
||||
dimension = engine.getDimension();
|
||||
data = engine.getData();
|
||||
height = engine.getHeight();
|
||||
IrisContext context = IrisContext.get();
|
||||
boolean scoped = engine instanceof IrisEngine irisEngine
|
||||
boolean generating = engine instanceof IrisEngine irisEngine
|
||||
&& irisEngine.hasGenerationRuntimeScope() && !engine.getPlatformHooks().isMainThread()
|
||||
&& context != null && context.getChunkContext() != null
|
||||
&& context.getChunkContext().getComplex() == complex
|
||||
&& context.getChunkContext().isNaturalTerrain();
|
||||
&& context.getChunkContext().getComplex() == complex;
|
||||
boolean natural = generating && context.getChunkContext().isNaturalTerrain();
|
||||
boolean scoped = natural || generating && contentChunk;
|
||||
this.chunkLimited = !natural;
|
||||
this.chunkX = chunkX;
|
||||
this.chunkZ = chunkZ;
|
||||
snapshot = scoped ? IrisDimensionCarvingResolver.snapshot(engine) : null;
|
||||
minimumY = scoped ? engine.getWorld().minHeight() : 0;
|
||||
stack = scoped ? engine.getDimensionStackContext() : null;
|
||||
@@ -1202,7 +1206,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
}
|
||||
|
||||
IrisBiome resolve(int x, int y, int z) {
|
||||
if (snapshot == null) {
|
||||
if (snapshot == null || chunkLimited && ((x >> 4) != chunkX || (z >> 4) != chunkZ)) {
|
||||
return engine.getCaveBiome(x, y, z, state);
|
||||
}
|
||||
IrisBiome configured = snapshot.resolveBiome(x, y + minimumY, z);
|
||||
|
||||
@@ -110,6 +110,7 @@ public final class IrisDimensionStackActuator extends EngineAssignedActuator<Nat
|
||||
if (!context.hasDimensionStack() || !context.getComplex().allowsMantleChunkWrite(x >> 4, z >> 4)) {
|
||||
return;
|
||||
}
|
||||
context.setCaveTerrain(null);
|
||||
MantleChunk<Matter> chunk = engine.getMantle().getMantle().getChunk(x >> 4, z >> 4).use();
|
||||
try {
|
||||
synchronized (chunk) {
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package art.arcane.iris.generation.mantle;
|
||||
|
||||
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveAction;
|
||||
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveCell;
|
||||
import art.arcane.iris.world.storage.matter.IrisMatterSupport;
|
||||
import art.arcane.iris.world.storage.matter.PreObjectMatterCell;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.matter.IrisMatter;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class CaveTerrainSnapshotTest {
|
||||
private static final int SECTIONS = 5;
|
||||
|
||||
@BeforeClass
|
||||
public static void registerMatter() {
|
||||
IrisMatterSupport.ensureRegistered();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void snapshotMatchesTerrainViewForEveryCellFaceAndResolverInput() {
|
||||
for (long seed = 1; seed <= 2; seed++) {
|
||||
MantleChunk<Matter> chunk = randomChunk(new Random(seed), seed % 2 == 0);
|
||||
CaveTerrainSnapshot snapshot = CaveTerrainSnapshot.capture(chunk, 3, -4);
|
||||
assertTrue(snapshot.covers(3, -4));
|
||||
assertFalse(snapshot.covers(3, 4));
|
||||
boolean anyHydrology = false;
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int y = -2; y < SECTIONS * 16 + 3; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
String cell = "seed=" + seed + " " + x + "," + y + "," + z;
|
||||
assertSame(cell, TerrainMatterView.get(chunk, x, y, z, MatterCavern.class), snapshot.cavern(x, y, z));
|
||||
HydrologyCaveCell hydrology = TerrainMatterView.get(chunk, x, y, z, HydrologyCaveCell.class);
|
||||
anyHydrology |= hydrology != null;
|
||||
assertSame(cell, hydrology, snapshot.hydrology(x, y, z));
|
||||
assertSame(cell, TerrainMatterView.getComposedCavern(chunk, x, y, z), snapshot.composedCavern(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(anyHydrology, snapshot.hasHydrology());
|
||||
for (TerrainMatterView.Face face : TerrainMatterView.Face.values()) {
|
||||
for (int height : new int[]{0, 1, 37, SECTIONS * 16, SECTIONS * 16 + 9}) {
|
||||
MatterCavern[] expected = TerrainMatterView.getComposedFace(chunk, face, height);
|
||||
MatterCavern[] actual = snapshot.composedFace(face, height);
|
||||
assertEquals(expected.length, actual.length);
|
||||
for (int index = 0; index < expected.length; index++) {
|
||||
assertSame(face + " " + index, expected[index], actual[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(legacyResolverInputs(chunk), snapshotResolverInputs(snapshot));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunksWithoutHydrologyAnswerNullWithoutCells() {
|
||||
Matter[] sections = new Matter[SECTIONS];
|
||||
sections[1] = new IrisMatter(16, 16, 16);
|
||||
MatterCavern cavern = new MatterCavern(true, "", (byte) 0);
|
||||
sections[1].slice(MatterCavern.class).set(4, 5, 6, cavern);
|
||||
sections[1].slice(PreObjectMatterCell.class).set(4, 6, 6, PreObjectMatterCell.hydrology(null));
|
||||
CaveTerrainSnapshot snapshot = CaveTerrainSnapshot.capture(chunk(sections), 0, 0);
|
||||
assertFalse(snapshot.hasHydrology());
|
||||
assertNull(snapshot.hydrology(4, 22, 6));
|
||||
assertSame(cavern, snapshot.composedCavern(4, 21, 6));
|
||||
assertNull(snapshot.cavern(4, 22, 6));
|
||||
}
|
||||
|
||||
private static Map<String, String> legacyResolverInputs(MantleChunk<Matter> chunk) {
|
||||
Map<String, String> inputs = new HashMap<>();
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int y = 0; y < SECTIONS * 16; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
MatterCavern cavern = TerrainMatterView.get(chunk, x, y, z, MatterCavern.class);
|
||||
HydrologyCaveCell hydrology = TerrainMatterView.get(chunk, x, y, z, HydrologyCaveCell.class);
|
||||
if (cavern != null || hydrology != null) {
|
||||
inputs.put(x + ":" + y + ":" + z, identity(cavern) + "/" + identity(hydrology));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return inputs;
|
||||
}
|
||||
|
||||
private static Map<String, String> snapshotResolverInputs(CaveTerrainSnapshot snapshot) {
|
||||
Map<String, String> inputs = new HashMap<>();
|
||||
snapshot.forEachCell((x, y, z, cavern, hydrology) -> assertNull(inputs.put(x + ":" + y + ":" + z,
|
||||
identity(cavern) + "/" + identity(hydrology))));
|
||||
return inputs;
|
||||
}
|
||||
|
||||
private static String identity(Object value) {
|
||||
return value == null ? "null" : Integer.toHexString(System.identityHashCode(value));
|
||||
}
|
||||
|
||||
private static MantleChunk<Matter> randomChunk(Random random, boolean manyCaverns) {
|
||||
MatterCavern[] caverns = new MatterCavern[manyCaverns ? 40 : 4];
|
||||
for (int index = 0; index < caverns.length; index++) {
|
||||
caverns[index] = new MatterCavern(index % 3 != 0, "biome-" + index, (byte) (index % 4));
|
||||
}
|
||||
HydrologyCaveCell[] hydrology = new HydrologyCaveCell[HydrologyCaveAction.values().length];
|
||||
for (int index = 0; index < hydrology.length; index++) {
|
||||
hydrology[index] = new HydrologyCaveCell(HydrologyCaveAction.values()[index], "river", "flooded-" + index);
|
||||
}
|
||||
Matter[] sections = new Matter[SECTIONS];
|
||||
for (int section = 0; section < SECTIONS; section++) {
|
||||
if (section == 2) {
|
||||
continue;
|
||||
}
|
||||
Matter matter = new IrisMatter(16, 16, 16);
|
||||
sections[section] = matter;
|
||||
int mode = section % 4;
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
boolean aligned = ((z << 8 | y << 4 | x) & 0x333) == 0;
|
||||
int roll = random.nextInt(100);
|
||||
if (mode == 1 ? aligned && roll < 50 : roll < 40) {
|
||||
matter.slice(MatterCavern.class).set(x, y, z, caverns[random.nextInt(caverns.length)]);
|
||||
}
|
||||
if (mode != 3 && random.nextInt(100) < 6) {
|
||||
matter.slice(HydrologyCaveCell.class).set(x, y, z, hydrology[random.nextInt(hydrology.length)]);
|
||||
}
|
||||
int journal = random.nextInt(100);
|
||||
if (journal < 3) {
|
||||
matter.slice(PreObjectMatterCell.class).set(x, y, z, PreObjectMatterCell.cavern(null));
|
||||
} else if (journal < 6) {
|
||||
matter.slice(PreObjectMatterCell.class).set(x, y, z,
|
||||
PreObjectMatterCell.cavern(caverns[random.nextInt(caverns.length)]));
|
||||
} else if (journal < 8) {
|
||||
matter.slice(PreObjectMatterCell.class).set(x, y, z, PreObjectMatterCell.hydrology(null));
|
||||
} else if (journal < 10) {
|
||||
matter.slice(PreObjectMatterCell.class).set(x, y, z,
|
||||
PreObjectMatterCell.block(null).captureHydrology(hydrology[random.nextInt(hydrology.length)]));
|
||||
} else if (journal < 13) {
|
||||
matter.slice(PreObjectMatterCell.class).set(x, y, z, PreObjectMatterCell.string("marker"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return chunk(sections);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static MantleChunk<Matter> chunk(Matter[] sections) {
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
when(chunk.sectionCount()).thenReturn(sections.length);
|
||||
when(chunk.exists(anyInt())).thenAnswer(call -> {
|
||||
int section = call.getArgument(0);
|
||||
return section >= 0 && section < sections.length && sections[section] != null;
|
||||
});
|
||||
when(chunk.get(anyInt())).thenAnswer(call -> {
|
||||
assertTrue(Thread.holdsLock(chunk));
|
||||
int section = call.getArgument(0);
|
||||
return section >= 0 && section < sections.length ? sections[section] : null;
|
||||
});
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
+18
@@ -45,6 +45,7 @@ import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
@@ -188,6 +189,23 @@ public class MantleObjectComponentBoundaryRadiusTest {
|
||||
MantleObjectComponent.calculateInputRadius(Integer.MAX_VALUE, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eagerInputRadiusCoversEverySourceChunkPlusOneChunkOfFootprint() {
|
||||
assertEquals(129, MantleObjectComponent.calculateEagerInputRadius(103, false));
|
||||
assertEquals(65, MantleObjectComponent.calculateEagerInputRadius(33, false));
|
||||
assertEquals(113, MantleObjectComponent.calculateEagerInputRadius(33, true));
|
||||
assertEquals(27, MantleObjectComponent.calculateEagerInputRadius(10, false));
|
||||
assertEquals(1, MantleObjectComponent.calculateEagerInputRadius(0, false));
|
||||
for (int radius = 0; radius < 300; radius++) {
|
||||
for (boolean collisions : new boolean[]{false, true}) {
|
||||
assertTrue(MantleObjectComponent.calculateEagerInputRadius(radius, collisions)
|
||||
<= MantleObjectComponent.calculateInputRadius(radius, collisions));
|
||||
}
|
||||
}
|
||||
assertEquals(Integer.MAX_VALUE,
|
||||
MantleObjectComponent.calculateEagerInputRadius(Integer.MAX_VALUE, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void proceduralTreeTransformsContributeToRadius() {
|
||||
IrisObjectPlacement placement = new IrisObjectPlacement()
|
||||
|
||||
+2
-2
@@ -309,8 +309,8 @@ public class MatterGeneratorCarvePassRadiusTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInputGenerationLazy() {
|
||||
return lazyInputGeneration;
|
||||
public int getEagerInputRadius(int inputRadius) {
|
||||
return lazyInputGeneration ? 0 : inputRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+71
-4
@@ -387,27 +387,94 @@ public class MatterGeneratorConcurrencyTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentPhaseRequiresTerrainEvenWhenItsExplicitPrerequisiteIsMissing() {
|
||||
public void contentPhaseGeneratesTerrainOfEveryChunkBeforeItIsReadOrWritten() {
|
||||
GeneratorFixture fixture = new GeneratorFixture(true);
|
||||
List<String> events = new ArrayList<>();
|
||||
RecordingComponent terrain = new RecordingComponent(ReservedFlag.CARVED, 0, 0) {
|
||||
@Override
|
||||
public MatterGenerationPhase getGenerationPhase() {
|
||||
return MatterGenerationPhase.TERRAIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
|
||||
assertSame(fixture.mantle.getChunk(x, z), writer.acquireChunk(x, z));
|
||||
events.add("terrain " + x + "," + z);
|
||||
}
|
||||
};
|
||||
RecordingComponent content = new RecordingComponent(ReservedFlag.OBJECT, 1, 0) {
|
||||
@Override
|
||||
public MantleFlag[] getPrerequisiteFlags() {
|
||||
return new MantleFlag[]{ReservedFlag.CARVED};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInputRadius() {
|
||||
return 48;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEagerInputRadius(int inputRadius) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
|
||||
events.add("content " + x + "," + z);
|
||||
assertSame(fixture.mantle.getChunk(x + 3, z), writer.acquireChunk(x + 3, z));
|
||||
writer.acquireChunk(x + 3, z);
|
||||
events.add("read " + (x + 3) + "," + z);
|
||||
}
|
||||
};
|
||||
TestMatterGenerator generator = fixture.generator(List.of(
|
||||
new MantlePass(List.of(terrain), 0, 0),
|
||||
new MantlePass(List.of(content), 0, 0)));
|
||||
|
||||
assertThrows(IllegalStateException.class,
|
||||
() -> generator.generateContentMatter(0, 0, false, fixture.context));
|
||||
assertFalse(fixture.mantle.getChunk(0, 0).isFlagged(MantleFlag.PLANNED));
|
||||
generator.generateTerrainMatter(0, 0, false, fixture.context);
|
||||
assertEquals(List.of("terrain 0,0"), events);
|
||||
generator.generateContentMatter(0, 0, false, fixture.context);
|
||||
|
||||
assertEquals(List.of("terrain 0,0", "content 0,0", "terrain 3,0", "read 3,0"), events);
|
||||
assertTrue(fixture.mantle.getChunk(0, 0).isFlagged(MantleFlag.PLANNED));
|
||||
assertTrue(fixture.mantle.getChunk(3, 0).isFlagged(ReservedFlag.CARVED));
|
||||
assertFalse(fixture.mantle.getChunk(3, 0).isFlagged(ReservedFlag.OBJECT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentPhaseGeneratesMissingTerrainOfItsOwnChunk() {
|
||||
GeneratorFixture fixture = new GeneratorFixture(true);
|
||||
AtomicInteger terrainRuns = new AtomicInteger();
|
||||
RecordingComponent terrain = new RecordingComponent(ReservedFlag.CARVED, 0, 0) {
|
||||
@Override
|
||||
public MatterGenerationPhase getGenerationPhase() {
|
||||
return MatterGenerationPhase.TERRAIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
|
||||
terrainRuns.incrementAndGet();
|
||||
}
|
||||
};
|
||||
RecordingComponent content = new RecordingComponent(ReservedFlag.OBJECT, 1, 0) {
|
||||
@Override
|
||||
public MantleFlag[] getPrerequisiteFlags() {
|
||||
return new MantleFlag[]{ReservedFlag.CARVED};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
|
||||
assertEquals(1, terrainRuns.get());
|
||||
}
|
||||
};
|
||||
TestMatterGenerator generator = fixture.generator(List.of(
|
||||
new MantlePass(List.of(terrain), 0, 0),
|
||||
new MantlePass(List.of(content), 0, 0)));
|
||||
|
||||
generator.generateContentMatter(0, 0, false, fixture.context);
|
||||
|
||||
assertEquals(1, terrainRuns.get());
|
||||
assertTrue(fixture.mantle.getChunk(0, 0).isFlagged(ReservedFlag.OBJECT));
|
||||
assertTrue(fixture.mantle.getChunk(0, 0).isFlagged(MantleFlag.PLANNED));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+11
-13
@@ -2,7 +2,6 @@ package art.arcane.iris.generation.runtime;
|
||||
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.generation.mantle.EngineMantle;
|
||||
import art.arcane.iris.generation.mantle.TerrainMatterView;
|
||||
import art.arcane.iris.generation.stage.IrisCarveModifier;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.biome.IrisBiomePaletteLayer;
|
||||
@@ -15,12 +14,13 @@ import art.arcane.iris.generation.context.ChunkContext;
|
||||
import art.arcane.iris.generation.context.IrisContext;
|
||||
import art.arcane.volmlib.util.hunk.Hunk;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.function.Consumer4;
|
||||
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.stream.ProceduralStream;
|
||||
import art.arcane.volmlib.util.stream.interpolation.Interpolated;
|
||||
import art.arcane.iris.world.storage.matter.IrisMatterSupport;
|
||||
import art.arcane.volmlib.util.matter.IrisMatter;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import org.junit.Test;
|
||||
@@ -42,7 +42,6 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
@@ -100,19 +99,10 @@ public class IrisCarveWallPaletteScopeTest {
|
||||
}
|
||||
try (IrisEngine.GenerationRuntimeScope generation = fixture.engine.openGenerationRuntimeScope(
|
||||
new IrisEngine.GenerationRuntimeBinding(fixture.engine, selected));
|
||||
IrisContext.Scope context = IrisContext.open(fixture.engine, 71L, fixture.context);
|
||||
MockedStatic<TerrainMatterView> terrain = mockStatic(TerrainMatterView.class)) {
|
||||
IrisContext.Scope context = IrisContext.open(fixture.engine, 71L, fixture.context)) {
|
||||
if (mode == Mode.ASSEMBLY) {
|
||||
fixture.engine.threadState.setAssembly(fixture.assembly);
|
||||
}
|
||||
terrain.when(() -> TerrainMatterView.iterate(eq(fixture.chunk), eq(MatterCavern.class), any()))
|
||||
.thenAnswer(call -> {
|
||||
Consumer4<Integer, Integer, Integer, MatterCavern> consumer = call.getArgument(2);
|
||||
for (int coordinate : new int[]{4, 8, 12}) {
|
||||
consumer.accept(coordinate, 10, coordinate, new MatterCavern(true, "", (byte) 0));
|
||||
}
|
||||
return null;
|
||||
});
|
||||
try {
|
||||
assertSame(expectedData, fixture.engine.getData());
|
||||
assertEquals(mode == Mode.NATURAL, expectedComplex.isNaturalTerrainContext());
|
||||
@@ -196,6 +186,14 @@ public class IrisCarveWallPaletteScopeTest {
|
||||
doReturn(mantle).when(engineMantle).getMantle();
|
||||
doReturn(chunk).when(mantle).getChunk(-2, 3);
|
||||
doReturn(chunk).when(chunk).use();
|
||||
IrisMatterSupport.ensureRegistered();
|
||||
Matter section = new IrisMatter(16, 16, 16);
|
||||
for (int coordinate : new int[]{4, 8, 12}) {
|
||||
section.slice(MatterCavern.class).set(coordinate, 10, coordinate, new MatterCavern(true, "", (byte) 0));
|
||||
}
|
||||
doReturn(1).when(chunk).sectionCount();
|
||||
doReturn(true).when(chunk).exists(0);
|
||||
doReturn(section).when(chunk).get(0);
|
||||
active = runtime(mock(IrisData.class), mock(IrisComplex.class), engineMantle);
|
||||
detached = runtime(mock(IrisData.class), mock(IrisComplex.class), engineMantle);
|
||||
field(engine, IrisEngine.class, "lifecycleLock", new Object());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package art.arcane.iris.generation.stage;
|
||||
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.mantle.CaveTerrainSnapshot;
|
||||
import art.arcane.iris.generation.mantle.TerrainMatterView;
|
||||
import art.arcane.iris.world.IrisWorld;
|
||||
import art.arcane.iris.world.storage.matter.IrisMatterSupport;
|
||||
@@ -56,6 +57,7 @@ public class IrisCarveBoundaryFacesTest {
|
||||
return true;
|
||||
});
|
||||
when(chunks[side].get(anyInt())).thenAnswer(call -> matter[selected][(int) call.getArgument(0)]);
|
||||
when(chunks[side].sectionCount()).thenReturn(2);
|
||||
if (side == 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -87,10 +89,10 @@ public class IrisCarveBoundaryFacesTest {
|
||||
int[] surfaces = new int[256];
|
||||
Arrays.fill(surfaces, 30);
|
||||
Method method = IrisCarveModifier.class.getDeclaredMethod("addCrossChunkBoundaryWalls",
|
||||
Mantle.class, MantleChunk.class, CarveWallBuffer.class, CarveColumnMask[].class,
|
||||
Mantle.class, CaveTerrainSnapshot.class, CarveWallBuffer.class, CarveColumnMask[].class,
|
||||
int.class, int.class, int[].class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(modifier, mantle, chunks[0], walls, masks, 0, 0, surfaces);
|
||||
method.invoke(modifier, mantle, CaveTerrainSnapshot.capture(chunks[0], 0, 0), walls, masks, 0, 0, surfaces);
|
||||
CarveWallBuffer expectedWalls = new CarveWallBuffer(64);
|
||||
for (int y = 1; y < 32; y++) {
|
||||
for (int offset = 0; offset < 16; offset++) {
|
||||
|
||||
+12
-13
@@ -2,6 +2,7 @@ package art.arcane.iris.generation.stage;
|
||||
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.pack.loading.ResourceLoader;
|
||||
import art.arcane.iris.generation.mantle.CaveTerrainSnapshot;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
@@ -12,9 +13,10 @@ import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.volmlib.util.hunk.Hunk;
|
||||
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.iris.world.storage.matter.IrisMatterSupport;
|
||||
import art.arcane.volmlib.util.matter.IrisMatter;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import art.arcane.volmlib.util.matter.MatterSlice;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import org.junit.Test;
|
||||
@@ -55,30 +57,27 @@ public class IrisCarveModifierBoundarySupportTest {
|
||||
rng.setAccessible(true);
|
||||
rng.set(modifier, new RNG(71L));
|
||||
|
||||
IrisMatterSupport.ensureRegistered();
|
||||
Matter floorMatter = new IrisMatter(16, 16, 16);
|
||||
Matter ceilingMatter = new IrisMatter(16, 16, 16);
|
||||
floorMatter.slice(MatterCavern.class).set(1, 6, 2, new MatterCavern(true, "custom/floor", (byte) 0));
|
||||
ceilingMatter.slice(MatterCavern.class).set(1, 11, 2, new MatterCavern(true, "custom/other", (byte) 0));
|
||||
MantleChunk<Matter> mantleChunk = mock(MantleChunk.class);
|
||||
Matter floorMatter = mock(Matter.class);
|
||||
Matter ceilingMatter = mock(Matter.class);
|
||||
MatterSlice<MatterCavern> floorSlice = mock(MatterSlice.class);
|
||||
MatterSlice<MatterCavern> ceilingSlice = mock(MatterSlice.class);
|
||||
doReturn(3).when(mantleChunk).sectionCount();
|
||||
doReturn(true).when(mantleChunk).exists(0);
|
||||
doReturn(true).when(mantleChunk).exists(2);
|
||||
doReturn(floorMatter).when(mantleChunk).get(0);
|
||||
doReturn(ceilingMatter).when(mantleChunk).get(2);
|
||||
doReturn(true).when(floorMatter).hasSlice(MatterCavern.class);
|
||||
doReturn(true).when(ceilingMatter).hasSlice(MatterCavern.class);
|
||||
doReturn(floorSlice).when(floorMatter).getSlice(MatterCavern.class);
|
||||
doReturn(ceilingSlice).when(ceilingMatter).getSlice(MatterCavern.class);
|
||||
doReturn(new MatterCavern(true, "custom/floor", (byte) 0)).when(floorSlice).get(1, 6, 2);
|
||||
doReturn(null).when(ceilingSlice).get(1, 10, 2);
|
||||
CaveTerrainSnapshot terrain = CaveTerrainSnapshot.capture(mantleChunk, 2, 2);
|
||||
|
||||
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache = new Long2ObjectOpenHashMap<>();
|
||||
Map<String, IrisBiome> customBiomeCache = new HashMap<>();
|
||||
IrisCarveModifier.CaveInputs resolverState = new IrisCarveModifier.CaveInputs(engine);
|
||||
|
||||
IrisBiome floor = modifier.resolveCaveBoundaryBiome(
|
||||
mantleChunk, 1, 6, 2, 40, 44, resolverState, caveBiomeCache, customBiomeCache);
|
||||
terrain, 1, 6, 2, 40, 44, resolverState, caveBiomeCache, customBiomeCache);
|
||||
IrisBiome ceiling = modifier.resolveCaveBoundaryBiome(
|
||||
mantleChunk, 1, 42, 2, 40, 44, resolverState, caveBiomeCache, customBiomeCache);
|
||||
terrain, 1, 42, 2, 40, 44, resolverState, caveBiomeCache, customBiomeCache);
|
||||
|
||||
assertSame(customFloor, floor);
|
||||
assertSame(resolvedCeiling, ceiling);
|
||||
|
||||
+26
@@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
@@ -122,6 +123,31 @@ public class IrisCarveModifierCaveInputsTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentChunkCapturesOnlyItsOwnColumnsAndDispatchesTheRestThroughHistory() {
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
when(fixture.context.isNaturalTerrain()).thenReturn(false);
|
||||
IrisBiome historical = biome("historical", 0);
|
||||
when(fixture.engine.getCaveBiome(anyInt(), anyInt(), anyInt(), any(IrisDimensionCarvingResolver.State.class))).thenReturn(historical);
|
||||
IrisCarveModifier.CaveInputs inputs = IrisCarveModifier.CaveInputs.forContentChunk(fixture.engine, -2, 1);
|
||||
IrisDimensionCarvingResolver.State state = new IrisDimensionCarvingResolver.State();
|
||||
for (int x = -32; x <= -17; x++) {
|
||||
for (int z = 16; z <= 31; z += 5) {
|
||||
for (int y : new int[]{0, 30, 33, 34, 39, 40}) {
|
||||
assertSame(fixture.scalar.getCaveBiome(x, y, z, state), inputs.resolve(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
int captured = fixture.samples.get();
|
||||
assertTrue(captured > 0);
|
||||
assertSame(historical, inputs.resolve(-33, 20, 16));
|
||||
assertSame(historical, inputs.resolve(-16, 20, 16));
|
||||
assertSame(historical, inputs.resolve(-17, 20, 15));
|
||||
assertSame(historical, inputs.resolve(-17, 20, 32));
|
||||
assertEquals(captured, fixture.samples.get());
|
||||
}
|
||||
}
|
||||
|
||||
private static IrisBiome biome(String key, int depth) {
|
||||
IrisBiome biome = new IrisBiome();
|
||||
biome.setLoadKey(key);
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
package art.arcane.iris.generation.stage;
|
||||
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.generation.mantle.CaveTerrainSnapshot;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.terrain.InferredType;
|
||||
@@ -56,7 +57,7 @@ public class IrisCarveModifierInferenceIsolationTest {
|
||||
Method paintBoundaryZone = IrisCarveModifier.class.getDeclaredMethod(
|
||||
"paintBoundaryZone",
|
||||
Hunk.class,
|
||||
MantleChunk.class,
|
||||
CaveTerrainSnapshot.class,
|
||||
CarveWallBuffer.class,
|
||||
int.class,
|
||||
int.class,
|
||||
@@ -72,7 +73,7 @@ public class IrisCarveModifierInferenceIsolationTest {
|
||||
paintBoundaryZone.invoke(
|
||||
modifier,
|
||||
mock(Hunk.class),
|
||||
mock(MantleChunk.class),
|
||||
CaveTerrainSnapshot.capture(mock(MantleChunk.class), 0, 0),
|
||||
walls,
|
||||
0,
|
||||
0,
|
||||
|
||||
@@ -5,6 +5,8 @@ import art.arcane.iris.generation.decoration.IrisCeilingDecorator;
|
||||
import art.arcane.iris.generation.decoration.IrisSurfaceDecorator;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.SeedManager;
|
||||
import art.arcane.iris.generation.context.ChunkContext;
|
||||
import art.arcane.iris.generation.mantle.CaveTerrainSnapshot;
|
||||
import art.arcane.iris.generation.mantle.EngineMantle;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.decoration.IrisDecorationPart;
|
||||
@@ -32,18 +34,22 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class IrisNaturalCaveScanTest {
|
||||
@@ -70,6 +76,26 @@ public class IrisNaturalCaveScanTest {
|
||||
verifyScan(16, 16);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decorationReusesOnlyTheCarveSnapshotOfItsOwnChunk() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
ChunkContext context = mock(ChunkContext.class);
|
||||
doReturn(CaveTerrainSnapshot.capture(fixture.chunk, -2, 3)).when(context).getCaveTerrain();
|
||||
clearInvocations(fixture.chunk);
|
||||
fixture.modifier.decorateNaturalCaves(-32, 48, fixture.output(3, 2), context);
|
||||
verify(fixture.chunk, never()).use();
|
||||
verify(context).setCaveTerrain(null);
|
||||
assertFalse(fixture.calls.isEmpty());
|
||||
|
||||
Fixture other = new Fixture();
|
||||
ChunkContext stale = mock(ChunkContext.class);
|
||||
doReturn(CaveTerrainSnapshot.capture(other.chunk, 5, 5)).when(stale).getCaveTerrain();
|
||||
clearInvocations(other.chunk);
|
||||
other.modifier.decorateNaturalCaves(-32, 48, other.output(3, 2), stale);
|
||||
verify(other.chunk).use();
|
||||
assertEquals(fixture.calls, other.calls);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listenerFailureReleasesMantleChunk() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
@@ -78,7 +104,7 @@ public class IrisNaturalCaveScanTest {
|
||||
throw failure;
|
||||
});
|
||||
assertSame(failure, assertThrows(IllegalStateException.class,
|
||||
() -> fixture.modifier.decorateNaturalCaves(-32, 48, output)));
|
||||
() -> fixture.modifier.decorateNaturalCaves(-32, 48, output, null)));
|
||||
verify(fixture.chunk).release();
|
||||
}
|
||||
|
||||
@@ -92,7 +118,7 @@ public class IrisNaturalCaveScanTest {
|
||||
IllegalStateException failure = new IllegalStateException("height unavailable");
|
||||
doAnswer(call -> { throw failure; }).when(output).getHeight();
|
||||
assertSame(failure, assertThrows(IllegalStateException.class,
|
||||
() -> fixture.modifier.decorateNaturalCaves(-32, 48, output)));
|
||||
() -> fixture.modifier.decorateNaturalCaves(-32, 48, output, null)));
|
||||
verify(fixture.chunk).release();
|
||||
}
|
||||
|
||||
@@ -104,7 +130,7 @@ public class IrisNaturalCaveScanTest {
|
||||
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())));
|
||||
fullOutput.listen((x, y, z, state) -> fullWrites.add(x + ":" + y + ":" + z + ":" + state.key())), null);
|
||||
|
||||
Fixture bounded = new Fixture(true);
|
||||
ChunkGenerator.ChunkData chunkData = mock(ChunkGenerator.ChunkData.class);
|
||||
@@ -114,7 +140,7 @@ public class IrisNaturalCaveScanTest {
|
||||
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())));
|
||||
(x, y, z, state) -> boundedWrites.add(x + ":" + y + ":" + z + ":" + state.key())), null);
|
||||
|
||||
assertTrue("fixture must decorate a zone above the initial column top",
|
||||
full.calls.contains("floor:0:0:27:1"));
|
||||
@@ -141,7 +167,7 @@ public class IrisNaturalCaveScanTest {
|
||||
Hunk<NativeBlockState> output = fixture.output(width, depth);
|
||||
List<String> writes = new ArrayList<>();
|
||||
fixture.modifier.decorateNaturalCaves(-32, 48,
|
||||
output.listen((x, y, z, state) -> writes.add(x + ":" + y + ":" + z + ":" + state.key())));
|
||||
output.listen((x, y, z, state) -> writes.add(x + ":" + y + ":" + z + ":" + state.key())), null);
|
||||
List<String> expectedCalls = new ArrayList<>();
|
||||
List<String> expectedMarkers = new ArrayList<>();
|
||||
List<String> expectedWrites = new ArrayList<>();
|
||||
@@ -239,7 +265,7 @@ public class IrisNaturalCaveScanTest {
|
||||
IrisBiome biome = mock(IrisBiome.class);
|
||||
doReturn(new IrisDecorator[]{new IrisDecorator()}).when(biome).getDecoratorBucket(IrisDecorationPart.NONE);
|
||||
doReturn(new IrisDecorator[]{new IrisDecorator()}).when(biome).getDecoratorBucket(IrisDecorationPart.CEILING);
|
||||
doReturn(biome).when(modifier).resolveCaveBoundaryBiome(eq(chunk), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), any(), any());
|
||||
doReturn(biome).when(modifier).resolveCaveBoundaryBiome(argThat((CaveTerrainSnapshot terrain) -> terrain != null && terrain.covers(-2, 3)), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(), any(), any());
|
||||
IrisDecorantActuator decorant = mock(IrisDecorantActuator.class);
|
||||
IrisSurfaceDecorator surface = mock(IrisSurfaceDecorator.class);
|
||||
IrisCeilingDecorator roof = mock(IrisCeilingDecorator.class);
|
||||
|
||||
Reference in New Issue
Block a user