Preserved terrain across updates

- Saved chunks retain their natural geometry and generation ownership
- Future chunks blend against frozen terrain boundaries
- Build revisions capture generation source and dependencies automatically
- Native terrain queries use contained transition geometry and final height caches
- Historical searches retain recorded pack identities and placement facts
- World creation and Studio updates preserve native lifecycle ordering
- Hydrology containment and scoped content workers retain generation context
- Pregeneration validates saved completion before trusting cached progress
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-05 11:57:01 -04:00
1 parent 20ad49b9d2
commit b1e1fbc15e
214 files changed
+11985 -4082

No files matched your search

@@ -18,6 +18,10 @@
package art.arcane.iris.modded;
import art.arcane.iris.nativegen.NativeTransitionColumn;
import art.arcane.iris.engine.history.TerrainBoundarySignature;
import java.util.Optional;
import art.arcane.iris.nativegen.NativeGenerationWriteGuard;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.pack.PackValidationRegistry;
import art.arcane.iris.engine.IrisEngine;
@@ -1019,14 +1023,19 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
generationEngine.generate(pos.getMinBlockX(), pos.getMinBlockZ(), blocks, biomes, false);
writeBlocks(chunk, blocks, dimMinY, height);
if (route != null) {
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
route.claimGeneratedSemantics((x, y, z) -> {
BlockState state = chunk.getBlockState(position.set(x, dimMinY + y, z));
return state.isAir() || state.liquid();
});
}
ModdedNativeTerrainReceipts.persist(chunk, route);
writeTerrainHeightmaps(chunk, generationEngine, pos, height);
Heightmap.primeHeightmaps(chunk, EnumSet.of(
Heightmap.Types.MOTION_BLOCKING,
Heightmap.Types.MOTION_BLOCKING_NO_LEAVES));
ModdedWorldManager.enqueueGenerated(generationEngine, pos.x(), pos.z());
if (route != null) {
route.claimGeneratedSemantics();
}
return chunk;
} catch (GenerationSessionException e) {
if (generationEngine.isClosing() || e.isExpectedTeardown()) {
@@ -1066,6 +1075,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
completion.completeExceptionally(completionFailure);
}
});
route.detachThread();
return completion;
}
@@ -1205,6 +1215,10 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope runtimeScope = openHistoryRuntimeScope(route);
GenerationSessionLease lease = requireGenerationLease(current, "modded_biome_decoration");
IrisContext.Scope ignored = IrisContext.open(current, lease.sessionId(), null)) {
if (chunk.getPersistedStatus().isOrAfter(ChunkStatus.FEATURES)) {
return;
}
nativeStructures.placeVanillaStructures(level, chunk, structureManager);
if (!allowsRoutedDiscreteGeneration(
current,
route,
@@ -1214,8 +1228,10 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
)) {
return;
}
if (!NativeGenerationWriteGuard.allowsDecoration(current, level, chunkPos)) {
return;
}
importedFeatures.prepare(current);
nativeStructures.placeVanillaStructures(level, chunk, structureManager);
// Vanilla's placed-feature pass, on THIS thread and never on ModdedGenPool: the FEATURES chunk
// step writes into the eight neighbouring chunks and is not parallel-safe. Inert unless the
// dimension set importedFeatures.enabled.
@@ -1257,6 +1273,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
));
nativeStructures.adjustGeneratedStructures(
registryAccess, chunk, previousStarts, configuredStarts, current, templateManager);
ModdedNativeTerrainReceipts.persistStructureActivation(chunk, route);
}
}
@@ -1355,6 +1372,10 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
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);
if (resolved.isPresent()) {
return NativeTransitionColumn.height(resolved.get(), type, heightAccessor);
}
int height = current.getDimensionStackContext() == null
? current.getHeight(x, z, ignoreFluid)
: Engine.hostHeight(current, x, z, ignoreFluid);
@@ -1367,12 +1388,16 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
@Override
public NoiseColumn getBaseColumn(int x, int z, LevelHeightAccessor heightAccessor, RandomState randomState) {
int minY = heightAccessor.getMinY();
BlockState[] states = new BlockState[heightAccessor.getHeight()];
Engine current = requireDataQueryEngine("base column");
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope = openHistoryCoordinateScope(
current, x, z, "modded_base_column");
GenerationSessionLease lease = current.acquireGenerationLease("modded_base_column");
IrisContext.Scope ignored = IrisContext.open(current, lease.sessionId(), null)) {
Optional<TerrainBoundarySignature> resolved = current.getComplex().resolvedTerrainColumn(x, z);
if (resolved.isPresent()) {
return NativeTransitionColumn.column(resolved.get(), heightAccessor);
}
BlockState[] states = new BlockState[heightAccessor.getHeight()];
BlockState airState = Blocks.AIR.defaultBlockState();
boolean dimensionStack = current.getDimensionStackContext() != null;
int surface = dimensionStack
@@ -1394,6 +1419,12 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
}
}
public boolean allowsNativeChunkWrite(int chunkX, int chunkZ) {
Engine current = engine;
return current == null || !current.isClosing() && !current.isClosed()
&& current.getComplex().allowsMantleChunkWrite(chunkX, chunkZ);
}
private boolean allowsRoutedDiscreteGeneration(
Engine current,
GenerationHistoryRuntimeRouter.RuntimeRoute route,
@@ -1407,7 +1438,8 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
if (route == null) {
return allowsGenerationHistoryBypass(current);
}
return current.getComplex().allowsNewGenerationChunk(chunkPos.x(), chunkPos.z());
return NativeGenerationWriteGuard.allowsPendingStage(current, chunk, stage)
|| current.getComplex().allowsNewGenerationChunk(chunkPos.x(), chunkPos.z());
}
private void bindGenerationRuntimeRetirementListener(Engine current) {
@@ -362,13 +362,15 @@ public final class ModdedForcedDatapack {
KList<String> presetIds = new KList<>();
Path worldRoot = worldRootOrNull();
Set<PackSelection> legacySelections = unadoptedSelections(worldRoot);
for (File pack : compilationPackRoots(worldRoot)) {
List<File> packRoots = compilationPackRoots(worldRoot);
for (File pack : packRoots) {
PackStageContext context = packStageContext(pack.toPath(), legacySelections);
if (stagePack(context, fixer, stagingDirectory, seenBiomes, presetIds)) {
packCount++;
}
}
IrisDatapackCompiler.installRetainedRegistrySources(packRoots, List.of(stagingDirectory.toFile()), fixer);
writePackMeta(stagingDirectory);
// Forge only: FML has no block-drops event Iris can use, so drops are routed through a global loot
// modifier. NeoForge does not need one - IrisNeoForgeBootstrap listens to BlockDropsEvent and both
@@ -996,7 +998,7 @@ public final class ModdedForcedDatapack {
return currentSource;
}
PhysicalResourceKey key = new PhysicalResourceKey(registryKey, resourceKey);
return GenerationRegistryContractFactory.requireGeneratedSource(retainedContract, key, fixer);
return GenerationRegistryContractFactory.renderGeneratedSource(retainedContract, key, fixer);
}
}
@@ -18,6 +18,10 @@
package art.arcane.iris.modded;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import art.arcane.iris.nativegen.NativeGenerationWriteGuard;
import art.arcane.iris.engine.DimensionStackContext;
import art.arcane.iris.engine.DimensionStackLayout;
import art.arcane.iris.engine.framework.Engine;
@@ -341,7 +345,8 @@ final class ModdedNativeStructureStage {
List<NativePlacement> resolvedPlacements = new ArrayList<>(starts.size());
for (StructureStart start : starts) {
BoundingBox footprint = start.getBoundingBox();
if (!current.getComplex().allowsNewGenerationFootprint(
if (!isHistoricalStructureStart(current, world, start)
&& !current.getComplex().allowsNewGenerationFootprint(
footprint.minX(),
footprint.minZ(),
footprint.maxX(),
@@ -574,6 +579,16 @@ final class ModdedNativeStructureStage {
return blockState;
}
private boolean isHistoricalStructureStart(Engine current, WorldGenLevel world, StructureStart start) {
ChunkPos origin = start.getChunkPos();
if (!current.getComplex().allowsMantleChunkWrite(origin.x(), origin.z())) {
return true;
}
ChunkAccess source = world.getChunk(origin.x(), origin.z(), ChunkStatus.EMPTY, false);
return source != null && NativeGenerationWriteGuard.isHistoricalStructure(
current, ModdedNativeTerrainReceipts.structureActivation(source));
}
private BoundingBox writableArea(ChunkAccess chunk) {
ChunkPos chunkPos = chunk.getPos();
int minX = chunkPos.getMinBlockX();
@@ -0,0 +1,53 @@
package art.arcane.iris.modded;
import art.arcane.iris.engine.history.GenerationHistoryRuntimeRouter;
import art.arcane.iris.engine.history.NativeTerrainReceipt;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ImposterProtoChunk;
import java.io.IOException;
public final class ModdedNativeTerrainReceipts {
private ModdedNativeTerrainReceipts() {
}
public static void persist(ChunkAccess chunk, GenerationHistoryRuntimeRouter.RuntimeRoute route) throws IOException {
if (route == null || route.naturalTerrain().isEmpty()) {
return;
}
set(chunk, NativeTerrainReceipt.encode(route.naturalTerrain().orElseThrow(),
route.activation().activationId(), route.epoch().epochId()));
chunk.markUnsaved();
}
public static long structureActivation(ChunkAccess chunk) {
return holder(chunk).iris$getStructureActivation();
}
public static void setStructureActivation(ChunkAccess chunk, long activation) {
holder(chunk).iris$setStructureActivation(activation);
}
public static void persistStructureActivation(ChunkAccess chunk, GenerationHistoryRuntimeRouter.RuntimeRoute route) {
if (route != null) {
setStructureActivation(chunk, route.activation().activationId());
chunk.markUnsaved();
}
}
public static byte[] get(ChunkAccess chunk) {
return holder(chunk).iris$getNaturalTerrain();
}
public static void set(ChunkAccess chunk, byte[] receipt) {
holder(chunk).iris$setNaturalTerrain(receipt);
}
private static NativeTerrainReceiptHolder holder(ChunkAccess chunk) {
ChunkAccess target = chunk instanceof ImposterProtoChunk imposter ? imposter.getWrapped() : chunk;
if (!(target instanceof NativeTerrainReceiptHolder holder)) {
throw new IllegalStateException("Native terrain receipt storage is unavailable for " + chunk.getPos());
}
return holder;
}
}
@@ -0,0 +1,142 @@
package art.arcane.iris.modded;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.history.SavedTerrainChunk;
import art.arcane.iris.modded.mixin.ChunkMapTerrainReceiptAccess;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
public final class ModdedSavedTerrainCapture {
private ModdedSavedTerrainCapture() {
}
public static CompletableFuture<SavedTerrainChunk> capture(Engine engine, int chunkX, int chunkZ) {
ServerLevel level = requireLevel(engine);
CompletableFuture<CompoundTag> snapshot = new CompletableFuture<>();
runOwned(level, () -> {
try {
ChunkMap map = level.getChunkSource().chunkMap;
ChunkHolder holder = map.getUpdatingChunkIfPresent(ChunkPos.pack(chunkX, chunkZ));
ChunkAccess chunk = holder == null ? null : holder.getLatestChunk();
if (chunk == null) {
map.read(new ChunkPos(chunkX, chunkZ)).whenComplete((data, failure) -> {
if (failure != null) {
snapshot.completeExceptionally(failure);
} else {
snapshot.complete(data.orElse(null));
}
});
return;
}
String status = BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getPersistedStatus()).toString();
if (!SavedTerrainChunk.hasTerrain(status)) {
throw new IOException("Native chunk " + chunkX + "," + chunkZ + " has no terrain at " + status);
}
CompoundTag data = SerializableChunkData.copyOf(level, chunk).write();
if (chunk.isUnsaved()) {
((ChunkMapTerrainReceiptAccess) map).iris$saveTerrainCheckpoint(chunk);
}
snapshot.complete(data);
} catch (Throwable failure) {
snapshot.completeExceptionally(failure);
}
});
return snapshot.thenApplyAsync(data -> {
try {
if (data == null) {
throw new IOException("Saved native chunk is unavailable: " + chunkX + "," + chunkZ);
}
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (DataOutputStream output = new DataOutputStream(bytes)) {
NbtIo.write(data, output);
}
return SavedTerrainChunk.readNbt(bytes.toByteArray(), chunkX, chunkZ, engine.getMinHeight(), engine.getHeight());
} catch (IOException failure) {
throw new CompletionException(failure);
}
});
}
public static CompletableFuture<Void> flush(Engine engine) {
ServerLevel level = requireLevel(engine);
CompletableFuture<Void> complete = new CompletableFuture<>();
runOwned(level, () -> {
try {
ChunkMap map = level.getChunkSource().chunkMap;
List<Checkpoint> checkpoints = new ArrayList<>();
for (ChunkHolder holder : ((ChunkMapTerrainReceiptAccess) map).iris$updatingChunks().values()) {
ChunkAccess chunk = holder.getLatestChunk();
if (chunk == null) {
continue;
}
String status = BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getPersistedStatus()).toString();
if (!SavedTerrainChunk.hasTerrain(status)
&& ModdedNativeTerrainReceipts.structureActivation(chunk) == 0) {
continue;
}
checkpoints.add(new Checkpoint(chunk.getPos().x(), chunk.getPos().z(), status,
ModdedNativeTerrainReceipts.get(chunk), ModdedNativeTerrainReceipts.structureActivation(chunk)));
if (chunk.isUnsaved()) {
((ChunkMapTerrainReceiptAccess) map).iris$saveTerrainCheckpoint(chunk);
}
}
map.synchronize(true).thenRunAsync(() -> verifyCheckpoints(engine, checkpoints))
.whenComplete((ignored, failure) -> {
if (failure != null) {
complete.completeExceptionally(failure);
} else {
complete.complete(null);
}
});
} catch (Throwable failure) {
complete.completeExceptionally(failure);
}
});
return complete;
}
private static void verifyCheckpoints(Engine engine, List<Checkpoint> checkpoints) {
try {
for (Checkpoint checkpoint : checkpoints) {
SavedTerrainChunk.verifyCheckpoint(engine.getWorld().worldFolder().toPath(),
checkpoint.chunkX(), checkpoint.chunkZ(), checkpoint.status(), checkpoint.receipt(),
checkpoint.structureActivation());
}
} catch (IOException failure) {
throw new CompletionException(failure);
}
}
private record Checkpoint(int chunkX, int chunkZ, String status, byte[] receipt, long structureActivation) {
}
private static ServerLevel requireLevel(Engine engine) {
if (!(engine.getWorld().platformWorld().nativeHandle() instanceof ServerLevel level)) {
throw new IllegalStateException("The level is unavailable for terrain capture.");
}
return level;
}
private static void runOwned(ServerLevel level, Runnable action) {
if (level.getServer().isSameThread()) {
action.run();
} else {
level.getServer().execute(action);
}
}
}
@@ -191,6 +191,8 @@ public final class ModdedWorldEngines {
ModdedGenerationHistoryStorage.ActivePack opened
) throws IOException {
GenerationHistory history = opened.history();
history.prepareCurrentGenerator(IrisSettings.get().getGenerator().getGenerationTransitionWidthBlocks());
opened = ModdedGenerationHistoryStorage.resolveActive(history);
long openedActivationId = history.activeActivation().activationId();
IrisEngine candidate = buildEngine(level, seed, opened);
boolean ready = false;
@@ -0,0 +1,11 @@
package art.arcane.iris.modded;
public interface NativeTerrainReceiptHolder {
long iris$getStructureActivation();
void iris$setStructureActivation(long activation);
byte[] iris$getNaturalTerrain();
void iris$setNaturalTerrain(byte[] receipt);
}
@@ -25,6 +25,7 @@ import art.arcane.iris.core.pregenerator.PregenPerformanceProfile;
import art.arcane.iris.core.pregenerator.PregenTask;
import art.arcane.iris.core.pregenerator.PregeneratorMethod;
import art.arcane.iris.core.pregenerator.cache.PregenCache;
import art.arcane.iris.core.pregenerator.cache.PregenSavedChunkStatus;
import art.arcane.iris.core.pregenerator.methods.CachedPregenMethod;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.volmlib.util.format.Form;
@@ -64,7 +65,9 @@ public final class ModdedPregenJob {
ModdedPregenMethod moddedMethod = new ModdedPregenMethod(level, engine, sync);
PregeneratorMethod method = moddedMethod;
if (cached) {
method = new CachedPregenMethod(method, PregenCache.create(cacheDirectory(level)).sync(), task);
method = new CachedPregenMethod(new CachedPregenMethod.Configuration(method,
PregenCache.create(cacheDirectory(level)).sync(), task,
PregenSavedChunkStatus.fromWorld(engine.getWorld().worldFolder().toPath())));
}
ActivePregen active = new ActivePregen(engine, moddedMethod);
ACTIVE.set(active);
@@ -0,0 +1,18 @@
package art.arcane.iris.modded.mixin;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.world.level.chunk.ChunkAccess;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(ChunkMap.class)
public interface ChunkMapTerrainReceiptAccess {
@Invoker("save")
boolean iris$saveTerrainCheckpoint(ChunkAccess chunk);
@Accessor("updatingChunkMap")
Long2ObjectLinkedOpenHashMap<ChunkHolder> iris$updatingChunks();
}
@@ -0,0 +1,37 @@
package art.arcane.iris.modded.mixin;
import art.arcane.iris.modded.NativeTerrainReceiptHolder;
import net.minecraft.world.level.chunk.ChunkAccess;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(ChunkAccess.class)
public abstract class ChunkTerrainReceiptMixin implements NativeTerrainReceiptHolder {
@Unique
private byte[] iris$naturalTerrain;
@Unique
private long iris$structureActivation;
@Override
public long iris$getStructureActivation() {
return iris$structureActivation;
}
@Override
public void iris$setStructureActivation(long activation) {
if (activation < 0) {
throw new IllegalArgumentException("Native structure activation cannot be negative");
}
iris$structureActivation = activation;
}
@Override
public byte[] iris$getNaturalTerrain() {
return iris$naturalTerrain == null ? null : iris$naturalTerrain.clone();
}
@Override
public void iris$setNaturalTerrain(byte[] receipt) {
iris$naturalTerrain = receipt == null ? null : receipt.clone();
}
}
@@ -0,0 +1,21 @@
package art.arcane.iris.modded.mixin;
import art.arcane.iris.modded.ModdedNativeTerrainReceipts;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LevelChunk.class)
public abstract class LevelChunkTerrainReceiptMixin {
@Inject(method = "<init>(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/chunk/ProtoChunk;Lnet/minecraft/world/level/chunk/LevelChunk$PostLoadProcessor;)V", at = @At("RETURN"))
private void iris$transferTerrain(ServerLevel level, ProtoChunk source, LevelChunk.PostLoadProcessor processor,
CallbackInfo callback) {
ModdedNativeTerrainReceipts.setStructureActivation((LevelChunk) (Object) this,
ModdedNativeTerrainReceipts.structureActivation(source));
ModdedNativeTerrainReceipts.set((LevelChunk) (Object) this, ModdedNativeTerrainReceipts.get(source));
}
}
@@ -0,0 +1,89 @@
package art.arcane.iris.modded.mixin;
import art.arcane.iris.engine.history.NativeTerrainReceipt;
import art.arcane.iris.modded.ModdedNativeTerrainReceipts;
import art.arcane.iris.modded.NativeTerrainReceiptHolder;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.PalettedContainerFactory;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(SerializableChunkData.class)
public abstract class SerializableTerrainReceiptMixin implements NativeTerrainReceiptHolder {
@Unique
private byte[] iris$naturalTerrain;
@Unique
private long iris$structureActivation;
@Override
public long iris$getStructureActivation() {
return iris$structureActivation;
}
@Override
public void iris$setStructureActivation(long activation) {
if (activation < 0) {
throw new IllegalArgumentException("Native structure activation cannot be negative");
}
iris$structureActivation = activation;
}
@Override
public byte[] iris$getNaturalTerrain() {
return iris$naturalTerrain == null ? null : iris$naturalTerrain.clone();
}
@Override
public void iris$setNaturalTerrain(byte[] receipt) {
iris$naturalTerrain = receipt == null ? null : receipt.clone();
}
@Inject(method = "parse", at = @At("RETURN"))
private static void iris$parseTerrain(LevelHeightAccessor height, PalettedContainerFactory containers,
CompoundTag tag, CallbackInfoReturnable<SerializableChunkData> callback) {
SerializableChunkData parsed = callback.getReturnValue();
if (parsed != null) {
((NativeTerrainReceiptHolder) (Object) parsed).iris$setStructureActivation(
tag.getLongOr(NativeTerrainReceipt.STRUCTURE_ACTIVATION_KEY, 0));
((NativeTerrainReceiptHolder) (Object) parsed).iris$setNaturalTerrain(
tag.getByteArray(NativeTerrainReceipt.NBT_KEY).orElse(null));
}
}
@Inject(method = "copyOf", at = @At("RETURN"))
private static void iris$copyTerrain(ServerLevel level, ChunkAccess chunk,
CallbackInfoReturnable<SerializableChunkData> callback) {
((NativeTerrainReceiptHolder) (Object) callback.getReturnValue()).iris$setStructureActivation(
ModdedNativeTerrainReceipts.structureActivation(chunk));
((NativeTerrainReceiptHolder) (Object) callback.getReturnValue()).iris$setNaturalTerrain(
ModdedNativeTerrainReceipts.get(chunk));
}
@Inject(method = "read", at = @At("RETURN"))
private void iris$restoreTerrain(ServerLevel level, PoiManager points, RegionStorageInfo storage, ChunkPos position,
CallbackInfoReturnable<ProtoChunk> callback) {
ModdedNativeTerrainReceipts.set(callback.getReturnValue(), iris$naturalTerrain);
ModdedNativeTerrainReceipts.setStructureActivation(callback.getReturnValue(), iris$structureActivation);
}
@Inject(method = "write", at = @At("RETURN"))
private void iris$writeTerrain(CallbackInfoReturnable<CompoundTag> callback) {
if (iris$structureActivation > 0) {
callback.getReturnValue().putLong(NativeTerrainReceipt.STRUCTURE_ACTIVATION_KEY, iris$structureActivation);
}
if (iris$naturalTerrain != null) {
callback.getReturnValue().putByteArray(NativeTerrainReceipt.NBT_KEY, iris$naturalTerrain.clone());
}
}
}
@@ -0,0 +1,25 @@
package art.arcane.iris.modded.mixin;
import art.arcane.iris.modded.IrisModdedChunkGenerator;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(WorldGenRegion.class)
public abstract class WorldGenerationWriteGuardMixin {
@Inject(method = "ensureCanWrite", at = @At("RETURN"), cancellable = true)
private void iris$guardCompletedTerrain(BlockPos position, CallbackInfoReturnable<Boolean> result) {
WorldGenRegion region = (WorldGenRegion) (Object) this;
if (result.getReturnValue() && region.getLevel().getChunkSource().getGenerator()
instanceof IrisModdedChunkGenerator generator
&& region.getChunk(position.getX() >> 4, position.getZ() >> 4)
.getPersistedStatus().isOrAfter(ChunkStatus.FULL)
&& !generator.allowsNativeChunkWrite(position.getX() >> 4, position.getZ() >> 4)) {
result.setReturnValue(false);
}
}
}
@@ -23,6 +23,10 @@ import art.arcane.iris.core.gui.PregeneratorJob;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.tools.WorldMaintenance;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.history.SavedTerrainChunk;
import art.arcane.iris.modded.ModdedSavedTerrainCapture;
import java.util.concurrent.CompletableFuture;
import art.arcane.iris.engine.framework.EnginePlatformHooks;
import art.arcane.iris.engine.framework.NativeStructureVolume;
import art.arcane.iris.engine.object.IrisDimension;
@@ -64,6 +68,16 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
private volatile ExecutorService executor;
private long lastPollAt;
@Override
public CompletableFuture<Void> flushSavedTerrainCapture(Engine engine) {
return ModdedSavedTerrainCapture.flush(engine);
}
@Override
public CompletableFuture<SavedTerrainChunk> captureSavedTerrainChunk(Engine engine, int chunkX, int chunkZ) {
return ModdedSavedTerrainCapture.capture(engine, chunkX, chunkZ);
}
@Override
public void onEnable() {
if (executor != null) {
@@ -4,6 +4,11 @@
"package": "art.arcane.iris.modded.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [
"WorldGenerationWriteGuardMixin",
"ChunkMapTerrainReceiptAccess",
"ChunkTerrainReceiptMixin",
"LevelChunkTerrainReceiptMixin",
"SerializableTerrainReceiptMixin",
"EntityPersistenceMixin",
"LivingEntityLootMixin",
"MobAwarenessMixin",
@@ -19,6 +19,16 @@
package art.arcane.iris.modded;
import art.arcane.volmlib.util.collection.KSet;
import art.arcane.iris.core.nms.datapack.v1217.DataFixerV1217;
import art.arcane.iris.engine.history.GenerationEpochContractFactory;
import art.arcane.iris.engine.history.GenerationRegistryContract;
import art.arcane.iris.engine.history.GenerationRegistryContractFactory;
import art.arcane.iris.engine.object.IrisCustomBiomeAliasResolver;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisDimensionType;
import art.arcane.iris.spi.PlatformGenerationRegistry;
import java.lang.reflect.Constructor;
import java.util.Set;
import org.junit.Test;
import java.io.IOException;
@@ -39,6 +49,33 @@ import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class ModdedForcedDatapackTest {
@Test
public void retainedDimensionSourceRendersBeforeNativeRegistryExists() throws Exception {
DataFixerV1217 fixer = new DataFixerV1217();
IrisDimensionType type = new IrisDimension().getDimensionType();
String source = type.toJson(fixer);
GenerationRegistryContract.PhysicalResourceKey key = new GenerationRegistryContract.PhysicalResourceKey(
GenerationRegistryContractFactory.DIMENSION_TYPE_REGISTRY, "iris:test_type");
GenerationRegistryContract.GeneratedSource saved = new GenerationRegistryContract.GeneratedSource(
GenerationRegistryContractFactory.DIMENSION_TYPE_EFFECTIVE_SOURCE_SCHEMA,
GenerationEpochContractFactory.fingerprintDimensionType(type),
GenerationEpochContractFactory.dimensionTypeSemanticJson(type), "test-renderer",
GenerationRegistryContractFactory.fingerprintDefinition(
key, PlatformGenerationRegistry.Definition.exactJson(source), "generated"), source);
GenerationRegistryContract contract = GenerationRegistryContract.fromDefinitionsAndGeneratedSources(
Map.of(key, GenerationRegistryContractFactory.fingerprintGeneratedSemantic(key, saved)),
Map.of(key, saved));
Class<?> stageType = Class.forName(ModdedForcedDatapack.class.getName() + "$PackStageContext");
Constructor<?> constructor = stageType.getDeclaredConstructor(Path.class, String.class, String.class,
String.class, boolean.class, boolean.class, boolean.class, Set.class, GenerationRegistryContract.class);
constructor.setAccessible(true);
IrisCustomBiomeAliasResolver resolver = (IrisCustomBiomeAliasResolver) constructor.newInstance(
Path.of("retained"), "test", "test", "iris:test_type", false, false, true, Set.of(), contract);
assertEquals(source, resolver.generatedSource(key.registryKey(), key.resourceKey(), "{}", fixer));
assertThrows(IOException.class, () -> resolver.generatedSource(
key.registryKey(), "iris:missing_type", "{}", fixer));
}
@Test
public void packScopedIdsCannotCollideAndHaveReadableLabels() {
String first = ModdedWorldgenIds.presetRef("overworld", "overworld");
@@ -25,7 +25,15 @@ public class ModdedGenerationHistoryRoutingContractTest {
assertTrue(createHistoryEngine.contains("IrisBoundarySignatureSampler.INSTANCE"));
assertTrue(createHistoryEngine.contains("getGenerationTransitionWidthBlocks()"));
assertTrue(createHistoryEngine.contains("router.preloadActiveRuntimes();"));
assertBefore(createHistoryEngine, "close(candidate);", "ModdedGenerationHistoryStorage.resolveActive(history)");
assertBefore(createHistoryEngine, "history.prepareCurrentGenerator(",
"opened = ModdedGenerationHistoryStorage.resolveActive(history)");
assertBefore(createHistoryEngine, "opened = ModdedGenerationHistoryStorage.resolveActive(history)",
"IrisEngine candidate = buildEngine(level, seed, opened)");
String promoted = method(createHistoryEngine,
"if (history.activeActivation().activationId() != openedActivationId)");
assertBefore(promoted, "close(candidate);", "ModdedGenerationHistoryStorage.resolveActive(history)");
assertBefore(promoted, "ModdedGenerationHistoryStorage.resolveActive(history)",
"candidate = buildEngine(level, seed, promoted)");
assertTrue(createHistoryEngine.contains("GenerationHistoryRuntimeRouter.attach("));
assertTrue(buildEngine.contains("active.packRoot().toFile()"));
assertTrue(buildEngine.contains("history.paths().activationMantleRoot(activation.activationId())"));
@@ -84,10 +92,16 @@ public class ModdedGenerationHistoryRoutingContractTest {
assertTrue(fill.contains("generateTerrain(chunk, generationEngine, pos, air, route)"));
assertTrue(fill.contains("return closeRouteOnCompletion(pipeline, route);"));
assertTrue(terrain.contains("openHistoryRuntimeScope(route)"));
assertBefore(terrain, "generationEngine.generate(", "route.claimGeneratedSemantics();");
assertBefore(terrain, "route.claimGeneratedSemantics();", "return chunk;");
assertBefore(terrain, "generationEngine.generate(", "writeBlocks(chunk, blocks, dimMinY, height)");
assertBefore(terrain, "writeBlocks(chunk, blocks, dimMinY, height)", "route.claimGeneratedSemantics(");
assertTrue(terrain.contains("chunk.getBlockState(position.set(x, dimMinY + y, z))"));
assertTrue(terrain.contains("state.isAir() || state.liquid()"));
assertBefore(terrain, "route.claimGeneratedSemantics(", "ModdedNativeTerrainReceipts.persist(chunk, route)");
assertBefore(terrain, "ModdedNativeTerrainReceipts.persist(chunk, route)", "return chunk;");
assertTrue(completion.contains("pipeline.whenComplete("));
assertTrue(completion.contains("route.close();"));
assertBefore(completion, "pipeline.whenComplete(", "route.detachThread()");
assertBefore(completion, "route.detachThread()", "return completion;");
}
@Test
@@ -108,7 +122,7 @@ public class ModdedGenerationHistoryRoutingContractTest {
}
@Test
public void discreteStagesResumeIncompleteHistoricalChunksAndRejectTransitionOwnership() throws IOException {
public void pendingNativeStagesPreserveSavedStartsAndGuardNewFeatureFootprints() throws IOException {
String generator = source("IrisModdedChunkGenerator.java");
String discrete = method(generator, "private boolean allowsRoutedDiscreteGeneration(");
String decoration = method(generator, "public void applyBiomeDecoration(");
@@ -117,19 +131,28 @@ public class ModdedGenerationHistoryRoutingContractTest {
String nativeStage = source("ModdedNativeStructureStage.java");
String adjust = method(nativeStage, "void adjustGeneratedStructures(");
String place = method(nativeStage, "void placeVanillaStructures(");
String savedStart = method(nativeStage, "private boolean isHistoricalStructureStart(");
assertTrue(discrete.contains("chunk.getPersistedStatus().isOrAfter(stage)"));
assertFalse(discrete.contains("router.history().activeActivation().activationId()"));
assertFalse(discrete.contains("return true;"));
assertTrue(discrete.contains("allowsNewGenerationChunk("));
assertTrue(discrete.contains("NativeGenerationWriteGuard.allowsPendingStage(current, chunk, stage)"));
assertTrue(decoration.contains("ChunkStatus.FEATURES"));
assertTrue(structures.contains("ChunkStatus.STRUCTURE_STARTS"));
assertTrue(mobs.contains("ChunkStatus.SPAWN"));
assertBefore(decoration, "allowsRoutedDiscreteGeneration(", "nativeStructures.placeVanillaStructures(");
assertBefore(decoration, "chunk.getPersistedStatus().isOrAfter(ChunkStatus.FEATURES)",
"nativeStructures.placeVanillaStructures(");
assertBefore(decoration, "nativeStructures.placeVanillaStructures(", "allowsRoutedDiscreteGeneration(");
assertBefore(decoration, "allowsRoutedDiscreteGeneration(", "importedFeatures.run(");
assertBefore(decoration, "NativeGenerationWriteGuard.allowsDecoration(", "importedFeatures.run(");
assertBefore(structures, "allowsRoutedDiscreteGeneration(", "super.createStructures(");
assertBefore(mobs, "allowsRoutedDiscreteGeneration(", "NaturalSpawner.spawnMobsForChunkGeneration(");
assertBefore(adjust, "allowsNewGenerationFootprint(", "recordWorldCheckStructureShift(");
assertTrue(place.contains("allowsNewGenerationFootprint("));
assertBefore(place, "!isHistoricalStructureStart(current, world, start)", "allowsNewGenerationFootprint(");
assertTrue(savedStart.contains("world.getChunk(origin.x(), origin.z(), ChunkStatus.EMPTY, false)"));
assertTrue(savedStart.contains("ModdedNativeTerrainReceipts.structureActivation(source)"));
assertBefore(structures, "super.createStructures(", "ModdedNativeTerrainReceipts.persistStructureActivation(");
}
private static void assertRuntimeRoute(String source, String signature) {