Lock-free scopes and caches

- History coordinate scopes read the router and routing flag lock-free
- Subterrain planner shares bounded plan and chunk candidate caches
- Cave blend roll uses a stateless hash identical to the parallel RNG
- Custom biome resource keys are cached per dimension and biome identity
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-21 19:15:01 -04:00
1 parent e2430500cf
commit 7282a46a26
10 files changed
+395 -81

No files matched your search

@@ -177,7 +177,7 @@ public class IrisEngine implements Engine {
volatile GenerationHistoryRuntimeRouter generationHistoryRuntimeRouter;
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
private boolean generationHistoryRoutingRequired;
private volatile boolean generationHistoryRoutingRequired;
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
volatile EngineTarget publishedTarget;
@@ -1011,13 +1011,7 @@ public class IrisEngine implements Engine {
}
public Optional<GenerationHistoryRuntimeRouter> getGenerationHistoryRuntimeRouter() {
GenerationHistoryRuntimeRouter router = generationHistoryRuntimeRouter;
if (router != null) {
return Optional.of(router);
}
synchronized (generationHistoryRuntimeRouterLock) {
return Optional.ofNullable(generationHistoryRuntimeRouter);
}
return Optional.ofNullable(generationHistoryRuntimeRouter);
}
public GenerationHistoryRuntimeRouter.CoordinateScope openGenerationHistoryCoordinateScope(
@@ -1028,13 +1022,14 @@ public class IrisEngine implements Engine {
if (router != null) {
return router.openCoordinateScope(blockX, blockZ);
}
synchronized (generationHistoryRuntimeRouterLock) {
router = generationHistoryRuntimeRouter;
if (router == null && generationHistoryRoutingRequired) {
throw new IllegalStateException("Iris generation-history runtime router is detached.");
}
if (!generationHistoryRoutingRequired) {
return null;
}
return router == null ? null : router.openCoordinateScope(blockX, blockZ);
router = generationHistoryRuntimeRouter;
if (router == null) {
throw new IllegalStateException("Iris generation-history runtime router is detached.");
}
return router.openCoordinateScope(blockX, blockZ);
}
public boolean hasGenerationRuntimeScope() {
@@ -79,15 +79,20 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
private static final int CAVE_BIOME_BLEND_CENTER_WEIGHT = 4;
private static final int CAVE_BIOME_BLEND_TOTAL_WEIGHT = 8;
private static final int SUBMERGED_FLOOR_SUBSTRATE_DEPTH = 8;
private static final long RANDOM_MULTIPLIER = 0x5DEECE66DL;
private static final long RANDOM_ADDEND = 0xBL;
private static final long RANDOM_MASK = (1L << 48) - 1;
private static final MatterCavern BASIC_CAVERN = new MatterCavern(true, "", (byte) 0);
private final RNG rng;
private final long caveBlendSeed;
private final NativeBlockState AIR = B.getState("CAVE_AIR");
private final NativeBlockState LAVA = B.getState("LAVA");
private final IrisDecorantActuator decorant;
public IrisCarveModifier(Engine engine) {
super(engine, "Carve");
rng = new RNG(getEngine().getSeedManager().getCarve());
caveBlendSeed = getEngine().getSeedManager().getCarve();
rng = new RNG(caveBlendSeed);
decorant = new IrisDecorantActuator(engine);
}
@@ -1233,7 +1238,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
return null;
}
int roll = Math.floorMod(rng.nextParallelRNG(BlockPosition.toLong(x, y, z)).nextInt(), CAVE_BIOME_BLEND_TOTAL_WEIGHT);
int roll = Math.floorMod(caveBlendRandom(caveBlendSeed, x, y, z), CAVE_BIOME_BLEND_TOTAL_WEIGHT);
if (roll < CAVE_BIOME_BLEND_CENTER_WEIGHT) {
return center;
}
@@ -1247,6 +1252,11 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
return neighbor != null ? neighbor : center;
}
static int caveBlendRandom(long seed, int x, int y, int z) {
long scrambled = ((seed + BlockPosition.toLong(x, y, z)) ^ RANDOM_MULTIPLIER) & RANDOM_MASK;
return (int) (((scrambled * RANDOM_MULTIPLIER + RANDOM_ADDEND) & RANDOM_MASK) >>> 16);
}
private IrisBiome sampleCaveBiome(Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache, int x, int y, int z, CaveInputs resolverState) {
long key = BlockPosition.toLong(x, y, z);
IrisBiome cachedBiome = caveBiomeCache.get(key);
@@ -6,14 +6,21 @@ import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.function.Predicate;
public final class SubterrainPlanner {
private static final int PLAN_CACHE_SLOTS = 4096;
private static final int TILE_CACHE_SLOTS = 1024;
private static final Comparator<SubterrainPlan> PLAN_ORDER =
Comparator.comparingInt(SubterrainPlan::priority).reversed().thenComparing(SubterrainPlan::id);
private final List<Definition> definitions;
private final long seed;
private final int minY;
private final int maxYExclusive;
private final ThreadLocal<CandidateTile> sampleTile = new ThreadLocal<>();
private final AtomicReferenceArray<CachedPlan> planCache = new AtomicReferenceArray<>(PLAN_CACHE_SLOTS);
private final AtomicReferenceArray<CandidateTile> tileCache = new AtomicReferenceArray<>(TILE_CACHE_SLOTS);
private final Predicate<SubterrainBounds> allowedBounds;
private final RegionResolver regionResolver;
@@ -70,8 +77,9 @@ public final class SubterrainPlanner {
if (minX > maxX || minZ > maxZ) {
throw new IllegalArgumentException("Subterrain query bounds are inverted");
}
List<SubterrainPlan> result = new ArrayList<>();
for (Definition definition : definitions) {
List<SubterrainPlan> result = null;
for (int definitionIndex = 0; definitionIndex < definitions.size(); definitionIndex++) {
Definition definition = definitions.get(definitionIndex);
long firstX = Math.floorDiv(minX, definition.spacing()) - 1L;
long lastX = Math.floorDiv(maxX, definition.spacing()) + 1L;
long firstZ = Math.floorDiv(minZ, definition.spacing()) - 1L;
@@ -81,14 +89,20 @@ public final class SubterrainPlanner {
}
for (long gridX = firstX; gridX <= lastX; gridX++) {
for (long gridZ = firstZ; gridZ <= lastZ; gridZ++) {
SubterrainPlan plan = create(definition, gridX, gridZ);
SubterrainPlan plan = plan(definitionIndex, definition, gridX, gridZ);
if (plan != null && plan.bounds().intersects(minX, minZ, maxX, maxZ)) {
if (result == null) {
result = new ArrayList<>();
}
result.add(plan);
}
}
}
}
result.sort(Comparator.comparingInt(SubterrainPlan::priority).reversed().thenComparing(SubterrainPlan::id));
if (result == null) {
return List.of();
}
result.sort(PLAN_ORDER);
return List.copyOf(result);
}
@@ -96,20 +110,7 @@ public final class SubterrainPlanner {
if (isEmpty() || y < minY || y >= maxYExclusive) {
return SubterrainCell.OUTSIDE;
}
int chunkX = Math.floorDiv(x, 16);
int chunkZ = Math.floorDiv(z, 16);
CandidateTile tile = sampleTile.get();
if (tile == null || tile.chunkX() != chunkX || tile.chunkZ() != chunkZ) {
long baseX = (long) chunkX * 16;
long baseZ = (long) chunkZ * 16;
tile = new CandidateTile(chunkX, chunkZ, plansForBounds(
(int) Math.max(Integer.MIN_VALUE, baseX - 1),
(int) Math.max(Integer.MIN_VALUE, baseZ - 1),
(int) Math.min(Integer.MAX_VALUE, baseX + 16),
(int) Math.min(Integer.MAX_VALUE, baseZ + 16)));
sampleTile.set(tile);
}
return sample(tile.plans(), x, y, z);
return sample(candidates(Math.floorDiv(x, 16), Math.floorDiv(z, 16)), x, y, z);
}
public SubterrainCell sample(List<SubterrainPlan> candidates, int x, int y, int z) {
@@ -127,10 +128,38 @@ public final class SubterrainPlanner {
return selected;
}
private List<SubterrainPlan> candidates(int chunkX, int chunkZ) {
int slot = (int) mix(((long) chunkX << 32) ^ (chunkZ & 0xffffffffL)) & (TILE_CACHE_SLOTS - 1);
CandidateTile tile = tileCache.getAcquire(slot);
if (tile != null && tile.chunkX() == chunkX && tile.chunkZ() == chunkZ) {
return tile.plans();
}
long baseX = (long) chunkX * 16;
long baseZ = (long) chunkZ * 16;
List<SubterrainPlan> plans = plansForBounds(
(int) Math.max(Integer.MIN_VALUE, baseX - 1),
(int) Math.max(Integer.MIN_VALUE, baseZ - 1),
(int) Math.min(Integer.MAX_VALUE, baseX + 16),
(int) Math.min(Integer.MAX_VALUE, baseZ + 16));
tileCache.setRelease(slot, new CandidateTile(chunkX, chunkZ, plans));
return plans;
}
private SubterrainPlan plan(int definitionIndex, Definition definition, long gridX, long gridZ) {
int slot = (int) mix(mix(gridX ^ definition.seedSalt()) ^ Long.rotateLeft(gridZ, 29)) & (PLAN_CACHE_SLOTS - 1);
CachedPlan cached = planCache.getAcquire(slot);
if (cached != null && cached.definition() == definitionIndex && cached.gridX() == gridX && cached.gridZ() == gridZ) {
return cached.plan();
}
SubterrainPlan plan = create(definition, gridX, gridZ);
planCache.setRelease(slot, new CachedPlan(definitionIndex, gridX, gridZ, plan));
return plan;
}
private SubterrainCell raw(List<SubterrainPlan> candidates, int x, int y, int z) {
SubterrainCell selected = SubterrainCell.OUTSIDE;
for (SubterrainPlan plan : candidates) {
SubterrainCell cell = plan.sample(x, y, z);
for (int index = 0; index < candidates.size(); index++) {
SubterrainCell cell = candidates.get(index).sample(x, y, z);
if (cell.solid() && cell.room().reservedSolid()) {
return cell;
}
@@ -212,6 +241,9 @@ public final class SubterrainPlanner {
private record CandidateTile(int chunkX, int chunkZ, List<SubterrainPlan> plans) {
}
private record CachedPlan(int definition, long gridX, long gridZ, SubterrainPlan plan) {
}
record Definition(String id, IrisSubterrainFamily family, IrisSubterrainFluid fluid, String biome, int spacing, double probability,
int length, int radius, int height, double shapeWarp, int fluidDepth, int chimneyHeight, int terraceCount,
int pillarSpacing, double formationFraction, String solid, int priority,
@@ -109,6 +109,7 @@ import java.util.concurrent.ConcurrentHashMap;
@Data
public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
private static final int CUSTOM_BIOME_KEY_CACHE_LIMIT = 4096;
private static final Map<File, IrisData> dataLoaders = new ConcurrentHashMap<>();
// Loaders (cached or detached) that currently have registered engines; see hasActiveEngines.
// Identity-keyed on purpose: Lombok's @Data hashCode over this class's mutable loader state
@@ -159,6 +160,10 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
@Setter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
private transient volatile GenerationRegistryContractFactory.CustomBiomeResourceResolver customBiomeResourceResolver;
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
private transient volatile CustomBiomeKeyCache customBiomeKeyCache;
private transient volatile Engine soleEngine;
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
@@ -234,6 +239,32 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
IrisDimension requiredDimension = Objects.requireNonNull(dimension, "dimension");
IrisBiomeCustom requiredBiome = Objects.requireNonNull(customBiome, "customBiome");
GenerationRegistryContractFactory.CustomBiomeResourceResolver resolver = customBiomeResourceResolver;
ContentGate gate = resolver == null ? getContentGate() : null;
Object owner = resolver == null ? gate : resolver;
CustomBiomeKeyCache cache = customBiomeKeyCache;
if (cache == null || cache.owner() != owner) {
cache = new CustomBiomeKeyCache(owner, new ConcurrentHashMap<>());
customBiomeKeyCache = cache;
}
CustomBiomeKey key = new CustomBiomeKey(requiredDimension.getLoadKey(), requiredBiome);
String cached = cache.keys().get(key);
if (cached != null) {
return cached;
}
String resolved = resolveCustomBiomeResourceKey(requiredDimension, requiredBiome, resolver, gate);
if (cache.keys().size() >= CUSTOM_BIOME_KEY_CACHE_LIMIT) {
cache.keys().clear();
}
cache.keys().put(key, resolved);
return resolved;
}
private String resolveCustomBiomeResourceKey(
IrisDimension requiredDimension,
IrisBiomeCustom requiredBiome,
GenerationRegistryContractFactory.CustomBiomeResourceResolver resolver,
ContentGate gate
) {
if (resolver != null) {
try {
return resolver.resolve(
@@ -248,7 +279,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
return GenerationRegistryContractFactory.customBiomeResourceKey(
requiredDimension.getLoadKey(),
requiredBiome,
getContentGate()
gate
);
}
@@ -1035,4 +1066,29 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
return immutable;
}
}
private record CustomBiomeKeyCache(Object owner, ConcurrentHashMap<CustomBiomeKey, String> keys) {
}
private static final class CustomBiomeKey {
private final String dimensionKey;
private final IrisBiomeCustom biome;
private final int hash;
private CustomBiomeKey(String dimensionKey, IrisBiomeCustom biome) {
this.dimensionKey = dimensionKey;
this.biome = biome;
hash = 31 * Objects.hashCode(dimensionKey) + System.identityHashCode(biome);
}
@Override
public boolean equals(Object other) {
return other instanceof CustomBiomeKey key && key.biome == biome && Objects.equals(key.dimensionKey, dimensionKey);
}
@Override
public int hashCode() {
return hash;
}
}
}