mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 13:30:08 +00:00
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:
1 parent
20ad49b9d2
commit
b1e1fbc15e
214 files changed
+11985
-4082
No files matched your search
+38
-6
@@ -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) {
|
||||
|
||||
+4
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-1
@@ -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();
|
||||
|
||||
+53
@@ -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;
|
||||
}
|
||||
}
|
||||
+142
@@ -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;
|
||||
|
||||
+11
@@ -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);
|
||||
}
|
||||
+4
-1
@@ -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);
|
||||
|
||||
+18
@@ -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();
|
||||
}
|
||||
+37
@@ -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();
|
||||
}
|
||||
}
|
||||
+21
@@ -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));
|
||||
}
|
||||
}
|
||||
+89
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -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",
|
||||
|
||||
+37
@@ -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");
|
||||
|
||||
+29
-6
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user