mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 21:40:05 +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();
|
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() {
|
default IrisData getData() {
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ public final class MantleHydrologyComponent extends IrisMantleComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInputGenerationLazy() {
|
public int getEagerInputRadius(int inputRadius) {
|
||||||
return true;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -128,6 +128,11 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
return calculateInputRadius(getRadius(), hasCollisionRules());
|
return calculateInputRadius(getRadius(), hasCollisionRules());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getEagerInputRadius(int inputRadius) {
|
||||||
|
return Math.min(inputRadius, calculateEagerInputRadius(getRadius(), hasCollisionRules()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void hotload() {
|
public void hotload() {
|
||||||
super.hotload();
|
super.hotload();
|
||||||
@@ -272,6 +277,17 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
return radius > 0 ? Math.ceilDiv(radius, 16) : 0;
|
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) {
|
static int calculateInputRadius(int radius, boolean sourceAnchoredCollisions) {
|
||||||
int normalizedRadius = Math.max(0, radius);
|
int normalizedRadius = Math.max(0, radius);
|
||||||
long sourceChunkRadius = sourceChunkRadius(normalizedRadius);
|
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.generation.noise.IrisGeneratorStyle;
|
||||||
import art.arcane.iris.pack.value.IrisPosition;
|
import art.arcane.iris.pack.value.IrisPosition;
|
||||||
import art.arcane.iris.generation.block.TileData;
|
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.iris.generation.hydrology.cave.HydrologyCaveCell;
|
||||||
import art.arcane.volmlib.util.collection.KSet;
|
import art.arcane.volmlib.util.collection.KSet;
|
||||||
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||||
import art.arcane.volmlib.util.function.Function3;
|
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.Mantle;
|
||||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
@@ -90,6 +92,15 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
|||||||
private final ThreadLocal<Integer> activeComponentPriority = new ThreadLocal<>();
|
private final ThreadLocal<Integer> activeComponentPriority = new ThreadLocal<>();
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
private final ThreadLocal<ObjectPlacementCapture> objectPlacementCapture = new ThreadLocal<>();
|
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) {
|
public MantleWriter(EngineMantle engineMantle, Mantle<Matter> mantle, int x, int z, int radius, boolean multicore) {
|
||||||
this(engineMantle, mantle, x, z, radius, radius * 2, multicore);
|
this(engineMantle, mantle, x, z, radius, radius * 2, multicore);
|
||||||
@@ -104,17 +115,7 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
|||||||
int accessRadius,
|
int accessRadius,
|
||||||
boolean multicore
|
boolean multicore
|
||||||
) {
|
) {
|
||||||
this.engineMantle = engineMantle;
|
this(engineMantle, mantle, x, z, accessRadius);
|
||||||
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);
|
|
||||||
|
|
||||||
final boolean foliaMaintenance = J.isFolia()
|
final boolean foliaMaintenance = J.isFolia()
|
||||||
&& WorldMaintenance.isWorldMaintenanceActive(engineMantle.getEngine().getWorld().identity());
|
&& 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) {
|
static int resolvePrefetchParallelism(boolean foliaMaintenance, boolean multicore, int availableProcessors) {
|
||||||
if (foliaMaintenance) {
|
if (foliaMaintenance) {
|
||||||
return 1;
|
return 1;
|
||||||
@@ -844,20 +881,24 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
MantleChunk<Matter> chunk = window.get(index);
|
||||||
MantleChunk<Matter> chunk = window.get(index);
|
while (chunk == null) {
|
||||||
if (chunk != null) {
|
|
||||||
return chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Losing this race must release our own use, never the winner's: the winner is already
|
// Losing this race must release our own use, never the winner's: the winner is already
|
||||||
// writing through the chunk it published.
|
// writing through the chunk it published.
|
||||||
MantleChunk<Matter> acquired = mantle.getChunk(cx, cz).use();
|
MantleChunk<Matter> acquired = mantle.getChunk(cx, cz).use();
|
||||||
if (window.compareAndSet(index, null, acquired)) {
|
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
|
@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 {
|
public final class ObjectPlacementCapture implements AutoCloseable {
|
||||||
private final ObjectContinuationBundle.PlacementKey key;
|
private final ObjectContinuationBundle.PlacementKey key;
|
||||||
private final Long2ObjectOpenHashMap<Matter> fragments = new Long2ObjectOpenHashMap<>();
|
private final Long2ObjectOpenHashMap<Matter> fragments = new Long2ObjectOpenHashMap<>();
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ public interface MatterGenerator {
|
|||||||
int prefetchRadius = passPlans[0].passChunkRadius();
|
int prefetchRadius = passPlans[0].passChunkRadius();
|
||||||
LongOpenHashSet partialChunks = new LongOpenHashSet();
|
LongOpenHashSet partialChunks = new LongOpenHashSet();
|
||||||
List<MantleComponent> requiredComponents = enabledComponents();
|
List<MantleComponent> requiredComponents = enabledComponents();
|
||||||
List<MantleFlag> terrainFlags = terrainFlags(requiredComponents);
|
boolean terrainOnAccess = false;
|
||||||
|
|
||||||
try (MantleWriter writer = new MantleWriter(
|
try (MantleWriter writer = new MantleWriter(
|
||||||
getEngine().getMantle(),
|
getEngine().getMantle(),
|
||||||
@@ -129,6 +129,10 @@ public interface MatterGenerator {
|
|||||||
if (enabledComponentCount == 0) {
|
if (enabledComponentCount == 0) {
|
||||||
continue;
|
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.
|
// 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
|
// A caller outside the pool spreads its window across the pool; a pool thread
|
||||||
@@ -185,9 +189,6 @@ public interface MatterGenerator {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (phase == MatterGenerationPhase.CONTENT) {
|
|
||||||
requireTerrainPhase(chunk, passX, passZ, terrainFlags);
|
|
||||||
}
|
|
||||||
int eligibleComponentCount = 0;
|
int eligibleComponentCount = 0;
|
||||||
for (int componentIndex = 0; componentIndex < enabledComponentCount; componentIndex++) {
|
for (int componentIndex = 0; componentIndex < enabledComponentCount; componentIndex++) {
|
||||||
MantleComponent component = enabledComponents[componentIndex];
|
MantleComponent component = enabledComponents[componentIndex];
|
||||||
@@ -303,9 +304,8 @@ public interface MatterGenerator {
|
|||||||
: 0;
|
: 0;
|
||||||
int componentInputRadius = component.getInputRadius(x, z, invocationChunkRadius, context);
|
int componentInputRadius = component.getInputRadius(x, z, invocationChunkRadius, context);
|
||||||
passAccessInputRadius = Math.max(passAccessInputRadius, componentInputRadius);
|
passAccessInputRadius = Math.max(passAccessInputRadius, componentInputRadius);
|
||||||
if (!component.isInputGenerationLazy()) {
|
passGenerationInputRadius = Math.max(passGenerationInputRadius,
|
||||||
passGenerationInputRadius = Math.max(passGenerationInputRadius, componentInputRadius);
|
component.getEagerInputRadius(componentInputRadius));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
int accessInvocationRadius = accessDownstreamBlockRadius + passBlockRadius;
|
int accessInvocationRadius = accessDownstreamBlockRadius + passBlockRadius;
|
||||||
int generationInvocationRadius = generationDownstreamBlockRadius + passBlockRadius;
|
int generationInvocationRadius = generationDownstreamBlockRadius + passBlockRadius;
|
||||||
@@ -332,24 +332,23 @@ public interface MatterGenerator {
|
|||||||
return List.copyOf(components);
|
return List.copyOf(components);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<MantleFlag> terrainFlags(List<MantleComponent> components) {
|
private static boolean includesContent(MantleComponent[] components, int count) {
|
||||||
List<MantleFlag> flags = new ArrayList<>();
|
for (int index = 0; index < count; index++) {
|
||||||
for (MantleComponent component : components) {
|
if (components[index].getGenerationPhase() == MatterGenerationPhase.CONTENT) {
|
||||||
if (component.getGenerationPhase() == MatterGenerationPhase.TERRAIN) {
|
return true;
|
||||||
flags.add(component.getFlag());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return List.copyOf(flags);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void requireTerrainPhase(MantleChunk<Matter> chunk, int chunkX, int chunkZ,
|
private static List<MantleComponent> terrainComponents(List<MantleComponent> components) {
|
||||||
List<MantleFlag> flags) {
|
List<MantleComponent> terrain = new ArrayList<>();
|
||||||
for (MantleFlag flag : flags) {
|
for (MantleComponent component : components) {
|
||||||
if (!chunk.isFlagged(flag)) {
|
if (component.getGenerationPhase() == MatterGenerationPhase.TERRAIN) {
|
||||||
throw new IllegalStateException("Content generation at " + chunkX + "," + chunkZ
|
terrain.add(component);
|
||||||
+ " requires completed terrain component " + flag.name());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return terrain;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean hasCompletedComponents(MantleChunk<Matter> chunk, List<MantleComponent> components) {
|
private static boolean hasCompletedComponents(MantleChunk<Matter> chunk, List<MantleComponent> components) {
|
||||||
|
|||||||
+18
@@ -45,6 +45,7 @@ import java.util.List;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
@@ -188,6 +189,23 @@ public class MantleObjectComponentBoundaryRadiusTest {
|
|||||||
MantleObjectComponent.calculateInputRadius(Integer.MAX_VALUE, true));
|
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
|
@Test
|
||||||
public void proceduralTreeTransformsContributeToRadius() {
|
public void proceduralTreeTransformsContributeToRadius() {
|
||||||
IrisObjectPlacement placement = new IrisObjectPlacement()
|
IrisObjectPlacement placement = new IrisObjectPlacement()
|
||||||
|
|||||||
+2
-2
@@ -309,8 +309,8 @@ public class MatterGeneratorCarvePassRadiusTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInputGenerationLazy() {
|
public int getEagerInputRadius(int inputRadius) {
|
||||||
return lazyInputGeneration;
|
return lazyInputGeneration ? 0 : inputRadius;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+71
-4
@@ -387,27 +387,94 @@ public class MatterGeneratorConcurrencyTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void contentPhaseRequiresTerrainEvenWhenItsExplicitPrerequisiteIsMissing() {
|
public void contentPhaseGeneratesTerrainOfEveryChunkBeforeItIsReadOrWritten() {
|
||||||
GeneratorFixture fixture = new GeneratorFixture(true);
|
GeneratorFixture fixture = new GeneratorFixture(true);
|
||||||
|
List<String> events = new ArrayList<>();
|
||||||
RecordingComponent terrain = new RecordingComponent(ReservedFlag.CARVED, 0, 0) {
|
RecordingComponent terrain = new RecordingComponent(ReservedFlag.CARVED, 0, 0) {
|
||||||
@Override
|
@Override
|
||||||
public MatterGenerationPhase getGenerationPhase() {
|
public MatterGenerationPhase getGenerationPhase() {
|
||||||
return MatterGenerationPhase.TERRAIN;
|
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) {
|
RecordingComponent content = new RecordingComponent(ReservedFlag.OBJECT, 1, 0) {
|
||||||
@Override
|
@Override
|
||||||
public MantleFlag[] getPrerequisiteFlags() {
|
public MantleFlag[] getPrerequisiteFlags() {
|
||||||
return new MantleFlag[]{ReservedFlag.CARVED};
|
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(
|
TestMatterGenerator generator = fixture.generator(List.of(
|
||||||
new MantlePass(List.of(terrain), 0, 0),
|
new MantlePass(List.of(terrain), 0, 0),
|
||||||
new MantlePass(List.of(content), 0, 0)));
|
new MantlePass(List.of(content), 0, 0)));
|
||||||
|
|
||||||
assertThrows(IllegalStateException.class,
|
generator.generateTerrainMatter(0, 0, false, fixture.context);
|
||||||
() -> generator.generateContentMatter(0, 0, false, fixture.context));
|
assertEquals(List.of("terrain 0,0"), events);
|
||||||
assertFalse(fixture.mantle.getChunk(0, 0).isFlagged(MantleFlag.PLANNED));
|
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
|
@Test
|
||||||
|
|||||||
Reference in new issue
Block a user