Carve terrain on demand

- eager terrain window covers the replayed object sources plus one chunk of footprint (radius 13 instead of 19 for overworld)
- content writers generate a chunk's missing terrain components before handing it out, so reads past the window stay identical
- getEagerInputRadius replaces isInputGenerationLazy
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-28 13:32:53 -04:00
1 parent 8d93431319
commit 808e133cac
8 files changed
+231 -48

No files matched your search

@@ -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,15 @@ 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)
@EqualsAndHashCode.Exclude
@ToString.Exclude
private final boolean[] terrainReady;
@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 +115,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 +143,42 @@ 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);
this.terrainReady = new boolean[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 +881,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 && !terrainReady[index]) {
access.ensure(chunk, cx, cz);
terrainReady[index] = true;
}
return chunk;
}
@ChunkCoordinates
@@ -1509,6 +1550,44 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
}
}
private final class TerrainAccess {
private final MantleComponent[] components;
private final ChunkContext context;
private final IrisComplex complex;
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) {