mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-28 23:10:32 +00:00
Rewrite object placement as resolved lifecycle modules
This commit is contained in:
1 parent
2d211cf106
commit
46d458e6e4
44 files changed
+3156
-1449
No files matched your search
+62
@@ -0,0 +1,62 @@
|
||||
package art.arcane.iris.generation.runtime;
|
||||
|
||||
import art.arcane.iris.generation.runtime.EngineEffects;
|
||||
import art.arcane.iris.generation.runtime.EngineMode;
|
||||
import art.arcane.iris.generation.runtime.EngineTarget;
|
||||
import art.arcane.iris.generation.runtime.EngineWorldManager;
|
||||
import art.arcane.iris.structure.object.ObjectPlaceMode;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class EnginePlacementLifecycleTest {
|
||||
@Test
|
||||
public void runtimeAssemblyResolvesBeforePublicationAndKeepsOldPlansIntact() {
|
||||
var initial = assembly(1);
|
||||
EngineRuntime previous = initial.freezeRuntime(new GenerationRuntime.BiomeMaxes(0, 0, 0));
|
||||
var replacement = assembly(2);
|
||||
EngineRuntime next = replacement.freezeRuntime(new GenerationRuntime.BiomeMaxes(0, 0, 0));
|
||||
|
||||
assertSame(initial.placementLifecycles, previous.generation().placementLifecycles());
|
||||
assertSame(replacement.placementLifecycles, next.generation().placementLifecycles());
|
||||
for (ObjectPlaceMode mode : ObjectPlaceMode.values()) {
|
||||
assertNotNull(next.generation().placementLifecycles().get(mode));
|
||||
assertNotSame(previous.generation().placementLifecycles().get(mode), next.generation().placementLifecycles().get(mode));
|
||||
}
|
||||
assertSame(initial.placementLifecycles, previous.generation().placementLifecycles());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void complexReplacementCarriesNewPlansAlongsideTheNewComplex() {
|
||||
var initial = assembly(1);
|
||||
EngineRuntime previous = initial.freezeRuntime(new GenerationRuntime.BiomeMaxes(0, 0, 0));
|
||||
var replacement = assembly(2);
|
||||
EngineRuntime next = previous.withGeneration(previous.generation().withComplex(2, replacement.complex, null, null,
|
||||
new GenerationRuntime.BiomeMaxes(1, 1, 1), replacement.placementLifecycles));
|
||||
|
||||
assertSame(replacement.placementLifecycles, next.generation().placementLifecycles());
|
||||
assertSame(replacement.complex, next.generation().complex());
|
||||
assertSame(previous.worldManager(), next.worldManager());
|
||||
assertSame(initial.placementLifecycles, previous.generation().placementLifecycles());
|
||||
}
|
||||
|
||||
private EngineRuntimeBuilder.RuntimeAssembly assembly(int id) {
|
||||
EngineTarget target = mock(EngineTarget.class);
|
||||
var world = mock(art.arcane.iris.world.IrisWorld.class);
|
||||
org.mockito.Mockito.when(target.getWorld()).thenReturn(world);
|
||||
org.mockito.Mockito.when(target.getData()).thenReturn(mock(art.arcane.iris.pack.loading.IrisData.class));
|
||||
org.mockito.Mockito.when(target.getDimension()).thenReturn(mock(art.arcane.iris.generation.terrain.IrisDimension.class));
|
||||
var assembly = new EngineRuntimeBuilder.RuntimeAssembly(id, target,
|
||||
java.nio.file.Path.of("build", "test-mantles", "placement-" + id));
|
||||
assembly.mantle = mock(art.arcane.iris.generation.mantle.EngineMantle.class);
|
||||
assembly.complex = mock(IrisComplex.class);
|
||||
assembly.effects = mock(EngineEffects.class);
|
||||
assembly.mode = mock(EngineMode.class);
|
||||
assembly.worldManager = mock(EngineWorldManager.class);
|
||||
assembly.hash32 = CompletableFuture.completedFuture(0L);
|
||||
return assembly;
|
||||
}
|
||||
}
|
||||
@@ -858,7 +858,7 @@ public class EngineShutdownDrainTest {
|
||||
when(generation.runtimeKernel()).thenReturn(kernel);
|
||||
GenerationRuntime nextGeneration = generation(target, mantle);
|
||||
when(nextGeneration.complex()).thenReturn(replacement);
|
||||
when(generation.withComplex(anyInt(), same(replacement), isNull(), isNull(), any()))
|
||||
when(generation.withComplex(anyInt(), same(replacement), isNull(), isNull(), any(), any()))
|
||||
.thenReturn(nextGeneration);
|
||||
EngineRuntimeBuilder builder = spy(new EngineRuntimeBuilder(engine));
|
||||
doReturn(new GenerationRuntime.BiomeMaxes(0D, 0D, 0D)).when(builder).computeBiomeMaxes();
|
||||
|
||||
+4
-1
@@ -335,6 +335,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
|
||||
assertSame(detached.dimension, engine.getDimension());
|
||||
assertSame(detached.runtime.seedManager(), engine.getSeedManager());
|
||||
assertSame(detached.complex, engine.getComplex());
|
||||
assertSame(detached.runtime.placementLifecycles(), engine.getPlacementLifecycles());
|
||||
assertSame(detached.upperContext, engine.getUpperContext());
|
||||
assertSame(detached.dimensionStackContext, engine.getDimensionStackContext());
|
||||
assertSame(detached.mode, engine.getMode());
|
||||
@@ -351,6 +352,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
|
||||
assertSame(active.target, engine.getTarget());
|
||||
assertSame(active.data, engine.getData());
|
||||
assertSame(active.runtime.seedManager(), engine.getSeedManager());
|
||||
assertSame(active.runtime.placementLifecycles(), engine.getPlacementLifecycles());
|
||||
assertSame(active.mantle, engine.getMantle());
|
||||
assertEquals(1, engine.getCacheID());
|
||||
}
|
||||
@@ -1009,7 +1011,8 @@ public class IrisEngineGenerationRuntimeScopeTest {
|
||||
mantle,
|
||||
mantleStorageDirectory,
|
||||
hash32,
|
||||
new BiomeMaxes(objectDensity, layerDensity, decoratorDensity));
|
||||
new BiomeMaxes(objectDensity, layerDensity, decoratorDensity),
|
||||
art.arcane.iris.structure.object.PlacementLifecycles.resolve());
|
||||
return new RuntimeFixture(
|
||||
runtime,
|
||||
target,
|
||||
|
||||
+617
@@ -0,0 +1,617 @@
|
||||
package art.arcane.iris.structure.object;
|
||||
|
||||
import art.arcane.iris.generation.terrain.IrisSlopeClip;
|
||||
|
||||
import art.arcane.iris.generation.noise.IrisNoiseGenerator;
|
||||
|
||||
import art.arcane.iris.generation.block.TileData;
|
||||
import art.arcane.iris.generation.cave.CarveResult;
|
||||
import art.arcane.iris.generation.cave.CarvingMode;
|
||||
import art.arcane.iris.generation.decoration.IrisStiltSettings;
|
||||
import art.arcane.iris.generation.decoration.IrisVacuumSettings;
|
||||
import art.arcane.iris.generation.geometry.IrisBlockVector;
|
||||
import art.arcane.iris.testsupport.PlatformLeakGuard;
|
||||
import org.junit.ClassRule;
|
||||
|
||||
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
|
||||
import art.arcane.iris.structure.placement.PlacedObject;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.iris.spi.PlatformRegistries;
|
||||
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisObjectPlacementLifecycleRegressionTest {
|
||||
@ClassRule
|
||||
public static final PlatformLeakGuard PLATFORM_GUARD = PlatformLeakGuard.clean();
|
||||
|
||||
private static final int ANCHOR_Y = 80;
|
||||
|
||||
private IrisData data;
|
||||
private Engine engine;
|
||||
private NativeBlockState solid;
|
||||
private NativeBlockState schematicAir;
|
||||
|
||||
@Before
|
||||
public void bindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||
Map<String, NativeBlockState> registryStates = new HashMap<>();
|
||||
when(registries.block(anyString())).thenAnswer(invocation -> {
|
||||
String key = invocation.getArgument(0);
|
||||
return registryStates.computeIfAbsent(key, value -> state(value.toLowerCase(), !value.toLowerCase().contains("air")));
|
||||
});
|
||||
IrisPlatform platform = mock(IrisPlatform.class);
|
||||
when(platform.registries()).thenReturn(registries);
|
||||
IrisPlatforms.bind(platform);
|
||||
|
||||
engine = mock(Engine.class);
|
||||
when(engine.getHeight()).thenReturn(256);
|
||||
data = mock(IrisData.class);
|
||||
when(data.getEngine()).thenReturn(engine);
|
||||
solid = state("minecraft:stone", true);
|
||||
schematicAir = state("minecraft:air", false);
|
||||
}
|
||||
|
||||
@After
|
||||
public void unbindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void placementFailurePropagatesToTheCaller() {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
placer.failAfterWrites(1);
|
||||
IrisObject object = lineObject(3);
|
||||
|
||||
IllegalStateException error = assertThrows(IllegalStateException.class,
|
||||
() -> object.place(0, ANCHOR_Y, 0, placer, placement(), new RNG(2L), data));
|
||||
|
||||
assertTrue(error.getMessage().contains("write failed"));
|
||||
assertEquals(1, placer.writes().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void boreUsesRotatedTranslationAndRandomizedAnchor() {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
IrisObjectPlacement placement = placement();
|
||||
placement.setBore(true);
|
||||
placement.setRotation(IrisObjectRotation.of(0, 90, 0));
|
||||
placement.setTranslate(new IrisObjectTranslate().setX(4).setYRandom(3));
|
||||
|
||||
int resultY = lineObject(3).place(0, ANCHOR_Y, 0, placer, placement, new RNG(2L), data);
|
||||
|
||||
List<BlockWrite> airWrites = placer.writesOf(IrisObject.States.air());
|
||||
assertFalse(airWrites.isEmpty());
|
||||
assertTrue(airWrites.stream().allMatch(write -> write.y() >= resultY && write.y() <= resultY));
|
||||
assertTrue(airWrites.stream().allMatch(write -> write.x() == 0));
|
||||
assertTrue(airWrites.stream().allMatch(write -> write.z() <= -3 && write.z() >= -5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void collisionCheckUsesTheTransformedBounds() {
|
||||
PlacedObject forbidden = new PlacedObject(null, object("forbidden"), 1, 0, 0);
|
||||
when(engine.resolveObjectPlacementMarker(anyInt(), anyInt(), org.mockito.ArgumentMatchers.isNull())).thenAnswer(invocation -> {
|
||||
int x = invocation.getArgument(0);
|
||||
int z = invocation.getArgument(1);
|
||||
return x == 0 && z == -4 ? forbidden : null;
|
||||
});
|
||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||
IrisObjectPlacement placement = placement();
|
||||
placement.setRotation(IrisObjectRotation.of(0, 90, 0));
|
||||
placement.setTranslate(new IrisObjectTranslate().setX(4));
|
||||
placement.getForbiddenCollisions().add("forbidden");
|
||||
|
||||
int result = lineObject(3).place(0, ANCHOR_Y, 0, placer, placement, new RNG(2L), data);
|
||||
|
||||
assertEquals(-1, result);
|
||||
assertTrue(placer.writes().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void arbitraryRotationSamplesEveryTransformedFootprintColumn() {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
IrisObjectPlacement placement = placement();
|
||||
placement.setMode(ObjectPlaceMode.MAX_HEIGHT);
|
||||
placement.setRotation(IrisObjectRotation.of(0, 45, 0));
|
||||
|
||||
boxObject(5, 1, 3).place(0, -1, 0, placer, placement, new RNG(2L), data);
|
||||
|
||||
assertTrue(placer.sampledColumns().contains("-2:-2"));
|
||||
assertTrue(placer.sampledColumns().contains("2:2"));
|
||||
assertTrue(placer.sampledColumns().size() >= 25);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void snowUsesOnlyBlocksThatWereWritten() {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
IrisObject object = new IrisObject(1, 3, 1);
|
||||
object.setUnsigned(0, 0, 0, solid);
|
||||
object.setUnsigned(0, 2, 0, schematicAir);
|
||||
IrisObjectPlacement placement = placement();
|
||||
placement.setSnow(0.1);
|
||||
|
||||
object.place(0, ANCHOR_Y, 0, placer, placement, new RNG(2L), data);
|
||||
|
||||
List<BlockWrite> snowWrites = placer.writesOf(IrisObject.States.snowLayer(0));
|
||||
assertEquals(1, snowWrites.size());
|
||||
assertEquals(ANCHOR_Y, snowWrites.get(0).y());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void debugPlacementDoesNotMutateSmartBoreCache() {
|
||||
IrisObject object = new IrisObject(1, 1, 1);
|
||||
object.setUnsigned(0, 0, 0, IrisObject.States.vair());
|
||||
object.setSmartBored(true);
|
||||
IrisObjectPlacement placement = placement();
|
||||
placement.setSmartBore(true);
|
||||
RecordingPlacer debug = new RecordingPlacer(null);
|
||||
debug.setDebugSmartBore(true);
|
||||
RecordingPlacer normal = new RecordingPlacer(null);
|
||||
|
||||
object.place(0, ANCHOR_Y, 0, debug, placement, new RNG(2L), data);
|
||||
object.place(0, ANCHOR_Y, 0, normal, placement, new RNG(2L), data);
|
||||
|
||||
NativeBlockState cachedState = object.getBlocks().get(object.getSigned(0, 0, 0));
|
||||
assertSame(IrisObject.States.vair(), cachedState);
|
||||
if (IrisObject.States.vair() != IrisObject.States.vairDebug()) {
|
||||
assertTrue(cachedState != IrisObject.States.vairDebug());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void placementDataIsRequiredAtTheBoundary() {
|
||||
NullPointerException error = assertThrows(NullPointerException.class,
|
||||
() -> lineObject(1).place(0, ANCHOR_Y, 0, new RecordingPlacer(null), placement(), new RNG(2L), null));
|
||||
|
||||
assertEquals("Object placement data is required.", error.getMessage());
|
||||
}
|
||||
|
||||
private IrisObjectPlacement placement() {
|
||||
IrisObjectPlacement placement = new IrisObjectPlacement();
|
||||
placement.setMode(ObjectPlaceMode.CENTER_HEIGHT);
|
||||
placement.setRequireSurfaceSupport(false);
|
||||
return placement;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stiltWaitsForTheWholeTransformedFootprint() {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
IrisObjectPlacement config = placement();
|
||||
config.setMode(ObjectPlaceMode.STILT);
|
||||
config.setRotation(IrisObjectRotation.of(0, 90, 0));
|
||||
config.setTranslate(new IrisObjectTranslate().setX(4));
|
||||
|
||||
lineObject(3).place(0, ANCHOR_Y + 4, 0, placer, config, new RNG(2L), data);
|
||||
|
||||
// Every bottom column reaches the ground after rotation and translation.
|
||||
for (int z = -5; z <= -3; z++) {
|
||||
assertSame(solid, placer.get(0, ANCHOR_Y, z));
|
||||
assertSame(solid, placer.get(0, ANCHOR_Y + 3, z));
|
||||
}
|
||||
assertTrue(placer.writes().stream().allMatch(write -> write.x() == 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ceilingFlipsBeforeTranslationAndFillsUpward() {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
placer.world.put("0:90:0", solid);
|
||||
IrisObjectPlacement config = placement();
|
||||
config.setMode(ObjectPlaceMode.CEILING_HANG);
|
||||
config.setTranslate(new IrisObjectTranslate().setY(3));
|
||||
config.setStiltSettings(new IrisStiltSettings().setOrganicJitter(0).setOrganicScratch(0));
|
||||
|
||||
boxObject(1, 3, 1).place(0, ANCHOR_Y, 0, placer, config, new RNG(2L), data);
|
||||
|
||||
assertSame(solid, placer.get(0, 82, 0));
|
||||
assertSame(solid, placer.get(0, 89, 0));
|
||||
assertTrue(placer.writes().stream().noneMatch(write -> write.y() < 82 || write.y() >= 90));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void organicStiltStopsAtTheFloor() {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
placer.world.put("0:75:0", solid);
|
||||
IrisObjectPlacement config = placement();
|
||||
config.setMode(ObjectPlaceMode.ORGANIC_STILT);
|
||||
config.setStiltSettings(new IrisStiltSettings().setOrganicJitter(0).setOrganicScratch(0));
|
||||
|
||||
lineObject(1).place(0, ANCHOR_Y, 0, placer, config, new RNG(2L), data);
|
||||
|
||||
for (int y = 76; y <= ANCHOR_Y; y++) {
|
||||
assertSame(solid, placer.get(0, y, 0));
|
||||
}
|
||||
assertTrue(placer.writes().stream().noneMatch(write -> write.y() <= 75));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitMinimumHeightAndFloatingDoNotNeedTerrainSampling() {
|
||||
for (ObjectPlaceMode mode : List.of(ObjectPlaceMode.MIN_HEIGHT, ObjectPlaceMode.FAST_MIN_HEIGHT,
|
||||
ObjectPlaceMode.FLOATING, ObjectPlaceMode.STRUCTURE_PIECE)) {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
IrisObjectPlacement config = placement();
|
||||
config.setMode(mode);
|
||||
assertEquals(ANCHOR_Y, lineObject(1).place(0, ANCHOR_Y, 0, placer, config, new RNG(2L), data));
|
||||
assertTrue(mode.toString(), placer.sampledColumns().isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runnerUsesTheEnginePlanAndCompletesTheBlockPassBeforeAfterBlocks() {
|
||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||
PlacementLifecycleBuilder builder = new PlacementLifecycleBuilder();
|
||||
ObjectPlaceMode.CENTER_HEIGHT.install(builder);
|
||||
builder.add(new PlacementLifecycleDependency("test.offset"), PlacementStage.HEIGHT,
|
||||
(context, state, ignored) -> state.y += 5, PlacementModuleAnchor.HEIGHT);
|
||||
List<Integer> finishedSizes = new ArrayList<>();
|
||||
builder.add(new PlacementLifecycleDependency("test.finished"), PlacementStage.AFTER_BLOCKS,
|
||||
(context, state, ignored) -> finishedSizes.add(placer.writes().size()));
|
||||
PlacementLifecycles plans = mock(PlacementLifecycles.class);
|
||||
when(plans.get(ObjectPlaceMode.CENTER_HEIGHT)).thenReturn(builder.build());
|
||||
when(engine.getPlacementLifecycles()).thenReturn(plans);
|
||||
|
||||
assertEquals(ANCHOR_Y + 5, lineObject(3).place(0, ANCHOR_Y, 0, placer, placement(), new RNG(2L), data));
|
||||
assertEquals(List.of(3), finishedSizes);
|
||||
assertTrue(placer.writes().stream().allMatch(write -> write.y() == ANCHOR_Y + 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromBottomKeepsItsAnchorWithoutSamplingTerrain() {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
IrisObjectPlacement config = placement();
|
||||
config.setFromBottom(true);
|
||||
assertEquals(2, lineObject(1).place(0, -1, 0, placer, config, new RNG(2L), data));
|
||||
assertTrue(placer.sampledColumns().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eligibilityGuardsRejectBeforeBoreAndOwnTheirForcePlaceExemptions() {
|
||||
for (String reason : List.of("carving-anchor", "underwater-anchor", "cave-clearance", "fluid-height", "height-clamp")) {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
IrisObjectPlacement config = placement().setBore(true).setSnow(0.1);
|
||||
CarveResult cave = null;
|
||||
int anchor = ANCHOR_Y;
|
||||
switch (reason) {
|
||||
case "carving-anchor" -> {
|
||||
placer.carved = true;
|
||||
config.setCarvingSupport(CarvingMode.SURFACE_ONLY);
|
||||
}
|
||||
case "underwater-anchor" -> {
|
||||
placer.underwater = true;
|
||||
anchor = -1;
|
||||
}
|
||||
case "cave-clearance" -> cave = new CarveResult(ANCHOR_Y, ANCHOR_Y + 1);
|
||||
case "fluid-height" -> {
|
||||
config.setUnderwater(true);
|
||||
placer.fluidHeight = ANCHOR_Y;
|
||||
}
|
||||
case "height-clamp" -> config.getClamp().setMaximumHeight(ANCHOR_Y - 1);
|
||||
}
|
||||
PlacementState result = execute(config, anchor, placer, cave);
|
||||
assertEquals(reason, result.getRejection());
|
||||
assertEquals(-1, result.heightOrRejected());
|
||||
assertTrue(reason, placer.writes().isEmpty());
|
||||
|
||||
config.setForcePlace(true);
|
||||
assertFalse(reason, execute(config, anchor, placer, cave).isRejected());
|
||||
assertFalse(reason, placer.writes().isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forcePlaceCannotBypassNativeProtectionButStructurePiecesCan() {
|
||||
when(engine.getNativeStructureVolumes(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(
|
||||
new KList<>(NativeStructureVolume.of("minecraft:village", 0, ANCHOR_Y, 0, 0, ANCHOR_Y, 0)));
|
||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||
IrisObjectPlacement config = placement().setForcePlace(true).setBore(true).setSnow(0.1);
|
||||
|
||||
assertEquals("native-structure", execute(config, ANCHOR_Y, placer, null).getRejection());
|
||||
assertTrue(placer.writes().isEmpty());
|
||||
|
||||
config.setMode(ObjectPlaceMode.STRUCTURE_PIECE);
|
||||
assertEquals(ANCHOR_Y, execute(config, ANCHOR_Y, placer, null).heightOrRejected());
|
||||
assertFalse(placer.writes().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void collisionRejectionPrecedesBoreAndAllowedObjectsOverrideForbiddenOnes() {
|
||||
PlacedObject forbidden = new PlacedObject(null, object("forbidden"), 1, 0, 0);
|
||||
when(engine.resolveObjectPlacementMarker(anyInt(), anyInt(), org.mockito.ArgumentMatchers.isNull())).thenReturn(forbidden);
|
||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||
IrisObjectPlacement config = placement().setBore(true);
|
||||
config.getForbiddenCollisions().add("forbidden");
|
||||
|
||||
assertEquals("object-collision", execute(config, ANCHOR_Y, placer, null).getRejection());
|
||||
assertTrue(placer.writes().isEmpty());
|
||||
|
||||
config.getAllowedCollisions().add("forbidden");
|
||||
assertFalse(execute(config, ANCHOR_Y, placer, null).isRejected());
|
||||
assertFalse(placer.writes().isEmpty());
|
||||
|
||||
config.getAllowedCollisions().clear();
|
||||
config.setForcePlace(true);
|
||||
assertFalse(execute(config, ANCHOR_Y, new RecordingPlacer(engine), null).isRejected());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingCollisionEngineFailsBeforeWrites() {
|
||||
when(data.getEngine()).thenReturn(null);
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
IrisObjectPlacement config = placement().setBore(true);
|
||||
config.getForbiddenCollisions().add("forbidden");
|
||||
assertEquals("Object placement requires active Iris data for collision settings.",
|
||||
assertThrows(IllegalStateException.class,
|
||||
() -> execute(config, ANCHOR_Y, placer, null)).getMessage());
|
||||
assertTrue(placer.writes().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void vacuumAndSnowObserveOnlyWrittenBlocksAndFinishInOrder() {
|
||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||
IrisObject object = new IrisObject(1, 3, 1);
|
||||
object.setUnsigned(0, 0, 0, solid);
|
||||
object.setUnsigned(0, 2, 0, schematicAir);
|
||||
IrisObjectPlacement config = placement().setMode(ObjectPlaceMode.VACUUM).setSnow(0.1);
|
||||
config.setTranslate(new IrisObjectTranslate().setY(3));
|
||||
config.setVacuumSettings(new IrisVacuumSettings().setRadius(1));
|
||||
|
||||
object.place(0, ANCHOR_Y, 0, placer, config, new RNG(2L), data);
|
||||
|
||||
List<BlockWrite> center = placer.writes().stream().filter(write -> write.x() == 0 && write.z() == 0).toList();
|
||||
assertEquals(List.of(82, 81, 83), center.stream().map(BlockWrite::y).toList());
|
||||
assertSame(solid, center.get(0).state());
|
||||
assertSame(IrisObject.States.snowLayer(0), center.get(2).state());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preparationPreservesRandomDrawsForPlacementAndLaterCallers() {
|
||||
for (int range : List.of(-3, 0, 3)) {
|
||||
RNG rng = new RNG(91);
|
||||
RNG expected = new RNG(91);
|
||||
expected.imax();
|
||||
expected.imax();
|
||||
expected.imax();
|
||||
int offset = range > 0 ? expected.i(0, range) : range < 0 ? expected.i(range, 0) : 0;
|
||||
IrisObjectPlacement config = placement().setBore(true);
|
||||
config.setTranslate(new IrisObjectTranslate().setYRandom(range));
|
||||
assertEquals(ANCHOR_Y + offset,
|
||||
lineObject(1).place(0, ANCHOR_Y, 0, new RecordingPlacer(null), config, rng, data));
|
||||
assertEquals(expected.nextLong(), rng.nextLong());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void heightmapPreparationKeepsItsSeedDrawBeforeRotation() {
|
||||
RNG rng = new RNG(91);
|
||||
RNG expected = new RNG(91);
|
||||
expected.nextLong();
|
||||
expected.imax();
|
||||
expected.imax();
|
||||
expected.imax();
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
IrisObjectPlacement config = placement();
|
||||
config.setHeightmap(new IrisNoiseGenerator(false).setOffsetY(93));
|
||||
|
||||
assertEquals(93, lineObject(1).place(0, -1, 0, placer, config, rng, data));
|
||||
assertTrue(placer.sampledColumns().isEmpty());
|
||||
assertEquals(expected.nextLong(), rng.nextLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void slopeRejectionConsumesNoTransformRandomness() {
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
ProceduralStream<Double> slopes = mock(ProceduralStream.class);
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(complex.getSlopeStream()).thenReturn(slopes);
|
||||
when(slopes.get(anyDouble(), anyDouble())).thenReturn(0.5);
|
||||
IrisObjectPlacement config = placement().setBore(true);
|
||||
config.setSlopeCondition(new IrisSlopeClip(1, 2));
|
||||
RNG rng = new RNG(91);
|
||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||
|
||||
assertEquals(-1, lineObject(1).place(0, ANCHOR_Y, 0, placer, config, rng, data));
|
||||
assertEquals(new RNG(91).nextLong(), rng.nextLong());
|
||||
assertTrue(placer.writes().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void slopeRotationUsesAPerPlacementCopy() {
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
ProceduralStream<Double> heights = mock(ProceduralStream.class);
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(complex.getHeightStream()).thenReturn(heights);
|
||||
when(heights.get(anyDouble(), anyDouble())).thenAnswer(call -> call.<Double>getArgument(0) > 0 ? 70D : 80D);
|
||||
IrisObjectPlacement config = placement().setRotateTowardsSlope(true);
|
||||
IrisObjectRotation original = config.getRotation();
|
||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||
|
||||
lineObject(3).place(0, ANCHOR_Y, 0, placer, config, new RNG(2L), data);
|
||||
|
||||
assertSame(original, config.getRotation());
|
||||
assertEquals(0, original.getYAxis().getMin(), 0);
|
||||
assertEquals(3, placer.writes().size());
|
||||
assertTrue(placer.writes().stream().allMatch(write -> write.x() == 0));
|
||||
assertEquals(List.of(-1, 0, 1), placer.writes().stream().map(BlockWrite::z).sorted().toList());
|
||||
}
|
||||
|
||||
private PlacementState execute(IrisObjectPlacement config, int y, RecordingPlacer placer, CarveResult cave) {
|
||||
return PlacementLifecycles.defaults().get(config.getMode()).execute(
|
||||
new PlacementContext(config, data, placer, cave, new RNG(2L), lineObject(1), 0, 0, y, null));
|
||||
}
|
||||
|
||||
private IrisObject lineObject(int width) {
|
||||
IrisObject object = new IrisObject(width, 1, 1);
|
||||
for (int x = 0; x < width; x++) {
|
||||
object.setUnsigned(x, 0, 0, solid);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
private IrisObject boxObject(int width, int height, int depth) {
|
||||
IrisObject object = new IrisObject(width, height, depth);
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int z = 0; z < depth; z++) {
|
||||
object.setUnsigned(x, y, z, solid);
|
||||
}
|
||||
}
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
private IrisObject object(String key) {
|
||||
IrisObject object = lineObject(1);
|
||||
object.setLoadKey(key);
|
||||
return object;
|
||||
}
|
||||
|
||||
private static NativeBlockState state(String key, boolean solid) {
|
||||
NativeBlockState state = mock(NativeBlockState.class);
|
||||
when(state.key()).thenReturn(key);
|
||||
when(state.materialKey()).thenReturn(key);
|
||||
when(state.isSolid()).thenReturn(solid);
|
||||
when(state.isOccluding()).thenReturn(solid);
|
||||
return state;
|
||||
}
|
||||
|
||||
private static final class RecordingPlacer implements IObjectPlacer {
|
||||
private final List<BlockWrite> writes = new ArrayList<>();
|
||||
private final Map<String, NativeBlockState> world = new HashMap<>();
|
||||
private final List<String> sampledColumns = new ArrayList<>();
|
||||
private final Engine engine;
|
||||
private int failAfterWrites = Integer.MAX_VALUE;
|
||||
private boolean debugSmartBore;
|
||||
private boolean carved;
|
||||
private boolean underwater;
|
||||
private int fluidHeight;
|
||||
|
||||
private RecordingPlacer(Engine engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
private void failAfterWrites(int writes) {
|
||||
failAfterWrites = writes;
|
||||
}
|
||||
|
||||
private void setDebugSmartBore(boolean debugSmartBore) {
|
||||
this.debugSmartBore = debugSmartBore;
|
||||
}
|
||||
|
||||
private List<BlockWrite> writes() {
|
||||
return writes;
|
||||
}
|
||||
|
||||
private List<BlockWrite> writesOf(NativeBlockState state) {
|
||||
return writes.stream().filter(write -> write.state() == state).toList();
|
||||
}
|
||||
|
||||
private List<String> sampledColumns() {
|
||||
return sampledColumns;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHighest(int x, int z, IrisData data) {
|
||||
sampledColumns.add(x + ":" + z);
|
||||
return ANCHOR_Y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHighest(int x, int z, IrisData data, boolean ignoreFluid) {
|
||||
sampledColumns.add(x + ":" + z);
|
||||
return ANCHOR_Y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int x, int y, int z, NativeBlockState state) {
|
||||
if (writes.size() >= failAfterWrites) {
|
||||
throw new IllegalStateException("write failed");
|
||||
}
|
||||
writes.add(new BlockWrite(x, y, z, state));
|
||||
world.put(x + ":" + y + ":" + z, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NativeBlockState get(int x, int y, int z) {
|
||||
return world.get(x + ":" + y + ":" + z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPreventingDecay() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCarved(int x, int y, int z) {
|
||||
return carved;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSolid(int x, int y, int z) {
|
||||
NativeBlockState state = get(x, y, z);
|
||||
return state != null && state.isSolid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnderwater(int x, int z) {
|
||||
return underwater;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFluidHeight() {
|
||||
return fluidHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDebugSmartBore() {
|
||||
return debugSmartBore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTile(int x, int y, int z, TileData tile) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void setData(int x, int y, int z, T data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getData(int x, int y, int z, Class<T> type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Engine getEngine() {
|
||||
return engine;
|
||||
}
|
||||
}
|
||||
|
||||
private record BlockWrite(int x, int y, int z, NativeBlockState state) {
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -8,20 +8,20 @@ import static org.junit.Assert.assertTrue;
|
||||
public class IrisObjectStructurePieceAirTest {
|
||||
@Test
|
||||
public void explicitAirPlacesOnlyForRawStructurePieces() {
|
||||
assertTrue(IrisObjectPlacementRunner.shouldPlaceObjectBlock(true, true, false));
|
||||
assertFalse(IrisObjectPlacementRunner.shouldPlaceObjectBlock(false, true, false));
|
||||
assertTrue(ObjectBlockPass.shouldPlaceObjectBlock(true, true, false));
|
||||
assertFalse(ObjectBlockPass.shouldPlaceObjectBlock(false, true, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ordinaryObjectAirRemainsNondestructive() {
|
||||
assertFalse(IrisObjectPlacementRunner.shouldPlaceObjectBlock(false, true, false));
|
||||
assertTrue(IrisObjectPlacementRunner.shouldPlaceObjectBlock(false, false, false));
|
||||
assertFalse(ObjectBlockPass.shouldPlaceObjectBlock(false, true, false));
|
||||
assertTrue(ObjectBlockPass.shouldPlaceObjectBlock(false, false, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void vineReplacementRemainsRejectedForEveryPlacementMode() {
|
||||
assertFalse(IrisObjectPlacementRunner.shouldPlaceObjectBlock(false, false, true));
|
||||
assertFalse(IrisObjectPlacementRunner.shouldPlaceObjectBlock(true, false, true));
|
||||
assertFalse(IrisObjectPlacementRunner.shouldPlaceObjectBlock(true, true, true));
|
||||
assertFalse(ObjectBlockPass.shouldPlaceObjectBlock(false, false, true));
|
||||
assertFalse(ObjectBlockPass.shouldPlaceObjectBlock(true, false, true));
|
||||
assertFalse(ObjectBlockPass.shouldPlaceObjectBlock(true, true, true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package art.arcane.iris.structure.object;
|
||||
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.generation.geometry.IrisBlockVector;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class PlacementLifecycleTest {
|
||||
private static PlacementLifecycleDependency id(String name) {
|
||||
return new PlacementLifecycleDependency(name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesDependenciesAcrossModulesRegardlessOfInstallOrder() {
|
||||
List<String> calls = new ArrayList<>();
|
||||
PlacementModule consumer = builder -> builder.add(id("consumer"), PlacementStage.HEIGHT,
|
||||
(context, state, ignored) -> calls.add("consumer"), id("provider"));
|
||||
PlacementModule provider = builder -> builder.add(id("provider"), PlacementStage.HEIGHT,
|
||||
(context, state, ignored) -> calls.add("provider"));
|
||||
PlacementLifecycleBuilder builder = new PlacementLifecycleBuilder();
|
||||
consumer.install(builder);
|
||||
provider.install(builder);
|
||||
builder.add(id("independent"), PlacementStage.HEIGHT,
|
||||
(context, state, ignored) -> calls.add("independent"));
|
||||
|
||||
PlacementLifecycle plan = builder.build();
|
||||
assertTrue(calls.isEmpty());
|
||||
plan.run(PlacementStage.HEIGHT, null, new PlacementState(0, null), null);
|
||||
assertEquals(List.of("provider", "consumer", "independent"), calls);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsMissingDuplicateCyclicAndBackwardDependencies() {
|
||||
PlacementLifecycleBuilder missing = new PlacementLifecycleBuilder();
|
||||
missing.add(id("stilt"), PlacementStage.AFTER_BLOCKS, (c, s, v) -> {}, id("bounds"));
|
||||
String message = assertThrows(IllegalArgumentException.class, missing::build).getMessage();
|
||||
assertTrue(message.contains("stilt"));
|
||||
assertTrue(message.contains("bounds"));
|
||||
|
||||
PlacementLifecycleBuilder duplicate = new PlacementLifecycleBuilder();
|
||||
duplicate.add(id("a"), PlacementStage.HEIGHT, (c, s, v) -> {});
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> duplicate.add(id("a"), PlacementStage.HEIGHT, (c, s, v) -> {}));
|
||||
|
||||
PlacementLifecycleBuilder cyclic = new PlacementLifecycleBuilder();
|
||||
cyclic.add(id("a"), PlacementStage.HEIGHT, (c, s, v) -> {}, id("b"));
|
||||
cyclic.add(id("b"), PlacementStage.HEIGHT, (c, s, v) -> {}, id("a"));
|
||||
message = assertThrows(IllegalArgumentException.class, cyclic::build).getMessage();
|
||||
assertTrue(message.contains("cycle"));
|
||||
assertTrue(message.contains("a"));
|
||||
assertTrue(message.contains("b"));
|
||||
|
||||
PlacementLifecycleBuilder backward = new PlacementLifecycleBuilder();
|
||||
backward.add(id("height"), PlacementStage.HEIGHT, (c, s, v) -> {}, id("fill"));
|
||||
backward.add(id("fill"), PlacementStage.AFTER_BLOCKS, (c, s, v) -> {});
|
||||
assertTrue(assertThrows(IllegalArgumentException.class, backward::build)
|
||||
.getMessage().contains("later step"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsSelfDependencyAndAcceptsRepeatedPrerequisite() {
|
||||
PlacementLifecycleBuilder self = new PlacementLifecycleBuilder();
|
||||
self.add(id("self"), PlacementStage.HEIGHT, (c, s, v) -> {}, id("self"));
|
||||
assertThrows(IllegalArgumentException.class, self::build);
|
||||
|
||||
PlacementLifecycleBuilder repeated = new PlacementLifecycleBuilder();
|
||||
repeated.add(id("a"), PlacementStage.HEIGHT, (c, s, v) -> s.y++);
|
||||
repeated.add(id("b"), PlacementStage.HEIGHT, (c, s, v) -> s.y *= 2, id("a"), id("a"));
|
||||
PlacementState state = new PlacementState(2, null);
|
||||
repeated.build().run(PlacementStage.HEIGHT, null, state, null);
|
||||
assertEquals(6, state.y);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void plansAreSnapshotsAndFailuresPropagateWithoutRunningDependents() {
|
||||
List<String> calls = new ArrayList<>();
|
||||
PlacementLifecycleBuilder builder = new PlacementLifecycleBuilder();
|
||||
builder.add(id("first"), PlacementStage.HEIGHT, (c, s, v) -> calls.add("first"));
|
||||
PlacementLifecycle original = builder.build();
|
||||
IllegalStateException failure = new IllegalStateException("failed placement");
|
||||
builder.add(id("failure"), PlacementStage.HEIGHT, (c, s, v) -> { throw failure; }, id("first"));
|
||||
builder.add(id("dependent"), PlacementStage.HEIGHT, (c, s, v) -> calls.add("dependent"), id("failure"));
|
||||
original.run(PlacementStage.HEIGHT, null, new PlacementState(0, null), null);
|
||||
assertEquals(List.of("first"), calls);
|
||||
assertSame(failure, assertThrows(IllegalStateException.class,
|
||||
() -> builder.build().run(PlacementStage.HEIGHT, null, new PlacementState(0, null), null)));
|
||||
assertEquals(List.of("first", "first"), calls);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void everyModeResolvesAndRuntimePlansAreIndependent() {
|
||||
PlacementLifecycles first = PlacementLifecycles.resolve();
|
||||
PlacementLifecycles second = PlacementLifecycles.resolve();
|
||||
for (ObjectPlaceMode mode : ObjectPlaceMode.values()) {
|
||||
assertNotNull(first.get(mode));
|
||||
assertNotSame(first.get(mode), second.get(mode));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesLongDependencyChainsWithoutRecursiveTraversal() {
|
||||
PlacementLifecycleBuilder builder = new PlacementLifecycleBuilder();
|
||||
for (int i = 9999; i >= 0; i--) {
|
||||
builder.add(id("step-" + i), PlacementStage.HEIGHT, (c, s, v) -> s.y++,
|
||||
i == 0 ? new PlacementLifecycleDependency[0]
|
||||
: new PlacementLifecycleDependency[]{id("step-" + (i - 1))});
|
||||
}
|
||||
PlacementState state = new PlacementState(0, null);
|
||||
builder.build().run(PlacementStage.HEIGHT, null, state, null);
|
||||
assertEquals(10000, state.y);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sharedHeightPlanKeepsConcurrentPlacementStateSeparate() throws Exception {
|
||||
PlacementLifecycleBuilder builder = new PlacementLifecycleBuilder();
|
||||
new PlacementModulePreparation().install(builder);
|
||||
new PlacementModuleSlope().install(builder);
|
||||
new PlacementModuleHeight(PlacementModuleHeight.Mode.MAX, false).install(builder);
|
||||
PlacementLifecycle plan = builder.build();
|
||||
CountDownLatch sampled = new CountDownLatch(2);
|
||||
try (var executor = Executors.newFixedThreadPool(2)) {
|
||||
var first = executor.submit(() -> sample(plan, 10, sampled));
|
||||
var second = executor.submit(() -> sample(plan, 100, sampled));
|
||||
assertEquals(11, (int) first.get(10, TimeUnit.SECONDS));
|
||||
assertEquals(101, (int) second.get(10, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
private int sample(PlacementLifecycle plan, int x, CountDownLatch sampled) throws Exception {
|
||||
IObjectPlacer placer = mock(IObjectPlacer.class);
|
||||
when(placer.getHighest(anyInt(), anyInt(), any(), anyBoolean())).thenAnswer(call -> {
|
||||
sampled.countDown();
|
||||
assertTrue(sampled.await(5, TimeUnit.SECONDS));
|
||||
return call.<Integer>getArgument(0);
|
||||
});
|
||||
IrisObjectPlacement config = new IrisObjectPlacement().setForcePlace(true);
|
||||
IrisObject object = mock(IrisObject.class);
|
||||
when(object.getCenter()).thenReturn(new art.arcane.volmlib.util.math.Vector3i(1, 0, 0));
|
||||
when(object.getW()).thenReturn(3);
|
||||
when(object.getH()).thenReturn(1);
|
||||
when(object.getD()).thenReturn(1);
|
||||
PlacementContext context = new PlacementContext(config, mock(IrisData.class), placer, null,
|
||||
new RNG(1), object, x, 0, -1, null);
|
||||
PlacementState state = new PlacementState(context);
|
||||
plan.run(PlacementStage.PREPARE, context, state, null);
|
||||
plan.run(PlacementStage.HEIGHT, context, state, null);
|
||||
return state.y;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void guardOwnsTheForcePlaceExemption() {
|
||||
PlacementLifecycleBuilder builder = new PlacementLifecycleBuilder();
|
||||
builder.add(id("guard"), PlacementStage.HEIGHT, (c, s, v) -> {
|
||||
if (!s.config.isForcePlace()) {
|
||||
s.reject("guard");
|
||||
}
|
||||
});
|
||||
builder.add(id("dependent"), PlacementStage.HEIGHT, (c, s, v) -> s.y = 99, id("guard"));
|
||||
PlacementLifecycle plan = builder.build();
|
||||
IrisObjectPlacement config = new IrisObjectPlacement();
|
||||
PlacementContext context = new PlacementContext(config, mock(IrisData.class), mock(IObjectPlacer.class),
|
||||
null, new RNG(1), mock(IrisObject.class), 0, 0, -1, null);
|
||||
PlacementState rejected = new PlacementState(context);
|
||||
plan.run(PlacementStage.HEIGHT, context, rejected, null);
|
||||
assertEquals(-1, rejected.heightOrRejected());
|
||||
assertEquals("guard", rejected.getRejection());
|
||||
config.setForcePlace(true);
|
||||
PlacementState forced = new PlacementState(context);
|
||||
plan.run(PlacementStage.HEIGHT, context, forced, null);
|
||||
assertEquals(99, forced.y);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectionIsTerminalAcrossPhasesEvenWithForcePlace() {
|
||||
PlacementLifecycleBuilder builder = new PlacementLifecycleBuilder();
|
||||
List<String> calls = new ArrayList<>();
|
||||
builder.add(id("reject"), PlacementStage.VALIDATE, (c, s, v) -> s.reject("protected"));
|
||||
builder.add(id("bore"), PlacementStage.BEFORE_BLOCKS, (c, s, v) -> calls.add("bore"));
|
||||
builder.add(id("blocks"), PlacementStage.BLOCKS, (c, s, v) -> calls.add("blocks"));
|
||||
builder.add(id("finish"), PlacementStage.AFTER_BLOCKS, (c, s, v) -> calls.add("finish"));
|
||||
PlacementContext context = new PlacementContext(new IrisObjectPlacement().setForcePlace(true),
|
||||
mock(IrisData.class), mock(IObjectPlacer.class), null, new RNG(1),
|
||||
mock(IrisObject.class), 0, 0, 80, null);
|
||||
PlacementState result = builder.build().execute(context);
|
||||
assertEquals(-1, result.heightOrRejected());
|
||||
result.reject("later reason");
|
||||
assertEquals("protected", result.getRejection());
|
||||
assertTrue(calls.isEmpty());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user