Tracked generated content ownership

- Generated furniture keeps ownership tokens for targeted removal
- Native writes invalidate pending custom content and tile records
- Regeneration and chunk updates remove invalidated content on owning threads
- Provider callbacks return ownership tokens across server platforms
- Regression fixtures cover terrain replacement and restart cleanup
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-18 17:58:30 -04:00
1 parent ed5483d75a
commit 34417b2f5b
53 files changed
+2082 -110

No files matched your search

@@ -57,6 +57,8 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedChunkGenera
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeGeneratorContext;
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedStructureStage;
import art.arcane.iris.world.safeguard.GenerationRefusalNotice;
@@ -829,12 +831,12 @@ public final class IrisModdedChunkGenerator implements NativeGeneratorOwner, Nat
importedFeatures.invalidate();
}
public void updateRegeneratedChunk(int chunkX, int chunkZ) {
public void updateRegeneratedChunk(Mantle<Matter> mantle, int chunkX, int chunkZ) {
ModdedChunkUpdateService service = ModdedEngineBootstrap.services().service(ModdedChunkUpdateService.class);
if (service == null) {
throw new IllegalStateException("Iris chunk update service is unavailable during regeneration");
}
service.updateRegeneratedChunk(engine(), boundLevel(), chunkX, chunkZ);
service.updateRegeneratedChunk(engine(), boundLevel(), mantle, chunkX, chunkZ);
}
public NativeBiomeResolver regenBiomeResolver() {
@@ -19,6 +19,8 @@
package art.arcane.iris.modded.api;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.mantle.CustomBlockOwnership;
import art.arcane.iris.integration.Identifier;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.ModdedBlockState;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeBlockPlacement;
@@ -297,11 +299,11 @@ public final class ModdedCustomContentRegistry {
* consulted for that position. An unparseable key or no matching provider is logged and skipped. Called on the
* server thread with the chunk loaded.
*/
public static void processBlockPlacement(Engine engine, String key, Supplier<NativeBlockPlacement> placement) {
public static CustomBlockOwnership.Entry processBlockPlacement(Engine engine, String key, Supplier<NativeBlockPlacement> placement) {
String base = parseIdentifier(key);
if (base == null) {
ModdedIrisLog.warn("Iris deferred custom block placement rejected invalid id {}", key);
return;
return null;
}
Map<String, String> state = parseState(key);
for (ModdedDataProvider provider : PROVIDERS) {
@@ -309,14 +311,33 @@ public final class ModdedCustomContentRegistry {
continue;
}
try {
provider.processBlockPlacement(new ModdedBlockPlacementContext(
String token = provider.processBlockPlacement(new ModdedBlockPlacementContext(
engine, placement.get(), base, state));
int properties = key.indexOf('[');
String qualifiedKey = base.replace(':', '/') + (properties < 0 ? "" : key.substring(properties));
return token == null ? null : new CustomBlockOwnership.Entry(
new Identifier(provider.modId(), qualifiedKey), token, false);
} catch (Throwable error) {
ModdedIrisLog.error("Iris custom content provider '{}' failed post-placement for {}", provider.modId(), key, error);
}
return;
return null;
}
ModdedIrisLog.warn("Iris deferred custom block placement has no provider for {}", key);
return null;
}
public static boolean removeGenerated(Engine engine, CustomBlockOwnership.Entry entry,
Supplier<NativeBlockPlacement> placement) {
for (ModdedDataProvider provider : PROVIDERS) {
if (provider.modId().equals(entry.identifier().namespace()) && provider.isReady()) {
String storedKey = entry.identifier().key();
int separator = storedKey.indexOf('/');
String key = storedKey.substring(0, separator) + ":" + storedKey.substring(separator + 1);
return provider.removeGenerated(new ModdedBlockPlacementContext(
engine, placement.get(), parseIdentifier(key), parseState(key)), entry.token());
}
}
return false;
}
/**
@@ -95,7 +95,12 @@ public interface ModdedDataProvider {
* Called on the server thread, once per deferred position, by the first provider that claims the identifier -
* later providers are not consulted for that position. Only fires for states returned as deferred.
*/
default void processBlockPlacement(ModdedBlockPlacementContext context) {
default String processBlockPlacement(ModdedBlockPlacementContext context) {
return null;
}
default boolean removeGenerated(ModdedBlockPlacementContext context, String token) {
return false;
}
/**
@@ -22,6 +22,7 @@ import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.world.WorldMaintenance;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.mantle.EngineMantle;
import art.arcane.iris.generation.mantle.DeferredPlacementRecords;
import art.arcane.iris.modded.IrisModdedChunkGenerator;
import art.arcane.iris.modded.ModdedBlockBuffer;
import art.arcane.iris.spi.IrisPlatforms;
@@ -36,6 +37,9 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeCommandSource;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeChunkRegeneration;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeProtocolPlayer;
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.matter.Matter;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -110,10 +114,12 @@ public final class ModdedRegen {
private void resetMantleMargin() {
EngineMantle engineMantle = engine.getMantle();
Mantle<Matter> mantle = engineMantle.getMantle();
int margin = radius + Math.max(engineMantle.getRadius(), engineMantle.getRealRadius()) + 1;
for (int dx = -margin; dx <= margin; dx++) {
for (int dz = -margin; dz <= margin; dz++) {
engineMantle.getMantle().deleteChunk(centerX + dx, centerZ + dz);
DeferredPlacementRecords.resetMantleChunk(mantle, centerX + dx, centerZ + dz,
Math.abs(dx) <= radius && Math.abs(dz) <= radius);
}
}
}
@@ -199,10 +205,21 @@ public final class ModdedRegen {
}
private void apply(int chunkX, int chunkZ, ModdedBlockBuffer blocks, Hunk<NativeBiome> biomes) {
Mantle<Matter> mantle = engine.getMantle().getMantle();
regeneration.apply(new NativeChunkRegeneration.Replacement(chunkX, chunkZ,
engine.getMinHeight(), engine.getMaxHeight() - engine.getMinHeight(),
blocks::rawOrNull, generator.regenBiomeResolver()),
() -> generator.updateRegeneratedChunk(chunkX, chunkZ));
() -> completeReplacement(mantle, chunkX, chunkZ));
}
private void completeReplacement(Mantle<Matter> mantle, int chunkX, int chunkZ) {
MantleChunk<Matter> records = mantle.useChunk(chunkX, chunkZ);
try {
DeferredPlacementRecords.invalidateCustomBlockOwnership(records);
} finally {
records.release();
}
generator.updateRegeneratedChunk(mantle, chunkX, chunkZ);
}
private void ok(String message) {
@@ -28,6 +28,8 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeBlockProperties;
import art.arcane.iris.configuration.IrisSettings;
import art.arcane.iris.studio.view.PregeneratorJob;
import art.arcane.iris.integration.Identifier;
import art.arcane.iris.generation.mantle.CustomBlockOwnership;
import art.arcane.iris.generation.mantle.DeferredPlacementRecords;
import art.arcane.iris.generation.cache.Cache;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.block.TileData;
@@ -166,8 +168,8 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
updatePassChunk(engine, level, (int) chunkKey, (int) (chunkKey >> 32)));
}
public void updateRegeneratedChunk(Engine engine, NativeWorld level, int chunkX, int chunkZ) {
updateChunk(engine, level, chunkX, chunkZ, false);
public void updateRegeneratedChunk(Engine engine, NativeWorld level, Mantle<Matter> mantle, int chunkX, int chunkZ) {
updateChunk(engine, level, mantle, chunkX, chunkZ, false);
}
/**
@@ -176,7 +178,7 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
*/
private void updatePassChunk(Engine engine, NativeWorld level, int chunkX, int chunkZ) {
try {
updateChunk(engine, level, chunkX, chunkZ, true);
updateChunk(engine, level, engine.getMantle().getMantle(), chunkX, chunkZ, true);
} catch (Throwable failure) {
if (engine.isClosed() || engine.getMantle().getMantle().isClosed()) {
throw failure;
@@ -188,7 +190,8 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
}
}
private void updateChunk(Engine engine, NativeWorld level, int chunkX, int chunkZ, boolean requireNeighbors) {
private void updateChunk(Engine engine, NativeWorld level, Mantle<Matter> mantle,
int chunkX, int chunkZ, boolean requireNeighbors) {
if (requireNeighbors) {
for (int x = -1; x <= 1; x++) {
for (int z = -1; z <= 1; z++) {
@@ -199,11 +202,11 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
}
}
Mantle<Matter> mantle = engine.getMantle().getMantle();
if (!mantle.isChunkLoaded(chunkX, chunkZ)) {
warmupMantleChunk(mantle, chunkX, chunkZ);
return;
}
DeferredPlacementRecords.removeInvalidated(engine, mantle, chunkX, chunkZ);
if (mantle.hasFlag(chunkX, chunkZ, MantleFlag.ETCHED)) {
return;
}
@@ -229,14 +232,19 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
int baseX = chunkX << 4;
int baseZ = chunkZ << 4;
int minHeight = engine.getWorld().minHeight();
materializeDeferredSlice(chunk, TileWrapper.class, () ->
chunk.iterate(TileWrapper.class, (Integer x, Integer yf, Integer z, TileWrapper v) -> {
int y = yf + minHeight;
if (y < level.minHeight() || y >= level.maxHeight() - 1) {
return;
}
applyTile(level, baseX + (x & 15), y, baseZ + (z & 15), v.getData());
}));
materializeDeferredSlice(chunk, TileWrapper.class, () -> {
for (DeferredPlacementRecords.Pending<TileWrapper> record : DeferredPlacementRecords.snapshot(chunk, TileWrapper.class)) {
if (!DeferredPlacementRecords.isPending(chunk, TileWrapper.class, record)) {
continue;
}
NativeBlockPoint position = record.position();
int y = position.y() + minHeight;
if (y < level.minHeight() || y >= level.maxHeight() - 1) {
continue;
}
applyTile(level, baseX + (position.x() & 15), y, baseZ + (position.z() & 15), record.value().getData());
}
});
}
private void runCustomPass(Engine engine, NativeWorld level, int chunkX, int chunkZ, MantleChunk<Matter> chunk) {
@@ -244,22 +252,30 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
int baseZ = chunkZ << 4;
int minHeight = engine.getWorld().minHeight();
StaleCarriers stale = new StaleCarriers();
materializeDeferredSlice(chunk, Identifier.class, () ->
chunk.iterate(Identifier.class, (Integer x, Integer yf, Integer z, Identifier identifier) -> {
int y = yf + minHeight;
if (y < level.minHeight() || y >= level.maxHeight() - 1) {
return;
}
int blockX = baseX + (x & 15);
int blockZ = baseZ + (z & 15);
String key = identifier.toString();
if (stale.carrierReplaced(key, level.getBlock(blockX, y, blockZ))) {
return;
}
NativeBlockPoint position = new NativeBlockPoint(blockX, y, blockZ);
ModdedCustomContentRegistry.processBlockPlacement(engine, key,
() -> NativeWorldMaintenance.placement(level, position));
}));
materializeDeferredSlice(chunk, Identifier.class, () -> {
for (DeferredPlacementRecords.Pending<Identifier> record : DeferredPlacementRecords.snapshot(chunk, Identifier.class)) {
if (!DeferredPlacementRecords.isPending(chunk, Identifier.class, record)) {
continue;
}
NativeBlockPoint local = record.position();
int y = local.y() + minHeight;
if (y < level.minHeight() || y >= level.maxHeight() - 1) {
continue;
}
int blockX = baseX + (local.x() & 15);
int blockZ = baseZ + (local.z() & 15);
String key = record.value().toString();
if (stale.carrierReplaced(key, level.getBlock(blockX, y, blockZ))) {
continue;
}
NativeBlockPoint position = new NativeBlockPoint(blockX, y, blockZ);
CustomBlockOwnership.Entry ownership = ModdedCustomContentRegistry.processBlockPlacement(engine, key,
() -> NativeWorldMaintenance.placement(level, position));
if (ownership != null) {
DeferredPlacementRecords.remember(engine, chunk, local, ownership);
}
}
});
stale.report(level.name(), chunkX, chunkZ);
}
@@ -411,7 +427,8 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
carrier = carrierKey(key);
carriers.put(key, carrier);
}
if (carrier.equals(UNCHECKED) || carrier.equals(IrisProceduralBlocks.materialKey(live))) {
if (carrier.equals(UNCHECKED) || carrier.equals(IrisProceduralBlocks.materialKey(live))
|| ((carrier.equals("minecraft:fire") || carrier.equals("minecraft:soul_fire")) && live.isAir())) {
return false;
}
skipped++;
@@ -28,6 +28,10 @@ import art.arcane.iris.modded.ModdedSavedTerrainCapture;
import java.util.concurrent.CompletableFuture;
import art.arcane.iris.generation.runtime.EnginePlatformHooks;
import art.arcane.iris.generation.mantle.CustomBlockOwnership;
import art.arcane.iris.modded.api.ModdedCustomContentRegistry;
import art.arcane.volmlib.nativelib.terrain.NativeBlockPoint;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldMaintenance;
import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.generation.terrain.IrisDimensionRuntimeContract;
@@ -72,6 +76,12 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
private volatile ExecutorService executor;
private long lastPollAt;
@Override
public boolean removeCustomBlock(Engine engine, NativeBlockPoint position, CustomBlockOwnership.Entry entry) {
return ModdedCustomContentRegistry.removeGenerated(engine, entry,
() -> NativeWorldMaintenance.placement(engine.getWorld().platformWorld(), position));
}
@Override
public CompletableFuture<Void> flushSavedTerrainCapture(Engine engine) {
return ModdedSavedTerrainCapture.flush(engine);