mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +00:00
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:
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) {
|
||||
|
||||
Reference in new issue
Block a user