mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +00:00
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:
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user