Merged generation history

- Immutable generation epochs now coexist with dimension stacks
- Historical terrain, biomes, structures, and locate behavior remain stable across updates
- Versioned generation kernels preserve the combined runtime contract
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-04 15:06:55 -04:00
commit 56b5f4dd0b
263 files changed
+39624 -1849

No files matched your search

@@ -8,9 +8,11 @@ import art.arcane.iris.engine.DimensionStackContext;
import art.arcane.iris.engine.DimensionStackLayout;
import art.arcane.iris.engine.DimensionTerrainContext;
import art.arcane.iris.engine.data.cache.AtomicCache;
import art.arcane.iris.engine.IrisEngine;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.GenerationSessionException;
import art.arcane.iris.engine.framework.GenerationSessionLease;
import art.arcane.iris.engine.history.GenerationHistoryRuntimeRouter;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisBiomeCustom;
import art.arcane.iris.engine.object.IrisDimension;
@@ -40,6 +42,7 @@ import org.bukkit.craftbukkit.CraftWorld;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -65,14 +68,11 @@ public class CustomBiomeSource extends BiomeSource {
private final Registry<Biome> biomeRegistry;
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
private final Holder<Biome> fallbackBiome;
private final ConcurrentHashMap<Long, Holder<Biome>> noiseBiomeCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, Holder<Biome>> structureBiomeCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, Holder<Biome>> surfaceStructureBiomeCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, Holder<Biome>> naturalSurfaceStructureBiomeCache = new ConcurrentHashMap<>();
private volatile KMap<String, Holder<Biome>> customBiomes;
private volatile Map<Biome, Holder<Biome>> vanillaSpawnBiomes;
private volatile IrisDimension cacheDimension;
private volatile int cacheRuntimeId;
private final ConcurrentHashMap<RuntimeNoiseKey, Holder<Biome>> noiseBiomeCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<RuntimeNoiseKey, Holder<Biome>> structureBiomeCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<RuntimeColumnKey, Holder<Biome>> surfaceStructureBiomeCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<RuntimeColumnKey, Holder<Biome>> naturalSurfaceStructureBiomeCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Integer, RuntimeBiomeState> runtimeBiomeStates = new ConcurrentHashMap<>();
public CustomBiomeSource(long seed, Engine engine, World world) {
this.engine = engine;
@@ -80,10 +80,10 @@ public class CustomBiomeSource extends BiomeSource {
this.biomeCustomRegistry = registry().lookup(Registries.BIOME).orElse(null);
this.biomeRegistry = ((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())).lookup(Registries.BIOME).orElse(null);
this.fallbackBiome = resolveFallbackBiome(this.biomeRegistry, this.biomeCustomRegistry);
this.customBiomes = fillCustomBiomes(this.biomeCustomRegistry, engine, this.fallbackBiome);
this.vanillaSpawnBiomes = fillVanillaSpawnBiomes(this.biomeCustomRegistry, this.biomeRegistry, engine);
this.cacheDimension = engine.getDimension();
this.cacheRuntimeId = engine.getCacheID();
if (engine instanceof IrisEngine irisEngine) {
irisEngine.addGenerationRuntimeRetirementListener(this::evictRuntimeCaches);
}
runtimeBiomeState();
}
private static List<Holder<Biome>> getAllBiomes(
@@ -133,7 +133,7 @@ public class CustomBiomeSource extends BiomeSource {
if (irisBiome.isCustom()) {
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
Holder<Biome> customHolder = resolveCustomBiomeHolder(
customRegistry, ownedBiome.dimension(), customBiome.getId());
customRegistry, engine, ownedBiome.dimension(), customBiome.getId());
if (customHolder == null) {
throw new IllegalStateException("Iris custom visible biome '"
+ customBiomeKey(ownedBiome.dimension(), customBiome.getId())
@@ -257,7 +257,7 @@ public class CustomBiomeSource extends BiomeSource {
if (!isRuntimeAvailable()) {
throw new IllegalStateException("Iris possible biome lookup has no active engine runtime");
}
ensureCachesCurrent();
runtimeBiomeState();
World world = BukkitWorldBinding.world(engine.getWorld());
if (world == null) {
throw new IllegalStateException("Iris biome source has no bound Bukkit world");
@@ -284,7 +284,7 @@ public class CustomBiomeSource extends BiomeSource {
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
String key = customBiomeKey(ownedBiome.dimension(), j.getId());
Holder<Biome> holder = resolveCustomBiomeHolder(
customRegistry, ownedBiome.dimension(), j.getId());
customRegistry, engine, ownedBiome.dimension(), j.getId());
if (holder == null) {
if (fallback != null) {
m.put(key, fallback);
@@ -318,7 +318,7 @@ public class CustomBiomeSource extends BiomeSource {
}
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
Holder<Biome> customHolder = resolveCustomBiomeHolder(
customRegistry, ownedBiome.dimension(), customBiome.getId());
customRegistry, engine, ownedBiome.dimension(), customBiome.getId());
if (customHolder != null) {
spawnBiomes.putIfAbsent(customHolder.value(), vanillaHolder);
}
@@ -340,8 +340,7 @@ public class CustomBiomeSource extends BiomeSource {
if (!isRuntimeAvailable()) {
return null;
}
ensureCachesCurrent();
return vanillaSpawnBiomes.get(biome.value());
return runtimeBiomeState().vanillaSpawnBiomes().get(biome.value());
}
}
@@ -354,7 +353,7 @@ public class CustomBiomeSource extends BiomeSource {
if (!isRuntimeAvailable()) {
return null;
}
ensureCachesCurrent();
runtimeBiomeState();
int quartX = QuartPos.fromBlock(blockX);
int quartZ = QuartPos.fromBlock(blockZ);
int sampleX = QuartPos.toBlock(quartX);
@@ -390,15 +389,17 @@ public class CustomBiomeSource extends BiomeSource {
@Override
public Holder<Biome> getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
GenerationSessionLease lease = tryAcquireGenerationLease("bukkit_structure_biome");
if (lease == null) {
throw new IllegalStateException("Iris structure biome lookup was rejected during an engine transition");
}
try (lease; IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
int blockX = x << 2;
int blockZ = z << 2;
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope =
openHistoryCoordinateScope(blockX, blockZ, "bukkit_structure_biome");
GenerationSessionLease lease = requireGenerationLease(
"bukkit_structure_biome",
"Iris structure biome lookup was rejected during an engine transition");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
if (!isRuntimeAvailable()) {
throw new IllegalStateException("Iris structure biome lookup has no active engine runtime");
}
ensureCachesCurrent();
return getStructureNoiseBiomeWithActiveGenerationLease(x, y, z);
}
}
@@ -409,7 +410,8 @@ public class CustomBiomeSource extends BiomeSource {
}
boolean cacheable = isBiomeCacheable(x, z);
long cacheKey = packNoiseKey(x, y, z);
RuntimeNoiseKey cacheKey = new RuntimeNoiseKey(
engine.getCacheID(), packNoiseKey(x, y, z));
if (cacheable) {
Holder<Biome> cachedHolder = structureBiomeCache.get(cacheKey);
if (cachedHolder != null) {
@@ -455,7 +457,6 @@ public class CustomBiomeSource extends BiomeSource {
if (!isRuntimeAvailable()) {
throw new IllegalStateException("Iris structure ring biome lookup has no active engine runtime");
}
ensureCachesCurrent();
return findNaturalSurfaceBiomeHorizontal(
x, y, z, searchRadius, quartStep, allowed, random);
}
@@ -533,7 +534,6 @@ public class CustomBiomeSource extends BiomeSource {
if (!isRuntimeAvailable()) {
throw new IllegalStateException("Iris biome radius lookup has no active engine runtime");
}
ensureCachesCurrent();
int minQuartY = QuartPos.fromBlock(y - radius);
boolean monumentQuery = radius == 29
&& y == engine.getMinHeight() + engine.getDimension().getFluidHeight();
@@ -556,20 +556,27 @@ public class CustomBiomeSource extends BiomeSource {
}
private Holder<Biome> getSurfaceStructureBiomeHolder(int x, int z) {
long columnKey = packColumnKey(x, z);
Holder<Biome> surfaceHolder = surfaceStructureBiomeCache.get(columnKey);
if (surfaceHolder != null) {
return surfaceHolder;
int blockX = x << 2;
int blockZ = z << 2;
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope =
openHistoryCoordinateScope(blockX, blockZ, "bukkit_surface_structure_biome")) {
RuntimeColumnKey columnKey = new RuntimeColumnKey(
engine.getCacheID(), packColumnKey(x, z));
Holder<Biome> surfaceHolder = surfaceStructureBiomeCache.get(columnKey);
if (surfaceHolder != null) {
return surfaceHolder;
}
Holder<Biome> resolvedSurfaceHolder = resolveSurfaceStructureBiomeHolder(x, z);
Holder<Biome> existingSurfaceHolder = surfaceStructureBiomeCache.putIfAbsent(
columnKey, resolvedSurfaceHolder);
if (existingSurfaceHolder != null) {
return existingSurfaceHolder;
}
if (surfaceStructureBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
surfaceStructureBiomeCache.clear();
}
return resolvedSurfaceHolder;
}
Holder<Biome> resolvedSurfaceHolder = resolveSurfaceStructureBiomeHolder(x, z);
Holder<Biome> existingSurfaceHolder = surfaceStructureBiomeCache.putIfAbsent(columnKey, resolvedSurfaceHolder);
if (existingSurfaceHolder != null) {
return existingSurfaceHolder;
}
if (surfaceStructureBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
surfaceStructureBiomeCache.clear();
}
return resolvedSurfaceHolder;
}
private Pair<BlockPos, Holder<Biome>> findNaturalSurfaceBiomeHorizontal(
@@ -609,21 +616,27 @@ public class CustomBiomeSource extends BiomeSource {
}
private Holder<Biome> getNaturalSurfaceStructureBiomeHolder(int x, int z) {
long columnKey = packColumnKey(x, z);
Holder<Biome> cachedHolder = naturalSurfaceStructureBiomeCache.get(columnKey);
if (cachedHolder != null) {
return cachedHolder;
int blockX = x << 2;
int blockZ = z << 2;
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope =
openHistoryCoordinateScope(blockX, blockZ, "bukkit_natural_structure_biome")) {
RuntimeColumnKey columnKey = new RuntimeColumnKey(
engine.getCacheID(), packColumnKey(x, z));
Holder<Biome> cachedHolder = naturalSurfaceStructureBiomeCache.get(columnKey);
if (cachedHolder != null) {
return cachedHolder;
}
Holder<Biome> resolvedHolder = resolveNaturalSurfaceStructureBiomeHolder(x, z);
Holder<Biome> existingHolder = naturalSurfaceStructureBiomeCache.putIfAbsent(
columnKey, resolvedHolder);
if (existingHolder != null) {
return existingHolder;
}
if (naturalSurfaceStructureBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
naturalSurfaceStructureBiomeCache.clear();
}
return resolvedHolder;
}
Holder<Biome> resolvedHolder = resolveNaturalSurfaceStructureBiomeHolder(x, z);
Holder<Biome> existingHolder = naturalSurfaceStructureBiomeCache.putIfAbsent(
columnKey, resolvedHolder);
if (existingHolder != null) {
return existingHolder;
}
if (naturalSurfaceStructureBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
naturalSurfaceStructureBiomeCache.clear();
}
return resolvedHolder;
}
private boolean isGuaranteedSurfaceBiome(int quartY) {
@@ -671,11 +684,12 @@ public class CustomBiomeSource extends BiomeSource {
}
public Holder<Biome> getVisibleNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
GenerationSessionLease lease = tryAcquireGenerationLease("bukkit_visible_biome");
if (lease == null) {
throw new IllegalStateException("Iris visible biome lookup was rejected during an engine transition");
}
try (lease; IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope = openHistoryCoordinateScope(
x << 2, z << 2, "bukkit_visible_biome");
GenerationSessionLease lease = requireGenerationLease(
"bukkit_visible_biome",
"Iris visible biome lookup was rejected during an engine transition");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
prepareVisibleBiomeBatch();
return getVisibleNoiseBiomeWithActiveGenerationLease(x, y, z, sampler);
}
@@ -685,7 +699,7 @@ public class CustomBiomeSource extends BiomeSource {
if (!isRuntimeAvailable()) {
throw new IllegalStateException("Iris visible biome lookup has no active engine runtime");
}
ensureCachesCurrent();
runtimeBiomeState();
}
Holder<Biome> getVisibleNoiseBiomeWithActiveGenerationLease(
@@ -717,7 +731,8 @@ public class CustomBiomeSource extends BiomeSource {
IrisDimensionCarvingResolver.State resolverState,
boolean cacheable
) {
long cacheKey = packNoiseKey(x, y, z);
RuntimeNoiseKey cacheKey = new RuntimeNoiseKey(
engine.getCacheID(), packNoiseKey(x, y, z));
if (cacheable) {
Holder<Biome> cachedHolder = noiseBiomeCache.get(cacheKey);
if (cachedHolder != null) {
@@ -768,32 +783,71 @@ public class CustomBiomeSource extends BiomeSource {
}
}
private GenerationSessionLease requireGenerationLease(String operation, String rejectionMessage) {
GenerationSessionLease lease = tryAcquireGenerationLease(operation);
if (lease == null) {
throw new IllegalStateException(rejectionMessage);
}
return lease;
}
private boolean isRuntimeAvailable() {
return !engine.isClosed() && engine.getComplex() != null;
}
private void ensureCachesCurrent() {
private GenerationHistoryRuntimeRouter.CoordinateScope openHistoryCoordinateScope(
int blockX,
int blockZ,
String operation
) {
if (!(engine instanceof IrisEngine irisEngine)) {
return null;
}
try {
return irisEngine.openGenerationHistoryCoordinateScope(blockX, blockZ);
} catch (IOException failure) {
throw new IllegalStateException("Iris " + operation
+ " could not route generation history at "
+ blockX + "," + blockZ + ".", failure);
}
}
private void evictRuntimeCaches(int runtimeId) {
runtimeBiomeStates.remove(runtimeId);
noiseBiomeCache.keySet().removeIf(key -> key.runtimeId() == runtimeId);
structureBiomeCache.keySet().removeIf(key -> key.runtimeId() == runtimeId);
surfaceStructureBiomeCache.keySet().removeIf(key -> key.runtimeId() == runtimeId);
naturalSurfaceStructureBiomeCache.keySet().removeIf(key -> key.runtimeId() == runtimeId);
}
private RuntimeBiomeState runtimeBiomeState() {
IrisDimension dimension = engine.getDimension();
int runtimeId = engine.getCacheID();
if (cacheDimension == dimension && cacheRuntimeId == runtimeId) {
return;
RuntimeBiomeState existing = runtimeBiomeStates.get(runtimeId);
if (existing != null) {
if (existing.dimension() != dimension) {
throw new IllegalStateException("Iris biome runtime cache ID is shared by different dimensions.");
}
return existing;
}
synchronized (this) {
if (cacheDimension == dimension && cacheRuntimeId == runtimeId) {
return;
synchronized (runtimeBiomeStates) {
existing = runtimeBiomeStates.get(runtimeId);
if (existing != null) {
if (existing.dimension() != dimension) {
throw new IllegalStateException("Iris biome runtime cache ID is shared by different dimensions.");
}
return existing;
}
KMap<String, Holder<Biome>> refreshedCustomBiomes = fillCustomBiomes(
biomeCustomRegistry, engine, fallbackBiome);
Map<Biome, Holder<Biome>> refreshedSpawnBiomes = fillVanillaSpawnBiomes(
biomeCustomRegistry, biomeRegistry, engine);
noiseBiomeCache.clear();
structureBiomeCache.clear();
surfaceStructureBiomeCache.clear();
naturalSurfaceStructureBiomeCache.clear();
customBiomes = refreshedCustomBiomes;
vanillaSpawnBiomes = refreshedSpawnBiomes;
cacheDimension = dimension;
cacheRuntimeId = runtimeId;
RuntimeBiomeState created = new RuntimeBiomeState(
dimension,
refreshedCustomBiomes,
refreshedSpawnBiomes);
runtimeBiomeStates.put(runtimeId, created);
return created;
}
}
@@ -830,6 +884,14 @@ public class CustomBiomeSource extends BiomeSource {
IrisDimensionCarvingResolver.State resolverState,
DimensionStackLayout stackLayout
) {
int blockX = x << 2;
int blockY = y << 2;
int blockZ = z << 2;
Optional<String> historicalKey = engine.getComplex().historicalPhysicalBiomeKeyAt(
blockX, blockY, blockZ);
if (historicalKey.isPresent()) {
return resolvePhysicalBiomeHolder(historicalKey.get());
}
BiomeResolution resolution = resolveBiomeResolution(
x, y, z, resolverState, true, stackLayout);
if (resolution == null) {
@@ -858,7 +920,7 @@ public class CustomBiomeSource extends BiomeSource {
private Holder<Biome> resolveCustomHolder(BiomeResolution resolution) {
IrisBiomeCustom customBiome = resolution.irisBiome.getCustomBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
if (customBiome != null) {
Holder<Biome> holder = customBiomes.get(customBiomeKey(
Holder<Biome> holder = runtimeBiomeState().customBiomes().get(customBiomeKey(
resolution.dimension, customBiome.getId()));
if (holder != null) {
return holder;
@@ -872,6 +934,18 @@ public class CustomBiomeSource extends BiomeSource {
return resolveBiomeResolution(x, y, z, null, false);
}
private Holder<Biome> resolvePhysicalBiomeHolder(String physicalKey) {
Holder<Biome> holder = resolveBiomeHolder(biomeCustomRegistry, physicalKey);
if (holder == null) {
holder = resolveBiomeHolder(biomeRegistry, physicalKey);
}
if (holder == null) {
throw new IllegalStateException("Historical Iris biome '" + physicalKey
+ "' is not registered in the active world registry.");
}
return holder;
}
private BiomeResolution resolveBiomeResolution(
int x,
int y,
@@ -1008,17 +1082,19 @@ public class CustomBiomeSource extends BiomeSource {
private static Holder<Biome> resolveCustomBiomeHolder(
Registry<Biome> customRegistry,
Engine engine,
IrisDimension dimension,
String customBiomeId
) {
if (customRegistry == null || dimension == null
if (customRegistry == null || engine == null || dimension == null
|| customBiomeId == null || customBiomeId.isBlank()) {
return null;
}
Identifier resourceLocation = Identifier.tryParse(customBiomeKey(dimension, customBiomeId));
String physicalKey = engine.getData().customBiomeResourceKey(dimension, customBiomeId);
Identifier resourceLocation = Identifier.tryParse(physicalKey);
if (resourceLocation == null) {
return null;
throw new IllegalStateException("Invalid Iris custom biome resource key '" + physicalKey + "'.");
}
Biome biome = customRegistry.getValue(resourceLocation);
if (biome == null) {
@@ -1170,4 +1246,17 @@ public class CustomBiomeSource extends BiomeSource {
private record OwnedBiome(IrisDimension dimension, IrisBiome biome) {
}
private record RuntimeNoiseKey(int runtimeId, long coordinateKey) {
}
private record RuntimeColumnKey(int runtimeId, long coordinateKey) {
}
private record RuntimeBiomeState(
IrisDimension dimension,
KMap<String, Holder<Biome>> customBiomes,
Map<Biome, Holder<Biome>> vanillaSpawnBiomes
) {
}
}
@@ -62,9 +62,7 @@ final class ImportedFeatureStage {
private static final String CYCLE_MARKER = "Feature order cycle found";
private final Engine engine;
private volatile FeatureTable featureTable;
private volatile IrisDimension inertDimension;
private volatile int settledRuntimeId;
private final ConcurrentHashMap<Integer, RuntimeFeatureState> runtimeStates = new ConcurrentHashMap<>();
ImportedFeatureStage(Engine engine) {
this.engine = engine;
@@ -76,7 +74,8 @@ final class ImportedFeatureStage {
* does today.
*/
BiomeGenerationSettings generationSettings(Holder<Biome> biome) {
FeatureTable table = featureTable;
RuntimeFeatureState state = currentState();
FeatureTable table = state == null ? null : state.featureTable();
if (table == null) {
return null;
}
@@ -87,50 +86,46 @@ final class ImportedFeatureStage {
/**
* Builds the table on the first decorated chunk, or leaves the stage inert. A feature-order cycle is
* reported once here and degrades to features-off; it never reaches chunk generation as a crash.
*
* <p>The volatile fast path is unlocked, so a prepared stage costs two reads per chunk. The build is
* synchronized: every worldgen thread decorating a chunk calls this, and two threads that both found the
* stage unprepared would each run {@code FeatureSorter}, whose cycle detection is the expensive part.
*/
void prepare(WorldGenLevel level) {
IrisDimension dimension = engine.getDimension();
int runtimeId = engine.getCacheID();
if (settled(dimension, runtimeId)) {
if (settled(runtimeStates.get(runtimeId), dimension)) {
return;
}
synchronized (this) {
if (settled(dimension, runtimeId)) {
if (settled(runtimeStates.get(runtimeId), dimension)) {
return;
}
build(level, dimension);
settledRuntimeId = runtimeId;
runtimeStates.put(runtimeId, build(level, dimension));
}
}
private boolean settled(IrisDimension dimension, int runtimeId) {
if (settledRuntimeId != runtimeId) {
return false;
}
FeatureTable current = featureTable;
if (current != null && current.dimension() == dimension) {
return true;
}
return inertDimension == dimension;
void evictRuntime(int runtimeId) {
runtimeStates.remove(runtimeId);
}
private void build(WorldGenLevel level, IrisDimension dimension) {
private RuntimeFeatureState currentState() {
IrisDimension dimension = engine.getDimension();
RuntimeFeatureState state = runtimeStates.get(engine.getCacheID());
return settled(state, dimension) ? state : null;
}
private static boolean settled(RuntimeFeatureState state, IrisDimension dimension) {
return state != null && state.dimension() == dimension;
}
private RuntimeFeatureState build(WorldGenLevel level, IrisDimension dimension) {
IrisImportedFeatureControl control;
try {
control = NativeFeatureGenerationPolicy.control(engine);
} catch (RuntimeException error) {
IrisLogging.error("Iris could not read importedFeatures for this dimension; features off: "
+ error);
markInert(dimension);
return;
return new RuntimeFeatureState(dimension, null);
}
if (!control.shouldGenerateFeatures()) {
markInert(dimension);
return;
return new RuntimeFeatureState(dimension, null);
}
FeatureTable built;
try {
@@ -138,23 +133,15 @@ final class ImportedFeatureStage {
} catch (Throwable error) {
IrisLogging.error("Iris importedFeatures is off for " + dimensionKey()
+ ": feature table construction failed: " + error);
markInert(dimension);
return;
return new RuntimeFeatureState(dimension, null);
}
if (built == null) {
markInert(dimension);
return;
return new RuntimeFeatureState(dimension, null);
}
featureTable = built;
inertDimension = null;
IrisLogging.info("Iris importedFeatures on for " + dimensionKey() + ": " + built.biomes().size()
+ " biomes, " + built.steps().size() + " steps, " + built.derivatives().size()
+ " custom-biome derivative maps");
}
private void markInert(IrisDimension dimension) {
featureTable = null;
inertDimension = dimension;
return new RuntimeFeatureState(dimension, built);
}
private FeatureTable buildTable(WorldGenLevel level, IrisImportedFeatureControl control,
@@ -217,7 +204,7 @@ final class ImportedFeatureStage {
continue;
}
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
addKey(keys, engine.getDimension().getCustomBiomeKey(customBiome.getId()));
addKey(keys, engine.getData().customBiomeResourceKey(engine.getDimension(), customBiome));
}
}
return keys;
@@ -249,7 +236,10 @@ final class ImportedFeatureStage {
continue;
}
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
derivatives.put(engine.getDimension().getCustomBiomeKey(customBiome.getId()), derivative);
derivatives.put(
engine.getData().customBiomeResourceKey(engine.getDimension(), customBiome),
derivative
);
}
}
return derivatives;
@@ -280,7 +270,8 @@ final class ImportedFeatureStage {
* control is disabled or the table degraded.
*/
void run(WorldGenLevel level, ChunkAccess chunk, ChunkGenerator owner) {
FeatureTable table = featureTable;
RuntimeFeatureState state = currentState();
FeatureTable table = state == null ? null : state.featureTable();
if (table == null) {
return;
}
@@ -467,4 +458,7 @@ final class ImportedFeatureStage {
List<FeatureSorter.StepFeatureData> steps,
Map<String, Holder<Biome>> derivatives, boolean filtered) {
}
private record RuntimeFeatureState(IrisDimension dimension, FeatureTable featureTable) {
}
}
@@ -7,9 +7,11 @@ import art.arcane.iris.engine.framework.GenerationSessionException;
import art.arcane.iris.engine.framework.GenerationSessionLease;
import art.arcane.iris.engine.framework.IrisStructureLocator;
import art.arcane.iris.engine.framework.NativeStructureOwnershipRecord;
import art.arcane.iris.engine.framework.NativeStructureOwnershipStore;
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
import art.arcane.iris.engine.IrisEngine;
import art.arcane.iris.engine.history.GenerationHistoryRuntimeRouter;
import art.arcane.iris.engine.platform.BukkitChunkGenerator;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisDimensionCarvingResolver;
@@ -101,6 +103,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -133,8 +136,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
private final int runtimeHeight;
private final int runtimeSeaLevel;
private final ConcurrentHashMap<SpawnTableKey, WeightedList<MobSpawnSettings.SpawnerData>> mergedSpawnTables = new ConcurrentHashMap<>();
private volatile IrisDimension spawnTableDimension;
private volatile int spawnTableRuntimeId;
private final ImportedFeatureStage importedFeatures;
private final AtomicReference<StudioStructureState> retainedStudioStructureState = new AtomicReference<>();
private volatile ReachableStructureCache reachableStructureCache;
@@ -158,9 +159,25 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
this.runtimeMinY = level.getMinY();
this.runtimeHeight = level.getHeight();
this.runtimeSeaLevel = runtimeMinY + engine.getDimension().getFluidHeight();
if (engine instanceof IrisEngine irisEngine) {
irisEngine.addGenerationRuntimeRetirementListener(this::evictRuntimeCaches);
}
installNativeStructureVolumeIndex(level);
}
private void evictRuntimeCaches(int runtimeId) {
importedFeatures.evictRuntime(runtimeId);
mergedSpawnTables.keySet().removeIf(key -> key.runtimeId() == runtimeId);
ReachableStructureCache reachable = reachableStructureCache;
if (reachable != null && reachable.runtimeId() == runtimeId) {
reachableStructureCache = null;
}
StructureStepCache steps = structureStepCache;
if (steps != null && steps.runtimeId() == runtimeId) {
structureStepCache = null;
}
}
private void installNativeStructureVolumeIndex(ServerLevel level) {
WeakReference<ServerLevel> levelReference = new WeakReference<>(level);
WeakReference<ChunkGenerator> generatorReference = new WeakReference<>(this);
@@ -186,7 +203,12 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
if (level != runtimeLevel || level.getChunkSource().getGenerator() != this) {
return null;
}
try (GenerationSessionLease lease = requireGenerationLease("bukkit_nms_structure_locate");
try (GenerationHistoryRuntimeRouter.RuntimeRoute route = openHistoryRoute(
SectionPos.blockToSectionCoord(pos.getX()),
SectionPos.blockToSectionCoord(pos.getZ()),
"bukkit_nms_structure_locate");
GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope runtimeScope = openHistoryRuntimeScope(route);
GenerationSessionLease lease = requireGenerationLease("bukkit_nms_structure_locate");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
HolderSet<Structure> reachable = filterReachableStructures(level, holders);
NativeStructureVanillaLocator.Candidate nativeCandidate =
@@ -365,9 +387,16 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|| runtimeLevel.getChunkSource().getGeneratorState() != structureState) {
return;
}
ChunkPos chunkPos = access.getPos();
try (BukkitChunkGenerator.GenerationStagePermit stage = requireGenerationStage("bukkit_nms_create_structures");
GenerationHistoryRuntimeRouter.RuntimeRoute route = openHistoryRoute(
chunkPos.x(), chunkPos.z(), "bukkit_nms_create_structures");
GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope runtimeScope = openHistoryRuntimeScope(route);
GenerationSessionLease lease = requireGenerationLease("bukkit_nms_create_structures");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
if (!allowsRoutedDiscreteGeneration(access, ChunkStatus.STRUCTURE_STARTS)) {
return;
}
Map<Structure, StructureStart> previousStarts = new HashMap<>(access.getAllStarts());
super.createStructures(registryAccess, structureState, structureManager, access, templateManager, levelKey);
Map<Structure, NativeStructureStartPlan> configuredStarts = NativeStructureStartInjector.inject(
@@ -399,15 +428,28 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
if (!start.isValid() || previousStarts.get(structure) == start) {
continue;
}
if (configuredStarts.containsKey(structure)) {
continue;
}
Identifier id = registry.getKey(structure);
String structureId = id == null ? null : id.toString();
if (structureId == null) {
throw NativeStructureGenerationException.failure(
"resolution", null, chunkPos.x(), chunkPos.z());
}
BoundingBox footprint = start.getBoundingBox();
if (!engine.getComplex().allowsNewGenerationFootprint(
footprint.minX(),
footprint.minZ(),
footprint.maxX(),
footprint.maxZ())) {
access.setStartForStructure(structure, StructureStart.INVALID_START);
if (configuredStarts.containsKey(structure)) {
NativeStructureOwnershipStore.discard(
engine, structureId, chunkPos.x(), chunkPos.z());
}
continue;
}
if (configuredStarts.containsKey(structure)) {
continue;
}
boolean undergroundStep = NativeStructureVegetationClearer.isUndergroundStep(structure.step());
IrisNativeStructureDecision decision;
try {
@@ -634,7 +676,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
if (runtimeLevel.getChunkSource().getGenerator() != this) {
return;
}
ChunkPos chunkPos = ichunkaccess.getPos();
try (BukkitChunkGenerator.GenerationStagePermit stage = requireGenerationStage("bukkit_nms_create_references");
GenerationHistoryRuntimeRouter.RuntimeRoute route = openHistoryRoute(
chunkPos.x(), chunkPos.z(), "bukkit_nms_create_references");
GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope runtimeScope = openHistoryRuntimeScope(route);
GenerationSessionLease lease = requireGenerationLease("bukkit_nms_create_references");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
NativeStructureReferenceRepair.createReferences(
@@ -644,7 +690,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public CompletableFuture<ChunkAccess> createBiomes(RandomState randomstate, Blender blender, StructureManager structuremanager, ChunkAccess ichunkaccess) {
ChunkPos chunkPos = ichunkaccess.getPos();
try (BukkitChunkGenerator.GenerationStagePermit stage = requireGenerationStage("bukkit_nms_create_biomes");
GenerationHistoryRuntimeRouter.RuntimeRoute route = openHistoryRoute(
chunkPos.x(), chunkPos.z(), "bukkit_nms_create_biomes");
GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope runtimeScope = openHistoryRuntimeScope(route);
GenerationSessionLease lease = requireGenerationLease("bukkit_nms_create_biomes");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
customBiomeSource.prepareVisibleBiomeBatch();
@@ -659,31 +709,63 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void buildSurface(WorldGenRegion regionlimitedworldaccess, StructureManager structuremanager, RandomState randomstate, ChunkAccess ichunkaccess) {
delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess);
ChunkPos chunkPos = ichunkaccess.getPos();
try (BukkitChunkGenerator.GenerationStagePermit stage = requireGenerationStage("bukkit_nms_build_surface");
GenerationHistoryRuntimeRouter.RuntimeRoute route = openHistoryRoute(
chunkPos.x(), chunkPos.z(), "bukkit_nms_build_surface");
GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope runtimeScope = openHistoryRuntimeScope(route);
GenerationSessionLease lease = requireGenerationLease("bukkit_nms_build_surface");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess);
}
}
@Override
public void applyCarvers(WorldGenRegion regionlimitedworldaccess, long seed, RandomState randomstate, BiomeManager biomemanager, StructureManager structuremanager, ChunkAccess ichunkaccess) {
delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess);
ChunkPos chunkPos = ichunkaccess.getPos();
try (BukkitChunkGenerator.GenerationStagePermit stage = requireGenerationStage("bukkit_nms_apply_carvers");
GenerationHistoryRuntimeRouter.RuntimeRoute route = openHistoryRoute(
chunkPos.x(), chunkPos.z(), "bukkit_nms_apply_carvers");
GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope runtimeScope = openHistoryRuntimeScope(route);
GenerationSessionLease lease = requireGenerationLease("bukkit_nms_apply_carvers");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess);
}
}
@Override
public CompletableFuture<ChunkAccess> fillFromNoise(Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) {
ChunkPos chunkPos = ichunkaccess.getPos();
BukkitChunkGenerator.GenerationStagePermit stage = requireNoiseGenerationStage(
ichunkaccess.getPos(),
chunkPos,
"bukkit_nms_chunk_pipeline");
GenerationSessionLease lease;
GenerationHistoryRuntimeRouter.RuntimeRoute route;
try {
lease = requireGenerationLease("bukkit_nms_chunk_pipeline");
route = openHistoryRoute(chunkPos.x(), chunkPos.z(), "bukkit_nms_chunk_pipeline");
} catch (RuntimeException | Error failure) {
stage.close();
throw failure;
}
GenerationSessionLease lease;
try {
CompletableFuture<ChunkAccess> pipeline = delegate
.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess)
lease = requireGenerationLease("bukkit_nms_chunk_pipeline");
} catch (RuntimeException | Error failure) {
closeHistoryRoute(route, failure);
stage.close();
throw failure;
}
try {
CompletableFuture<ChunkAccess> delegatePipeline;
try (GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope runtimeScope = openHistoryRuntimeScope(route);
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
delegatePipeline = delegate.fillFromNoise(
blender, randomstate, structuremanager, ichunkaccess);
}
CompletableFuture<ChunkAccess> pipeline = delegatePipeline
.thenApply(filled -> {
try (IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
try (GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope runtimeScope =
openHistoryRuntimeScope(route);
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
primeWorldgenHeightmaps(filled);
return filled;
}
@@ -691,24 +773,78 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
CompletableFuture<ChunkAccess> completion = new CompletableFuture<>();
pipeline.whenComplete((filled, failure) -> {
boolean cancelled = isCancellationFailure(failure);
lease.close();
stage.close();
if (failure == null) {
Throwable completionFailure = closeNoisePipelineResources(
failure, lease, route, stage);
if (completionFailure == null) {
completion.complete(filled);
} else if (cancelled) {
completion.cancel(false);
} else {
completion.completeExceptionally(failure);
completion.completeExceptionally(completionFailure);
}
});
return completion;
} catch (RuntimeException | Error failure) {
lease.close();
closeHistoryRoute(route, failure);
stage.close();
throw failure;
}
}
private static Throwable closeNoisePipelineResources(
Throwable failure,
GenerationSessionLease lease,
GenerationHistoryRuntimeRouter.RuntimeRoute route,
BukkitChunkGenerator.GenerationStagePermit stage
) {
Throwable result = failure;
try {
lease.close();
} catch (Throwable closeFailure) {
result = appendFailure(result, closeFailure);
}
try {
if (route != null) {
route.close();
}
} catch (Throwable closeFailure) {
result = appendFailure(result, closeFailure);
}
try {
stage.close();
} catch (Throwable closeFailure) {
result = appendFailure(result, closeFailure);
}
return result;
}
private static void closeHistoryRoute(
GenerationHistoryRuntimeRouter.RuntimeRoute route,
Throwable failure
) {
if (route == null) {
return;
}
try {
route.close();
} catch (Throwable closeFailure) {
if (failure != closeFailure) {
failure.addSuppressed(closeFailure);
}
}
}
private static Throwable appendFailure(Throwable failure, Throwable closeFailure) {
if (failure == null) {
return closeFailure;
}
if (failure != closeFailure) {
failure.addSuppressed(closeFailure);
}
return failure;
}
private static boolean isCancellationFailure(Throwable failure) {
Throwable current = failure;
while (current instanceof CompletionException && current.getCause() != null) {
@@ -739,6 +875,20 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public WeightedList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
try (GenerationHistoryRuntimeRouter.CoordinateScope route = openHistoryCoordinateScope(
blockposition.getX(), blockposition.getZ(), "bukkit_nms_mob_spawns");
GenerationSessionLease lease = requireGenerationLease("bukkit_nms_mob_spawns");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
return getMobsAtWithActiveRuntime(holder, structuremanager, enumcreaturetype, blockposition);
}
}
private WeightedList<MobSpawnSettings.SpawnerData> getMobsAtWithActiveRuntime(
Holder<Biome> holder,
StructureManager structuremanager,
MobCategory enumcreaturetype,
BlockPos blockposition
) {
Holder<Biome> vanillaSpawnBiome = customBiomeSource.getVanillaSpawnBiome(holder);
if (vanillaSpawnBiome == null) {
return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition);
@@ -759,18 +909,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return explicitSpawns;
}
IrisDimension spawnDimension = engine.getDimension();
int spawnRuntimeId = engine.getCacheID();
if (spawnTableDimension != spawnDimension || spawnTableRuntimeId != spawnRuntimeId) {
synchronized (mergedSpawnTables) {
if (spawnTableDimension != spawnDimension || spawnTableRuntimeId != spawnRuntimeId) {
mergedSpawnTables.clear();
spawnTableDimension = spawnDimension;
spawnTableRuntimeId = spawnRuntimeId;
}
}
}
SpawnTableKey key = new SpawnTableKey(holder.value(), enumcreaturetype);
SpawnTableKey key = new SpawnTableKey(spawnRuntimeId, holder.value(), enumcreaturetype);
return mergedSpawnTables.computeIfAbsent(key, ignored -> mergeSpawnTables(vanillaSpawns, explicitSpawns));
}
@@ -786,19 +926,26 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
ChunkPos chunkPos = ichunkaccess.getPos();
try (BukkitChunkGenerator.GenerationStagePermit stage = requireGenerationStage("bukkit_nms_biome_decoration");
GenerationHistoryRuntimeRouter.RuntimeRoute route = openHistoryRoute(
chunkPos.x(), chunkPos.z(), "bukkit_nms_biome_decoration");
GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope runtimeScope = openHistoryRuntimeScope(route);
GenerationSessionLease lease = requireGenerationLease("bukkit_nms_biome_decoration");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
// Bind-time equivalent for Bukkit: the table is built on the first decorated chunk, which is where a
// feature-order cycle is reported once and degraded to features-off.
importedFeatures.prepare(generatoraccessseed);
addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
placeVanillaStructures(generatoraccessseed, ichunkaccess, structuremanager);
// Vanilla's placed-feature pass, on THIS thread. The delegate is still called with
// addVanillaDecorations=false below, so the vanilla half never runs twice. Inert unless the
// dimension set importedFeatures.enabled.
importedFeatures.run(generatoraccessseed, ichunkaccess, this);
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false);
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
if (allowsRoutedDiscreteGeneration(ichunkaccess, ChunkStatus.FEATURES)) {
// Bind-time equivalent for Bukkit: the table is built on the first decorated chunk, which is where a
// feature-order cycle is reported once and degraded to features-off.
importedFeatures.prepare(generatoraccessseed);
addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
placeVanillaStructures(generatoraccessseed, ichunkaccess, structuremanager);
// Vanilla's placed-feature pass, on THIS thread. The delegate is still called with
// addVanillaDecorations=false below, so the vanilla half never runs twice. Inert unless the
// dimension set importedFeatures.enabled.
importedFeatures.run(generatoraccessseed, ichunkaccess, this);
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false);
}
claimGeneratedSemantics(route, "bukkit_nms_biome_decoration");
}
}
@@ -850,6 +997,14 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
List<StructureStart> starts = structureManager.startsForStructure(sectionPos, structure);
List<NativePlacement> resolvedPlacements = new ArrayList<>(starts.size());
for (StructureStart start : starts) {
BoundingBox footprint = start.getBoundingBox();
if (!engine.getComplex().allowsNewGenerationFootprint(
footprint.minX(),
footprint.minZ(),
footprint.maxX(),
footprint.maxZ())) {
continue;
}
NativeStructureOwnershipRecord ownership =
NativeStructureOwnershipRecovery.resolve(
engine, world.getLevel(), structureId, structure, start);
@@ -1001,13 +1156,14 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
private List<List<Structure>> structuresByStep(Registry<Structure> registry) {
int runtimeId = engine.getCacheID();
StructureStepCache cached = structureStepCache;
if (cached != null && cached.registry() == registry) {
if (cached != null && cached.runtimeId() == runtimeId && cached.registry() == registry) {
return cached.structures();
}
synchronized (this) {
cached = structureStepCache;
if (cached != null && cached.registry() == registry) {
if (cached != null && cached.runtimeId() == runtimeId && cached.registry() == registry) {
return cached.structures();
}
int steps = GenerationStep.Decoration.values().length;
@@ -1022,7 +1178,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
grouped.set(step, List.copyOf(grouped.get(step)));
}
List<List<Structure>> resolved = List.copyOf(grouped);
structureStepCache = new StructureStepCache(registry, resolved);
structureStepCache = new StructureStepCache(runtimeId, registry, resolved);
return resolved;
}
}
@@ -1054,7 +1210,10 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) {
try (GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_nms_heightmaps");
ChunkPos chunkPos = chunkAccess.getPos();
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope = openHistoryCoordinateScope(
chunkPos.getMinBlockX(), chunkPos.getMinBlockZ(), "bukkit_nms_heightmaps");
GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_nms_heightmaps");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSectionY());
BlockPos blockPos = sectionPos.origin();
@@ -1085,22 +1244,40 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void spawnOriginalMobs(WorldGenRegion region) {
ChunkPos center = region.getCenter();
Holder<Biome> visibleBiome;
if (engine.getDimensionStackContext() == null) {
visibleBiome = region.getBiome(center.getWorldPosition().atY(region.getMaxY()));
} else {
visibleBiome = customBiomeSource.getVisibleSurfaceBiome(
center.getMinBlockX() + 8,
center.getMinBlockZ() + 8);
if (visibleBiome == null) {
visibleBiome = region.getBiome(center.getWorldPosition().atY(region.getMaxY()));
try (BukkitChunkGenerator.GenerationStagePermit stage = requireGenerationStage("bukkit_nms_spawn_original_mobs");
GenerationHistoryRuntimeRouter.RuntimeRoute route = openHistoryRoute(
center.x(), center.z(), "bukkit_nms_spawn_original_mobs");
GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope runtimeScope = openHistoryRuntimeScope(route);
GenerationSessionLease lease = requireGenerationLease("bukkit_nms_spawn_original_mobs");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
if (!allowsRoutedDiscreteGeneration(region.getChunk(center.x(), center.z()), ChunkStatus.SPAWN)) {
return;
}
Holder<Biome> visibleBiome;
if (engine.getDimensionStackContext() == null) {
visibleBiome = region.getBiome(center.getWorldPosition().atY(region.getMaxY()));
} else {
visibleBiome = customBiomeSource.getVisibleSurfaceBiome(
center.getMinBlockX() + 8,
center.getMinBlockZ() + 8);
if (visibleBiome == null) {
visibleBiome = region.getBiome(center.getWorldPosition().atY(region.getMaxY()));
}
}
Holder<Biome> vanillaBiome = customBiomeSource.getVanillaSpawnBiome(visibleBiome);
WorldgenRandom random = new WorldgenRandom(new LegacyRandomSource(RandomSupport.generateUniqueSeed()));
random.setDecorationSeed(region.getSeed(), center.getMinBlockX(), center.getMinBlockZ());
NaturalSpawner.spawnMobsForChunkGeneration(
region, vanillaBiome == null ? visibleBiome : vanillaBiome, center, random);
}
Holder<Biome> vanillaBiome = customBiomeSource.getVanillaSpawnBiome(visibleBiome);
WorldgenRandom random = new WorldgenRandom(new LegacyRandomSource(RandomSupport.generateUniqueSeed()));
random.setDecorationSeed(region.getSeed(), center.getMinBlockX(), center.getMinBlockZ());
NaturalSpawner.spawnMobsForChunkGeneration(
region, vanillaBiome == null ? visibleBiome : vanillaBiome, center, random);
}
private boolean allowsRoutedDiscreteGeneration(ChunkAccess chunk, ChunkStatus stage) {
if (chunk.getPersistedStatus().isOrAfter(stage)) {
return false;
}
ChunkPos chunkPos = chunk.getPos();
return engine.getComplex().allowsNewGenerationChunk(chunkPos.x(), chunkPos.z());
}
private static WeightedList<MobSpawnSettings.SpawnerData> mergeSpawnTables(
@@ -1133,7 +1310,9 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
try (GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_nms_base_height");
try (GenerationHistoryRuntimeRouter.CoordinateScope route = openHistoryCoordinateScope(
i, j, "bukkit_nms_base_height");
GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_nms_base_height");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
boolean ignoreFluid = !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState());
int height = engine.getDimensionStackContext() == null
@@ -1148,7 +1327,9 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
try (GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_nms_base_column");
try (GenerationHistoryRuntimeRouter.CoordinateScope route = openHistoryCoordinateScope(
i, j, "bukkit_nms_base_column");
GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_nms_base_column");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
boolean dimensionStack = engine.getDimensionStackContext() != null;
int block = dimensionStack
@@ -1181,6 +1362,69 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
}
private GenerationHistoryRuntimeRouter.RuntimeRoute openHistoryRoute(
int chunkX,
int chunkZ,
String operation
) {
if (!(engine instanceof IrisEngine irisEngine)) {
return null;
}
GenerationHistoryRuntimeRouter router = irisEngine.getGenerationHistoryRuntimeRouter().orElse(null);
if (router == null) {
if (platformGenerator != null && platformGenerator.getGenerationHistory() != null) {
throw new IllegalStateException("Iris " + operation
+ " has no generation-history runtime router.");
}
return null;
}
try {
return router.openRoute(chunkX, chunkZ);
} catch (IOException failure) {
throw new IllegalStateException("Iris " + operation
+ " could not route generation history for chunk "
+ chunkX + "," + chunkZ + ".", failure);
}
}
private GenerationHistoryRuntimeRouter.CoordinateScope openHistoryCoordinateScope(
int blockX,
int blockZ,
String operation
) {
if (!(engine instanceof IrisEngine irisEngine)) {
return null;
}
try {
return irisEngine.openGenerationHistoryCoordinateScope(blockX, blockZ);
} catch (IOException failure) {
throw new IllegalStateException("Iris " + operation
+ " could not route generation history at "
+ blockX + "," + blockZ + ".", failure);
}
}
private static GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope openHistoryRuntimeScope(
GenerationHistoryRuntimeRouter.RuntimeRoute route
) {
return route == null ? null : route.openRuntimeScope();
}
private static void claimGeneratedSemantics(
GenerationHistoryRuntimeRouter.RuntimeRoute route,
String operation
) {
if (route == null) {
return;
}
try {
route.claimGeneratedSemantics();
} catch (IOException failure) {
throw new IllegalStateException("Iris " + operation
+ " could not durably claim generated chunk semantics.", failure);
}
}
private BukkitChunkGenerator.GenerationStagePermit requireGenerationStage(String operation) {
return platformGenerator == null
? BukkitChunkGenerator.GenerationStagePermit.noop()
@@ -1252,13 +1496,14 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
}
private record SpawnTableKey(Biome biome, MobCategory category) {
private record SpawnTableKey(int runtimeId, Biome biome, MobCategory category) {
}
private record ReachableStructureCache(IrisDimension dimension, int runtimeId, Set<String> keys) {
}
private record StructureStepCache(Registry<Structure> registry, List<List<Structure>> structures) {
private record StructureStepCache(int runtimeId, Registry<Structure> registry,
List<List<Structure>> structures) {
}
record StudioStructureState(
@@ -33,6 +33,7 @@ import art.arcane.iris.util.common.format.C;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.iris.util.project.hunk.view.ChunkDataHunkHolder;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.spi.PlatformGenerationRegistry;
import art.arcane.iris.util.common.data.IrisCustomData;
import art.arcane.volmlib.util.json.JSONObject;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
@@ -57,6 +58,9 @@ import it.unimi.dsi.fastutil.shorts.ShortList;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.ResettableClassFileTransformer;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.utility.JavaModule;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;
import net.minecraft.core.BlockPos;
@@ -169,7 +173,6 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -181,11 +184,22 @@ public class NMSBinding implements INMSBinding {
private final AtomicBoolean injected = new AtomicBoolean();
private volatile ResettableClassFileTransformer levelStorageAccessTransformer;
private volatile ResettableClassFileTransformer serverLevelTransformer;
private volatile ResettableClassFileTransformer pluginClassLoaderTransformer;
private boolean pluginClassLoaderCloseDeferred;
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
@Override
public PlatformGenerationRegistry generationRegistry() {
return new NmsGenerationRegistry(
this::registry,
"bukkit-generation-registry-v1",
"bukkit-v26_2_R1-generated-registry-json-v1"
);
}
private static Object getFor(Class<?> type, Object source) {
Object o = fieldFor(type, source);
@@ -1544,6 +1558,23 @@ public class NMSBinding implements INMSBinding {
}
try {
IrisLogging.info("Injecting Bukkit");
Agent.requireClassLoaderCloseDeferral();
Class<?> loaderCloseType = getClass().getClassLoader().getClass()
.getMethod("close").getDeclaringClass();
PluginClassLoaderInjectionListener loaderListener = new PluginClassLoaderInjectionListener();
pluginClassLoaderTransformer = new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(AgentBuilder.RedefinitionStrategy.Listener.ErrorEscalating.FAIL_FAST)
.with(loaderListener)
.type(ElementMatchers.is(loaderCloseType))
.transform((builder, typeDescription, classLoader, module, protectionDomain) ->
builder.visit(Advice.to(PluginClassLoaderCloseAdvice.class)
.on(ElementMatchers.named("close")
.and(ElementMatchers.takesArguments(0))
.and(ElementMatchers.returns(void.class)))))
.installOn(Agent.getInstrumentation());
loaderListener.requireInstalled();
levelStorageAccessTransformer = new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
@@ -1579,18 +1610,22 @@ public class NMSBinding implements INMSBinding {
IrisLogging.reportError(C.RED + "Failed to inject Bukkit", e);
ResettableClassFileTransformer partialServerLevel = serverLevelTransformer;
ResettableClassFileTransformer partialStorageAccess = levelStorageAccessTransformer;
ResettableClassFileTransformer partialPluginClassLoader = pluginClassLoaderTransformer;
serverLevelTransformer = null;
levelStorageAccessTransformer = null;
pluginClassLoaderTransformer = null;
for (ResettableClassFileTransformer partial : new ResettableClassFileTransformer[]{
partialServerLevel,
partialStorageAccess
partialStorageAccess,
partialPluginClassLoader
}) {
if (partial == null) {
continue;
}
try {
partial.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);
} catch (Throwable ignored) {
} catch (Throwable cleanupFailure) {
IrisLogging.reportError("Failed to remove partial Bukkit lifecycle injection", cleanupFailure);
}
}
return false;
@@ -1637,16 +1672,52 @@ public class NMSBinding implements INMSBinding {
}
@Override
public boolean awaitServerShutdownBoundary(long timeout, TimeUnit unit) {
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getHandle().getServer();
return ServerShutdownBoundary.await(
public boolean isServerStopping() {
return ((CraftServer) Bukkit.getServer()).getServer().hasStopped();
}
@Override
public ServerShutdownBoundary createServerShutdownBoundary() {
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
return new ServerShutdownBoundary(
() -> server.hasFullyShutdown,
server.getRunningThread(),
timeout,
unit
server.getRunningThread()
);
}
@Override
public void deferPluginClassLoaderClose() {
synchronized (injected) {
if (pluginClassLoaderTransformer == null || pluginClassLoaderCloseDeferred) {
return;
}
Agent.retainClassLoader(getClass().getClassLoader());
pluginClassLoaderCloseDeferred = true;
}
}
@Override
public void releasePluginClassLoaderClose() {
ResettableClassFileTransformer transformer;
boolean releaseLoader;
synchronized (injected) {
transformer = pluginClassLoaderTransformer;
pluginClassLoaderTransformer = null;
releaseLoader = pluginClassLoaderCloseDeferred;
pluginClassLoaderCloseDeferred = false;
}
try {
if (transformer != null
&& !transformer.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)) {
throw new IllegalStateException("Failed to remove Iris plugin class loader lifecycle injection");
}
} finally {
if (releaseLoader) {
Agent.releaseClassLoader(getClass().getClassLoader());
}
}
}
@Override
public KMap<Material, List<BlockProperty>> getBlockProperties() {
KMap<Material, List<BlockProperty>> states = new KMap<>();
@@ -1691,6 +1762,39 @@ public class NMSBinding implements INMSBinding {
return new FlatLevelSource(settings);
}
private static final class PluginClassLoaderInjectionListener extends AgentBuilder.Listener.Adapter {
private volatile boolean transformed;
private volatile Throwable failure;
@Override
public void onTransformation(TypeDescription type, ClassLoader loader, JavaModule module,
boolean loaded, DynamicType dynamicType) {
transformed = true;
}
@Override
public void onError(String typeName, ClassLoader loader, JavaModule module,
boolean loaded, Throwable throwable) {
failure = throwable;
}
private void requireInstalled() {
if (!transformed || failure != null) {
throw new IllegalStateException("Iris plugin class loader lifecycle injection failed", failure);
}
}
}
private static class PluginClassLoaderCloseAdvice {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean enter(@Advice.This ClassLoader loader) throws ReflectiveOperationException {
Class<?> installer = Class.forName(
"art.arcane.iris.util.project.agent.Installer", true, ClassLoader.getSystemClassLoader());
return Boolean.TRUE.equals(installer.getMethod("deferClassLoaderClose", ClassLoader.class)
.invoke(null, loader));
}
}
private static class ChunkAccessAdvice {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean enter(@Advice.This ChunkAccess access, @Advice.Argument(1) int index) {
@@ -0,0 +1,182 @@
package art.arcane.iris.core.nms.v26_2_R1;
import art.arcane.iris.spi.PlatformGenerationRegistry;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.dimension.DimensionType;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Supplier;
final class NmsGenerationRegistry implements PlatformGenerationRegistry {
private static final String BIOME_REGISTRY = "minecraft:worldgen/biome";
private static final String STRUCTURE_REGISTRY = "minecraft:worldgen/structure";
private static final String DIMENSION_TYPE_REGISTRY = "minecraft:dimension_type";
private static final String BLOCK_REGISTRY = "minecraft:block";
private static final String ENTITY_TYPE_REGISTRY = "minecraft:entity_type";
private final Supplier<RegistryAccess> registryAccess;
private final String runtimeIdentity;
private final String rendererIdentity;
NmsGenerationRegistry(
Supplier<RegistryAccess> registryAccess,
String runtimeIdentity,
String rendererIdentity
) {
this.registryAccess = Objects.requireNonNull(registryAccess, "registryAccess");
this.runtimeIdentity = requireText(runtimeIdentity, "runtimeIdentity");
this.rendererIdentity = requireText(rendererIdentity, "rendererIdentity");
}
@Override
public String runtimeIdentity() {
return runtimeIdentity;
}
@Override
public String generatedDefinitionRendererIdentity() {
return rendererIdentity;
}
@Override
public String customBiomeResourceKey(String identitySha256) {
return PlatformGenerationRegistry.contentAddressedCustomBiomeResourceKey(identitySha256);
}
@Override
public List<String> legacyCustomBiomeResourceKeys(
String packName,
String dimensionKey,
String customBiomeId
) {
return List.of(requireText(dimensionKey, "dimensionKey").toLowerCase(Locale.ROOT)
+ ":" + requireText(customBiomeId, "customBiomeId").toLowerCase(Locale.ROOT));
}
@Override
public String dimensionTypeResourceKey(String packName, String dimensionKey, String dimensionTypeKey) {
return "iris:" + requireText(dimensionTypeKey, "dimensionTypeKey").toLowerCase(Locale.ROOT);
}
@Override
public Definition definition(String registryKey, String resourceKey) {
RegistryAccess access = Objects.requireNonNull(registryAccess.get(), "Minecraft registry access");
return switch (registryKey) {
case BIOME_REGISTRY -> presence(access, Registries.BIOME, registryKey, resourceKey);
case STRUCTURE_REGISTRY -> presence(access, Registries.STRUCTURE, registryKey, resourceKey);
case DIMENSION_TYPE_REGISTRY -> presence(
access,
Registries.DIMENSION_TYPE,
registryKey,
resourceKey
);
case BLOCK_REGISTRY -> presence(access, Registries.BLOCK, registryKey, resourceKey);
case ENTITY_TYPE_REGISTRY -> presence(access, Registries.ENTITY_TYPE, registryKey, resourceKey);
default -> throw new IllegalArgumentException("Unsupported generation registry: " + registryKey);
};
}
@Override
public Definition canonicalDefinition(String registryKey, String resourceKey, String sourceJson) {
RegistryAccess access = Objects.requireNonNull(registryAccess.get(), "Minecraft registry access");
return switch (registryKey) {
case BIOME_REGISTRY -> canonicalize(access, Biome.DIRECT_CODEC, sourceJson);
case DIMENSION_TYPE_REGISTRY -> canonicalize(access, DimensionType.DIRECT_CODEC, sourceJson);
default -> throw new IllegalArgumentException(
"Registry does not accept generated JSON definitions: " + registryKey
);
};
}
@Override
public Definition generatedDefinition(String registryKey, String resourceKey) {
RegistryAccess access = Objects.requireNonNull(registryAccess.get(), "Minecraft registry access");
return switch (registryKey) {
case BIOME_REGISTRY -> exactRegistered(
access,
Registries.BIOME,
Biome.DIRECT_CODEC,
resourceKey
);
case DIMENSION_TYPE_REGISTRY -> exactRegistered(
access,
Registries.DIMENSION_TYPE,
DimensionType.DIRECT_CODEC,
resourceKey
);
default -> throw new IllegalArgumentException(
"Registry does not contain Iris-generated definitions: " + registryKey
);
};
}
private static <T> Definition presence(
RegistryAccess access,
ResourceKey<? extends Registry<T>> registryKey,
String registryResourceKey,
String resourceKey
) {
Registry<T> registry = access.lookupOrThrow(registryKey);
Identifier identifier = requireIdentifier(resourceKey);
T value = registry.getValue(identifier);
if (value == null) {
throw new IllegalStateException("Missing registry definition " + registryKey.identifier()
+ " / " + resourceKey + ".");
}
return Definition.resourceIdentity(registryResourceKey, resourceKey);
}
private static <T> Definition canonicalize(RegistryAccess access, Codec<T> codec, String sourceJson) {
RegistryOps<JsonElement> ops = RegistryOps.create(JsonOps.INSTANCE, access);
JsonElement source = JsonParser.parseString(Objects.requireNonNull(sourceJson, "sourceJson"));
T decoded = codec.parse(ops, source).getOrThrow(IllegalArgumentException::new);
JsonElement encoded = codec.encodeStart(ops, decoded).getOrThrow(IllegalStateException::new);
return Definition.exactJson(encoded.toString());
}
private static <T> Definition exactRegistered(
RegistryAccess access,
ResourceKey<? extends Registry<T>> registryKey,
Codec<T> codec,
String resourceKey
) {
Registry<T> registry = access.lookupOrThrow(registryKey);
Identifier identifier = requireIdentifier(resourceKey);
T value = registry.getValue(identifier);
if (value == null) {
throw new IllegalStateException("Missing generated registry definition "
+ registryKey.identifier() + " / " + resourceKey + ".");
}
RegistryOps<JsonElement> ops = RegistryOps.create(JsonOps.INSTANCE, access);
JsonElement encoded = codec.encodeStart(ops, value).getOrThrow(IllegalStateException::new);
return Definition.exactJson(encoded.toString());
}
private static Identifier requireIdentifier(String resourceKey) {
Identifier identifier = Identifier.tryParse(resourceKey);
if (identifier == null) {
throw new IllegalArgumentException("Invalid registry resource key: " + resourceKey);
}
return identifier;
}
private static String requireText(String value, String label) {
String required = Objects.requireNonNull(value, label).trim();
if (required.isEmpty()) {
throw new IllegalArgumentException(label + " must not be blank.");
}
return required;
}
}
@@ -42,10 +42,10 @@ public class CustomBiomeSourceStructureContractTest {
assertTrue(source.contains("tryAcquireGenerationLease(\"bukkit_spawn_biome\")"));
assertFalse(source.contains("Iris spawn biome lookup was rejected during an engine transition"));
assertFalse(source.contains("Iris spawn biome lookup has no active engine runtime"));
assertTrue(source.contains("vanillaSpawnBiomes.get(biome.value())"));
assertTrue(source.contains("tryAcquireGenerationLease(\"bukkit_structure_biome\")"));
assertTrue(source.contains("runtimeBiomeState().vanillaSpawnBiomes().get(biome.value())"));
assertTrue(source.contains("requireGenerationLease(\n \"bukkit_structure_biome\""));
assertTrue(source.contains("tryAcquireGenerationLease(\"bukkit_biomes_within\")"));
assertTrue(source.contains("tryAcquireGenerationLease(\"bukkit_visible_biome\")"));
assertTrue(source.contains("requireGenerationLease(\n \"bukkit_visible_biome\""));
assertTrue(source.contains("if (engine.isClosed())"));
assertTrue(source.contains("engine.isClosing() || e.isExpectedTeardown()"));
assertTrue(source.contains("catch (GenerationSessionException e)"));
@@ -140,7 +140,7 @@ public class CustomBiomeSourceStructureContractTest {
assertTrue(lookupEnd > lookupStart);
String lookup = source.substring(lookupStart, lookupEnd);
assertTrue(lookup.contains("tryAcquireGenerationLease(\"bukkit_visible_biome\")"));
assertTrue(lookup.contains("requireGenerationLease(\n \"bukkit_visible_biome\""));
assertTrue(lookup.contains("prepareVisibleBiomeBatch()"));
assertTrue(lookup.contains("getVisibleNoiseBiomeWithActiveGenerationLease(x, y, z, sampler)"));
assertTrue(lookup.indexOf("prepareVisibleBiomeBatch()")
@@ -0,0 +1,164 @@
package art.arcane.iris.core.nms.v26_2_R1;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public final class GenerationHistoryRoutingContractTest {
@Test
public void everyChunkStageScopesItsHistoryRuntimeBeforeSessionAdmission() throws IOException {
String source = chunkGeneratorSource();
assertRoutedStage(source, "public void createStructures(", "private void adjustGeneratedStructures");
assertRoutedStage(source, "public void createReferences(", "public CompletableFuture<ChunkAccess> createBiomes");
assertRoutedStage(source, "public CompletableFuture<ChunkAccess> createBiomes", "public void buildSurface");
assertRoutedStage(source, "public void buildSurface(", "public void applyCarvers(");
assertRoutedStage(source, "public void applyCarvers(", "public CompletableFuture<ChunkAccess> fillFromNoise");
assertRoutedStage(source, "public void spawnOriginalMobs(", "private boolean allowsRoutedDiscreteGeneration(");
assertRoutedStage(
source,
"public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla)",
"public BiomeGenerationSettings getBiomeGenerationSettings");
String baseHeight = method(source, "public int getBaseHeight(", "public NoiseColumn getBaseColumn(");
String baseColumn = method(source, "public NoiseColumn getBaseColumn(", "private GenerationSessionLease requireGenerationLease");
assertBefore(baseHeight, "openHistoryCoordinateScope(", "engine.acquireGenerationLease(");
assertBefore(baseColumn, "openHistoryCoordinateScope(", "engine.acquireGenerationLease(");
}
@Test
public void asyncNoiseKeepsRouteUntilCompletionAndReopensThreadLocalScopes() throws IOException {
String source = chunkGeneratorSource();
String noise = method(
source,
"public CompletableFuture<ChunkAccess> fillFromNoise",
"private static boolean isCancellationFailure");
assertBefore(noise, "requireNoiseGenerationStage(", "openHistoryRoute(");
assertBefore(noise, "openHistoryRoute(", "requireGenerationLease(");
assertTrue(count(noise, "openHistoryRuntimeScope(route)") >= 2);
assertBefore(noise, ".thenApply(filled ->", "pipeline.whenComplete(");
assertTrue(noise.contains("closeNoisePipelineResources(\n failure, lease, route, stage)"));
}
@Test
public void transitionBandSuppressesDiscreteGenerationAndCrossingStarts() throws IOException {
String source = chunkGeneratorSource();
String structures = method(source, "public void createStructures(", "private void adjustGeneratedStructures");
String adjustment = method(source, "private void adjustGeneratedStructures", "public ChunkGeneratorStructureState createState");
String decoration = method(
source,
"public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla)",
"public BiomeGenerationSettings getBiomeGenerationSettings");
assertTrue(structures.contains("allowsRoutedDiscreteGeneration(access, ChunkStatus.STRUCTURE_STARTS)"));
assertTrue(decoration.contains("allowsRoutedDiscreteGeneration(ichunkaccess, ChunkStatus.FEATURES)"));
String discrete = method(source, "private boolean allowsRoutedDiscreteGeneration(",
"private static WeightedList<MobSpawnSettings.SpawnerData> mergeSpawnTables(");
assertTrue(discrete.contains("chunk.getPersistedStatus().isOrAfter(stage)"));
assertTrue(discrete.contains("engine.getComplex().allowsNewGenerationChunk(chunkPos.x(), chunkPos.z())"));
assertBefore(decoration, "delegate.applyBiomeDecoration(", "claimGeneratedSemantics(route,");
assertTrue(adjustment.contains("start.getBoundingBox()"));
assertTrue(adjustment.contains("allowsNewGenerationFootprint("));
assertTrue(adjustment.contains("NativeStructureOwnershipStore.discard("));
}
@Test
public void biomeCachesAndVisibleHistoricalBiomesAreRuntimeAware() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")))
.replace("\r\n", "\n");
String visibleResolution = method(
source,
"private Holder<Biome> resolveVisibleBiomeHolder(",
"private Holder<Biome> resolveCustomHolder(");
String activeBatch = method(
source,
"Holder<Biome> getVisibleNoiseBiomeWithActiveGenerationLease(\n int x,",
"private GenerationSessionLease tryAcquireGenerationLease");
assertTrue(source.contains("record RuntimeNoiseKey(int runtimeId, long coordinateKey)"));
assertTrue(source.contains("record RuntimeColumnKey(int runtimeId, long coordinateKey)"));
assertTrue(source.contains("ConcurrentHashMap<Integer, RuntimeBiomeState> runtimeBiomeStates"));
assertTrue(source.contains("addGenerationRuntimeRetirementListener(this::evictRuntimeCaches)"));
assertTrue(source.contains("runtimeBiomeStates.remove(runtimeId)"));
assertTrue(source.contains("noiseBiomeCache.keySet().removeIf(key -> key.runtimeId() == runtimeId)"));
assertBefore(visibleResolution, "historicalPhysicalBiomeKeyAt(", "resolveBiomeResolution(");
assertFalse(activeBatch.contains("openHistoryCoordinateScope("));
String chunkGenerator = chunkGeneratorSource();
assertTrue(chunkGenerator.contains("addGenerationRuntimeRetirementListener(this::evictRuntimeCaches)"));
assertTrue(chunkGenerator.contains("mergedSpawnTables.keySet().removeIf(key -> key.runtimeId() == runtimeId)"));
}
@Test
public void importedFeatureTablesAreRuntimeScopedAndRetiredCentrally() throws IOException {
String stage = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource"))
.resolveSibling("ImportedFeatureStage.java"))
.replace("\r\n", "\n");
String generator = chunkGeneratorSource();
assertTrue(stage.contains("ConcurrentHashMap<Integer, RuntimeFeatureState> runtimeStates"));
assertTrue(stage.contains("runtimeStates.get(engine.getCacheID())"));
assertTrue(stage.contains("runtimeStates.remove(runtimeId)"));
assertFalse(stage.contains("volatile FeatureTable featureTable"));
assertFalse(stage.contains("volatile int settledRuntimeId"));
assertTrue(generator.contains("importedFeatures.evictRuntime(runtimeId)"));
}
@Test
public void nativeReferencesResolvePolicyInTheOriginRuntime() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nativeStructureVolumeIndexSource"))
.resolveSibling("NativeStructureReferenceRepair.java"))
.replace("\r\n", "\n");
String references = method(source, "public static void createReferences(",
"private static GenerationHistoryRuntimeRouter.CoordinateScope openOriginRuntimeScope(");
assertBefore(references, "openOriginRuntimeScope(engine, originChunkX, originChunkZ)", "scanStart(");
assertBefore(references, "scanStart(", "isTargetRelevant(");
assertTrue(source.contains("irisEngine.openGenerationHistoryCoordinateScope(chunkX << 4, chunkZ << 4)"));
assertFalse(references.contains("allowsNewGenerationFootprint("));
}
private static void assertRoutedStage(String source, String start, String end) {
String stage = method(source, start, end);
assertBefore(stage, "requireGenerationStage(", "openHistoryRoute(");
assertBefore(stage, "openHistoryRoute(", "requireGenerationLease(");
assertBefore(stage, "openHistoryRuntimeScope(route)", "requireGenerationLease(");
}
private static String chunkGeneratorSource() throws IOException {
return Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")))
.replace("\r\n", "\n");
}
private static String method(String source, String startToken, String endToken) {
int start = source.indexOf(startToken);
int end = source.indexOf(endToken, start);
assertTrue("Missing source contract token: " + startToken, start >= 0);
assertTrue("Missing source contract token: " + endToken, end > start);
return source.substring(start, end);
}
private static void assertBefore(String source, String first, String second) {
int firstIndex = source.indexOf(first);
int secondIndex = source.indexOf(second);
assertTrue("Missing source contract token: " + first, firstIndex >= 0);
assertTrue("Missing source contract token: " + second, secondIndex >= 0);
assertTrue(first + " must occur before " + second, firstIndex < secondIndex);
}
private static int count(String source, String token) {
int count = 0;
int index = 0;
while ((index = source.indexOf(token, index)) >= 0) {
count++;
index += token.length();
}
return count;
}
}
@@ -295,26 +295,29 @@ public class IrisChunkGeneratorFailureContractTest {
assertBefore(productionFill,
"BukkitChunkGenerator.GenerationStagePermit stage = requireNoiseGenerationStage(",
"GenerationHistoryRuntimeRouter.RuntimeRoute route");
assertBefore(productionFill,
"GenerationHistoryRuntimeRouter.RuntimeRoute route",
"GenerationSessionLease lease");
assertBefore(productionFill,
"requireNoiseGenerationStage(",
"\"bukkit_nms_chunk_pipeline\")");
assertBefore(productionFill,
"lease = requireGenerationLease(\"bukkit_nms_chunk_pipeline\")",
".fillFromNoise(blender, randomstate, structuremanager, ichunkaccess)");
"delegate.fillFromNoise(");
assertTrue(productionFill.contains("IrisContext.open(engine, lease.sessionId(), null)"));
assertTrue(productionFill.contains("openHistoryRuntimeScope(route)"));
assertBefore(productionFill, "primeWorldgenHeightmaps(filled)", "pipeline.whenComplete(");
assertTrue(productionFill.contains("CompletableFuture<ChunkAccess> completion = new CompletableFuture<>()"));
assertBefore(completion, "boolean cancelled = isCancellationFailure(failure);", "lease.close();");
assertBefore(completion, "lease.close();", "stage.close();");
assertBefore(completion, "stage.close();", "completion.complete(filled)");
assertBefore(completion, "stage.close();", "completion.cancel(false)");
assertBefore(completion, "stage.close();", "completion.completeExceptionally(failure)");
assertBefore(completion, "boolean cancelled = isCancellationFailure(failure);", "closeNoisePipelineResources(");
assertBefore(completion, "closeNoisePipelineResources(", "completion.complete(filled)");
assertBefore(completion, "closeNoisePipelineResources(", "completion.cancel(false)");
assertBefore(completion, "closeNoisePipelineResources(", "completion.completeExceptionally(completionFailure)");
assertTrue(completion.contains("else if (cancelled)"));
assertFalse(completion.contains("pipeline.isCancelled()"));
assertFalse(completion.contains("finally"));
assertTrue(productionFill.contains("catch (RuntimeException | Error failure)"));
assertTrue(productionFill.contains("lease.close();\n stage.close();\n throw failure;"));
assertTrue(productionFill.contains("lease.close();\n closeHistoryRoute(route, failure);\n stage.close();\n throw failure;"));
assertTrue(productionFill.contains("return completion;"));
assertFalse(productionFill.contains("return pipeline;"));
assertFalse(productionFill.contains("pipeline.cancel("));
@@ -422,6 +425,9 @@ public class IrisChunkGeneratorFailureContractTest {
String structures = method(source, "public void createStructures(", "private void adjustGeneratedStructures");
String references = method(source, "public void createReferences(", "public CompletableFuture<ChunkAccess> createBiomes");
String biomes = method(source, "public CompletableFuture<ChunkAccess> createBiomes", "public void buildSurface");
String surface = method(source, "public void buildSurface(", "public void applyCarvers(");
String carvers = method(source, "public void applyCarvers(", "public CompletableFuture<ChunkAccess> fillFromNoise");
String mobs = method(source, "public void spawnOriginalMobs(", "private boolean allowsRoutedDiscreteGeneration(");
String noise = method(source, "public CompletableFuture<ChunkAccess> fillFromNoise", "private static boolean isCancellationFailure");
String decoration = method(source,
"public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla)",
@@ -430,6 +436,9 @@ public class IrisChunkGeneratorFailureContractTest {
assertStageBeforeLease(structures, "bukkit_nms_create_structures");
assertStageBeforeLease(references, "bukkit_nms_create_references");
assertStageBeforeLease(biomes, "bukkit_nms_create_biomes");
assertStageBeforeLease(surface, "bukkit_nms_build_surface");
assertStageBeforeLease(carvers, "bukkit_nms_apply_carvers");
assertStageBeforeLease(mobs, "bukkit_nms_spawn_original_mobs");
assertBefore(noise,
"requireNoiseGenerationStage(",
"requireGenerationLease(\"bukkit_nms_chunk_pipeline\")");
@@ -437,7 +446,7 @@ public class IrisChunkGeneratorFailureContractTest {
assertBefore(decoration,
"requireGenerationLease(\"bukkit_nms_biome_decoration\")",
"importedFeatures.prepare(generatoraccessseed)");
assertEquals(4, occurrences(source, "requireGenerationStage(\"bukkit_nms_"));
assertEquals(7, occurrences(source, "requireGenerationStage(\"bukkit_nms_"));
assertEquals(2, occurrences(source, "requireNoiseGenerationStage("));
assertFalse(source.contains("GenerationSessionManager"));
}
@@ -6,16 +6,48 @@ import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
public class NMSBindingShutdownBoundaryContractTest {
@Test
public void shutdownClassificationUsesTheServerStopFlagInsteadOfPluginEnablement() throws Exception {
String source = Files.readString(Path.of(System.getProperty("iris.nmsBindingSource")));
String stopping = section(source, "public boolean isServerStopping()", "public ServerShutdownBoundary createServerShutdownBoundary");
assertTrue(stopping.contains("((CraftServer) Bukkit.getServer()).getServer().hasStopped()"));
assertFalse(stopping.contains("isEnabled()"));
assertFalse(stopping.contains("isRunning()"));
}
@Test
public void pluginLoaderGuardUsesSystemAgentIdentityAndIsRemovedAtRelease() throws Exception {
String source = Files.readString(Path.of(System.getProperty("iris.nmsBindingSource")));
String injection = section(source, "public boolean injectBukkit()", "public void ensureServerLevelInjection()");
String guard = section(source, "private static class PluginClassLoaderCloseAdvice", "private static class ChunkAccessAdvice");
String release = section(source, "public void releasePluginClassLoaderClose()", "public KMap<Material, List<BlockProperty>> getBlockProperties");
assertTrue(injection.contains("Agent.requireClassLoaderCloseDeferral()"));
assertTrue(injection.contains("getMethod(\"close\").getDeclaringClass()"));
assertTrue(injection.contains("Advice.to(PluginClassLoaderCloseAdvice.class)"));
assertTrue(guard.contains("skipOn = Advice.OnNonDefaultValue.class"));
assertTrue(guard.contains("ClassLoader.getSystemClassLoader()"));
assertTrue(guard.contains("getMethod(\"deferClassLoaderClose\", ClassLoader.class)"));
assertTrue(guard.contains(".invoke(null, loader)"));
assertFalse(guard.contains("Bukkit.getPluginManager()"));
assertTrue(release.indexOf("transformer.reset(") > release.indexOf("pluginClassLoaderCloseDeferred = false"));
assertTrue(release.indexOf("Agent.releaseClassLoader(") > release.indexOf("transformer.reset("));
}
@Test
public void shutdownBoundaryUsesPaperFullyShutdownStateAndServerThread() throws Exception {
String source = Files.readString(Path.of(System.getProperty("iris.nmsBindingSource"))).replace("\r\n", "\n");
String boundary = section(source, "public boolean awaitServerShutdownBoundary", "public KMap<Material, List<BlockProperty>> getBlockProperties");
String boundary = section(source, "public ServerShutdownBoundary createServerShutdownBoundary", "public KMap<Material, List<BlockProperty>> getBlockProperties");
assertTrue(boundary.contains("ServerShutdownBoundary.await("));
assertTrue(boundary.contains("new ServerShutdownBoundary("));
assertTrue(boundary.contains("server.hasFullyShutdown"));
assertTrue(boundary.contains("server.getRunningThread()"));
assertTrue(boundary.contains("((CraftServer) Bukkit.getServer()).getServer()"));
assertFalse(boundary.contains("getHandle()"));
}
private static String section(String source, String startMarker, String endMarker) {
@@ -19,6 +19,7 @@ import org.junit.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
@@ -80,6 +81,30 @@ public class NativeStructureVolumeIndexTest {
assertEquals(coldResolutions, resolver.resolutions());
}
@Test
public void runtimeBucketsStayIsolatedAndRetirementEvictsOnlyTheirEntries() {
AtomicInteger runtimeId = new AtomicInteger(1);
Engine engine = runtimeEngine(runtimeId);
CountingResolver resolver = new CountingResolver(0, 0);
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting(resolver);
index.resolve(engine, 0, 0, 15, 15);
assertEquals(289, resolver.resolutions());
index.resolve(engine, 0, 0, 15, 15);
assertEquals(289, resolver.resolutions());
runtimeId.set(2);
index.resolve(engine, 0, 0, 15, 15);
assertEquals(578, resolver.resolutions());
index.resolve(engine, 0, 0, 15, 15);
assertEquals(578, resolver.resolutions());
index.evictRuntime(1);
runtimeId.set(1);
index.resolve(engine, 0, 0, 15, 15);
assertEquals(867, resolver.resolutions());
}
@Test
public void independentIndexesResolveIdenticalVolumesFromTheSameSeed() {
NativeStructureVolumeIndex first = NativeStructureVolumeIndex.forTesting(new CountingResolver(0, 0));
@@ -188,12 +213,45 @@ public class NativeStructureVolumeIndexTest {
assertTrue(source.contains("isStructureChunk("));
}
@Test
public void originResolutionRoutesAndCachesByScopedRuntime() throws Exception {
String source = Files.readString(Path.of(System.getProperty("iris.nativeStructureVolumeIndexSource")));
assertTrue(source.contains("openHistoryCoordinateScope(engine, chunkX, chunkZ)"));
assertTrue(source.contains("new RuntimeChunkKey(runtimeId, chunkKey(chunkX, chunkZ))"));
assertTrue(source.contains("addGenerationRuntimeRetirementListener(retirementListener)"));
assertTrue(source.contains("originCache.keySet().removeIf(key -> key.runtimeId() == runtimeId)"));
assertTrue(source.contains("queryCache.keySet().removeIf(key -> key.runtimeId() == runtimeId)"));
}
private static StructureStart swampHut(int chunkX, int chunkZ, int x, int z) {
Structure source = new SwampHutStructure(new Structure.StructureSettings(HolderSet.empty()));
SwampHutPiece piece = new SwampHutPiece(RandomSource.create(17L), x, z);
return new StructureStart(source, new ChunkPos(chunkX, chunkZ), 0, new PiecesContainer(List.of(piece)));
}
private static Engine runtimeEngine(AtomicInteger runtimeId) {
return (Engine) Proxy.newProxyInstance(
Engine.class.getClassLoader(),
new Class<?>[]{Engine.class},
(proxy, method, arguments) -> {
if (method.getName().equals("getCacheID")) {
return runtimeId.get();
}
if (method.getName().equals("hashCode")) {
return System.identityHashCode(proxy);
}
if (method.getName().equals("equals")) {
return proxy == arguments[0];
}
if (method.getName().equals("toString")) {
return "runtime-engine-" + runtimeId.get();
}
throw new UnsupportedOperationException(method.getName());
}
);
}
private static int findDisjointWindow(int chunkX, int chunkZ) {
long sourceMask = NativeStructureVolumeIndex.originWindowStripeMask(chunkX, chunkZ);
for (int candidate = 64; candidate < 16_384; candidate += 17) {