Production-readiness suite fixes

Surface hydrology policy split, native terrain height cache, watched-edit
hotload cancellation, world-save requestSave with tracked ownership flush,
background-task drain that follows saves admitted mid-drain, peaceful spawn
guard, cached-completion terminal state, IO drains off the owner thread.

Studio pregen at scale: detached historical runtimes keep the configured
noise cache instead of the Studio-wide cache; retired runtimes are no longer
pinned by the volumetric fragment filter's per-thread scratch; pregen mantle
cleanup and native region flushes run on per-run threads instead of the
server thread and the shared Iris IO pool (the flush on the IO pool starved
tectonic plate loads and wedged every Paper worker in Mantle.getChunks);
background-task cancellation claims tasks atomically.
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-09 16:24:05 -04:00
1 parent 4de1aea8b9
commit 62e61970f8
150 files changed
+7892 -1341

No files matched your search

@@ -19,6 +19,7 @@
package art.arcane.iris.modded;
import art.arcane.iris.nativegen.NativeTransitionColumn;
import art.arcane.iris.nativegen.NativeTerrainHeightCache;
import art.arcane.iris.engine.history.TerrainBoundarySignature;
import art.arcane.iris.engine.history.NativeBiomeSpawnSelection;
import java.util.Optional;
@@ -107,6 +108,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CancellationException;
@@ -145,6 +147,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
private final ModdedNativeStructureStage nativeStructures = new ModdedNativeStructureStage(this);
private final ModdedSpawnTableMerger spawnTables = new ModdedSpawnTableMerger(this);
private final ModdedImportedFeatureStage importedFeatures;
private final NativeTerrainHeightCache terrainHeights = new NativeTerrainHeightCache();
private final AtomicBoolean announced = new AtomicBoolean(false);
private final IntConsumer generationRuntimeRetirementListener = this::retireGenerationRuntimeCaches;
private volatile boolean unloading;
@@ -1381,15 +1384,18 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
@Override
public int getBaseHeight(int x, int z, Heightmap.Types type, LevelHeightAccessor heightAccessor, RandomState randomState) {
Engine current = requireDataQueryEngine("base height");
boolean ignoreFluid = !type.isOpaque().test(Blocks.WATER.defaultBlockState());
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope = openHistoryCoordinateScope(
current, x, z, "modded_base_height");
GenerationSessionLease lease = current.acquireGenerationLease("modded_base_height");
IrisContext.Scope ignored = IrisContext.open(current, lease.sessionId(), null)) {
Optional<TerrainBoundarySignature> resolved = current.getComplex().resolvedTerrainColumn(x, z);
NativeTerrainHeightCache.Query query = new NativeTerrainHeightCache.Query(
current.getCacheID(), x, z, type, heightAccessor.getMinY(), heightAccessor.getHeight());
OptionalInt resolved = terrainHeights.resolvedHeight(query,
() -> resolvedBaseHeight(current, x, z, type, heightAccessor));
if (resolved.isPresent()) {
return NativeTransitionColumn.height(resolved.get(), type, heightAccessor);
return resolved.getAsInt();
}
boolean ignoreFluid = !type.isOpaque().test(Blocks.WATER.defaultBlockState());
int height = current.getDimensionStackContext() == null
? current.getHeight(x, z, ignoreFluid)
: Engine.hostHeight(current, x, z, ignoreFluid);
@@ -1399,6 +1405,13 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
}
}
private OptionalInt resolvedBaseHeight(Engine current, int x, int z, Heightmap.Types type, LevelHeightAccessor heightAccessor) {
Optional<TerrainBoundarySignature> resolved = current.getComplex().resolvedTerrainColumn(x, z);
return resolved.isPresent()
? OptionalInt.of(NativeTransitionColumn.height(resolved.get(), type, heightAccessor))
: OptionalInt.empty();
}
@Override
public NoiseColumn getBaseColumn(int x, int z, LevelHeightAccessor heightAccessor, RandomState randomState) {
int minY = heightAccessor.getMinY();
@@ -1482,6 +1495,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
}
private void retireGenerationRuntimeCaches(int runtimeIdentity) {
terrainHeights.evictRuntime(runtimeIdentity);
structureBiomeSource.evictRuntime(runtimeIdentity);
importedFeatures.evictRuntime(runtimeIdentity);
spawnTables.evictRuntime(runtimeIdentity);
@@ -46,6 +46,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.Difficulty;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.EntityType;
@@ -120,13 +121,20 @@ public final class ModdedEntitySpawner {
return null;
}
Entity entity = type.spawn(level, BlockPos.containing(x, y, z), reasonFor(irisEntity.getReason()));
Entity entity = spawnNative(type, level, BlockPos.containing(x, y, z), reasonFor(irisEntity.getReason()));
if (entity != null) {
entity.snapTo(x, y, z, 0F, 0F);
}
return entity;
}
static Entity spawnNative(EntityType<?> type, ServerLevel level, BlockPos position, EntitySpawnReason reason) {
if (level.getDifficulty() == Difficulty.PEACEFUL && !type.isAllowedInPeaceful()) {
return null;
}
return type.spawn(level, position, reason);
}
private static void applyConfig(Engine engine, IrisEntity irisEntity, Entity entity, ServerLevel level, int blockX, int blockY, int blockZ, RNG rng, boolean riseEffectActive) {
String customName = irisEntity.getCustomName();
if (customName != null) {
@@ -398,10 +398,7 @@ final class ModdedNativeStructureStage {
}
IrisStaticObjectLayer staticObjects = current.getDimension().getStaticObjectLayer(current.getData());
Predicate<BlockPos> protectedPosition = nativeStructureProtection(current, staticObjects);
WorldGenLevel boundedWorld = staticObjects.isEmpty()
&& current.getDimensionStackContext() == null
? world
: ModdedNativeStructureWorldgenAccess.create(
WorldGenLevel boundedWorld = ModdedNativeStructureWorldgenAccess.create(
world, chunkPos, worldgenSurfaceHeight(current, world.getMinY()), worldgenFloorHeight(current, world.getMinY()),
current.getDimensionStackContext() != null,
protectedPosition);
@@ -413,11 +410,12 @@ final class ModdedNativeStructureStage {
"vegetation cleanup", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
NativeStructureSurfaceFitter.VacuumFoundationPlan vacuumFoundationPlan;
NativeStructureSurfaceFitter.SurfaceTerrainPlan surfaceTerrainPlan;
try {
vacuumFoundationPlan = NativeStructureSurfaceFitter.prepareSurfaceStructures(
surfaceTerrainPlan = NativeStructureSurfaceFitter.prepareSurfaceStructures(
boundedWorld, area, terrainTargets,
(x, z) -> Engine.hostHeight(current, x, z, true) + current.getMinHeight());
surfaceTerrainPlan.primeHeightmaps(chunk);
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"terrain integration", nativeStructureBatchContext(placementGroups),
@@ -445,7 +443,7 @@ final class ModdedNativeStructureStage {
}
try {
NativeStructureSurfaceFitter.repairVacuumFoundations(
boundedWorld, area, vacuumFoundationPlan);
boundedWorld, area, surfaceTerrainPlan);
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"foundation repair", nativeStructureBatchContext(placementGroups),
@@ -210,7 +210,7 @@ public final class ModdedPlatform implements IrisPlatform {
}
EntityType<?> type = (EntityType<?>) resolved.nativeHandle();
BlockPos pos = BlockPos.containing(x, y, z);
Entity entity = type.spawn(level, pos, EntitySpawnReason.COMMAND);
Entity entity = ModdedEntitySpawner.spawnNative(type, level, pos, EntitySpawnReason.COMMAND);
return entity != null;
}
@@ -0,0 +1,132 @@
package art.arcane.iris.modded;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisEntity;
import art.arcane.volmlib.util.math.RNG;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.Identifier;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.Difficulty;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.chunk.LevelChunk;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class ModdedEntitySpawnDifficultyTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void sharedPlatformSpawnReturnsFalseForPeacefulHostiles() {
ServerLevel level = mock(ServerLevel.class);
when(level.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
ModdedPlatform platform = new ModdedPlatform(mock(ModdedLoader.class));
assertFalse(platform.spawnEntity(new ModdedPlatformWorld(level), "minecraft:zombie", 0.5, 80, 0.5));
verify(level, never()).addFreshEntity(any(Entity.class));
verify(level, never()).enabledFeatures();
}
@Test
public void ambientSpawnReturnsNullForPeacefulHostilesBeforeApplyingConfiguration() {
ServerLevel level = mock(ServerLevel.class);
ServerChunkCache chunks = mock(ServerChunkCache.class);
when(level.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
when(level.getChunkSource()).thenReturn(chunks);
when(chunks.getChunkNow(anyInt(), anyInt())).thenReturn(mock(LevelChunk.class));
IrisEntity configured = new IrisEntity().setType("zombie").setSpawnEffectRiseOutOfGround(false);
assertNull(ModdedEntitySpawner.spawn(mock(Engine.class), configured, level, 0, 80, 0, new RNG(1L)));
verify(level, never()).addFreshEntity(any(Entity.class));
verify(level, never()).enabledFeatures();
}
@Test
public void peacefulRejectsEveryNativeForbiddenTypeBeforeCreation() {
ServerLevel level = mock(ServerLevel.class);
when(level.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
int rejected = 0;
for (EntityType<?> registered : BuiltInRegistries.ENTITY_TYPE) {
if (registered.isAllowedInPeaceful()) {
continue;
}
EntityType<?> type = spy(registered);
assertNull(ModdedEntitySpawner.spawnNative(type, level, BlockPos.ZERO, EntitySpawnReason.NATURAL));
verify(type, never()).spawn(level, BlockPos.ZERO, EntitySpawnReason.NATURAL);
rejected++;
}
assertTrue(rejected > 20);
}
@Test
public void peacefulPreservesPassiveAndNativeMonsterCategoryExceptions() {
ServerLevel level = mock(ServerLevel.class);
when(level.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
Entity created = mock(Entity.class);
for (String key : List.of("cow", "villager", "wolf", "piglin", "shulker")) {
EntityType<?> type = spy(BuiltInRegistries.ENTITY_TYPE.getValue(Identifier.parse("minecraft:" + key)));
doReturn(created).when(type).spawn(level, BlockPos.ZERO, EntitySpawnReason.NATURAL);
assertSame(key, created, ModdedEntitySpawner.spawnNative(type, level, BlockPos.ZERO, EntitySpawnReason.NATURAL));
verify(type).spawn(level, BlockPos.ZERO, EntitySpawnReason.NATURAL);
}
}
@Test
public void nonPeacefulDifficultiesPreserveHostileSpawns() {
ServerLevel level = mock(ServerLevel.class);
Entity created = mock(Entity.class);
for (Difficulty difficulty : List.of(Difficulty.EASY, Difficulty.NORMAL, Difficulty.HARD)) {
EntityType<?> type = spy(BuiltInRegistries.ENTITY_TYPE.getValue(Identifier.parse("minecraft:zombie")));
when(level.getDifficulty()).thenReturn(difficulty);
doReturn(created).when(type).spawn(level, BlockPos.ZERO, EntitySpawnReason.NATURAL);
assertSame(created, ModdedEntitySpawner.spawnNative(type, level, BlockPos.ZERO, EntitySpawnReason.NATURAL));
verify(type).spawn(level, BlockPos.ZERO, EntitySpawnReason.NATURAL);
}
}
@Test
public void unrelatedNativeSpawnFailuresStillPropagate() {
ServerLevel level = mock(ServerLevel.class);
when(level.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
EntityType<?> type = spy(BuiltInRegistries.ENTITY_TYPE.getValue(Identifier.parse("minecraft:cow")));
IllegalStateException failure = new IllegalStateException("Spawn rejected by platform");
doThrow(failure).when(type).spawn(level, BlockPos.ZERO, EntitySpawnReason.NATURAL);
assertSame(failure, assertThrows(IllegalStateException.class,
() -> ModdedEntitySpawner.spawnNative(type, level, BlockPos.ZERO, EntitySpawnReason.NATURAL)));
}
}
@@ -1,9 +1,14 @@
package art.arcane.iris.modded;
import art.arcane.iris.engine.object.IrisStructureTerrain;
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
import art.arcane.iris.nativegen.NativeStructurePostProcessor;
import art.arcane.iris.nativegen.NativeStructureTerrainIntegrator;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ChunkPos;
@@ -12,10 +17,25 @@ import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import net.minecraft.world.level.levelgen.structure.templatesystem.GravityProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
@@ -30,11 +50,13 @@ import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
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.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class ModdedNativeStructureWorldgenAccessTest {
@@ -49,6 +71,69 @@ public class ModdedNativeStructureWorldgenAccessTest {
Bootstrap.bootStrap();
}
@Test
public void ordinaryWorldFlattenPreparationHandlesReferencedVillagePiecesBeyondEightChunks() {
int farX = (GENERATION_CENTER_X - 9) << 4;
int farZ = (GENERATION_CENTER_Z + 4) << 4;
BoundingBox bounds = new BoundingBox(farX, 64, farZ, farX + 4, 90, farZ + 4);
PoolElementStructurePiece piece = new PoolElementStructurePiece(
null, StructurePoolElement.single("minecraft:empty").apply(StructureTemplatePool.Projection.RIGID),
new BlockPos(farX, 64, farZ), 1, Rotation.NONE, bounds, LiquidSettings.APPLY_WATERLOGGING);
Structure structure = new DesertPyramidStructure(new Structure.StructureSettings(
HolderSet.empty(), Map.of(), GenerationStep.Decoration.SURFACE_STRUCTURES, TerrainAdjustment.BEARD_BOX));
StructureStart start = new StructureStart(structure, generationCenter(), 0, new PiecesContainer(List.of(piece)));
List<NativeStructureTerrainIntegrator.TerrainTarget> targets = List.of(
new NativeStructureTerrainIntegrator.TerrainTarget("minecraft:village_plains", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.FLATTEN)));
BoundingBox area = new BoundingBox(generationCenter().getMinBlockX(), -64, generationCenter().getMinBlockZ(),
generationCenter().getMaxBlockX(), 319, generationCenter().getMaxBlockZ());
RecordingDelegate unbounded = new RecordingDelegate();
unbounded.rejectDistantTerrainReads = true;
assertThrows(IllegalStateException.class, () -> NativeStructurePostProcessor.prepareTerrain(
unbounded.world(), area, targets, (palette, rng, x, y, z) -> Blocks.STONE.defaultBlockState()));
RecordingDelegate recording = new RecordingDelegate();
recording.rejectDistantTerrainReads = true;
ModdedNativeStructureWorldgenAccess access = access(recording);
NativeStructurePostProcessor.prepareTerrain(
access, area, targets, (palette, rng, x, y, z) -> Blocks.STONE.defaultBlockState());
assertEquals(0, recording.heightReads);
assertEquals(0, recording.chunkReads);
assertEquals(0, recording.terrainReads);
assertEquals(0, recording.mutations);
assertEquals(91, access.getHeight(Heightmap.Types.WORLD_SURFACE_WG,
generationCenter().getMinBlockX(), generationCenter().getMinBlockZ()));
assertEquals(1, recording.heightReads);
}
@Test
public void projectedTemplatePrefiltersUseRemoteTerrainAndRetainLocalHeightmaps() {
RecordingDelegate recording = new RecordingDelegate();
recording.rejectDistantTerrainReads = true;
ModdedNativeStructureWorldgenAccess access = access(recording);
GravityProcessor gravity = new GravityProcessor(Heightmap.Types.WORLD_SURFACE_WG, -1);
BlockPos remote = new BlockPos((GENERATION_CENTER_X - 9) << 4, 64, (GENERATION_CENTER_Z + 4) << 4);
StructureTemplate.StructureBlockInfo raw = new StructureTemplate.StructureBlockInfo(
new BlockPos(0, 4, 0), Blocks.DIRT_PATH.defaultBlockState(), null);
StructurePlaceSettings settings = new StructurePlaceSettings().addProcessor(gravity);
StructureTemplate.StructureBlockInfo projected = StructureTemplate.processBlockInfos(
access, remote, BlockPos.ZERO, settings, List.of(raw)).getFirst();
assertEquals(remote.atY(SURFACE_FIRST_FREE_Y + 3), projected.pos());
assertEquals(0, recording.heightReads);
BlockPos local = generationCenter().getMiddleBlockPosition(64);
StructureTemplate.StructureBlockInfo localBlock = new StructureTemplate.StructureBlockInfo(
BlockPos.ZERO, Blocks.DIRT_PATH.defaultBlockState(), null);
StructureTemplate.StructureBlockInfo localProjection = StructureTemplate.processBlockInfos(
access, local, BlockPos.ZERO, settings, List.of(localBlock)).getFirst();
assertEquals(local.atY(90), localProjection.pos());
assertEquals(1, recording.heightReads);
assertEquals(0, recording.chunkReads);
}
@Test
public void distanceTwoTerrainReadsNeverReachTheDelegate() {
RecordingDelegate recording = new RecordingDelegate();
@@ -253,6 +338,7 @@ public class ModdedNativeStructureWorldgenAccessTest {
private int statusChunkReads;
private int mutations;
private int events;
private boolean rejectDistantTerrainReads;
private RecordingDelegate() {
this.biome = Holder.direct((Biome) null);
@@ -293,6 +379,11 @@ public class ModdedNativeStructureWorldgenAccessTest {
return 384;
}
if (name.equals("getHeight")) {
if (rejectDistantTerrainReads
&& (Math.abs(((int) arguments[1] >> 4) - GENERATION_CENTER_X) > 1
|| Math.abs(((int) arguments[2] >> 4) - GENERATION_CENTER_Z) > 1)) {
throw new IllegalStateException("Requested chunk unavailable during world generation");
}
heightReads++;
return 91;
}