mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-28 23:10:32 +00:00
Heap-sized terrain bake
- Bake capacity derived from the heap (13 KB per entry against 1/32 of maxMemory, 2048 to 32768 entries); detached runtimes use the floor - Pregeneration profile no longer raises the global noise cache size or the bake; it only raises burst parallelism - Removed the hydrology bake reservation, noise cache budget, expansion machinery and the tile cache terrain preparation hook - Bake thread recents compare a generation counter; clear empties retired entries - Removed unused column bake, bake registry and bake accessors
This commit is contained in:
1 parent
bf9e65252c
commit
6582d9d14d
22 files changed
+188
-1037
No files matched your search
@@ -1,7 +0,0 @@
|
||||
package art.arcane.iris.generation.bake;
|
||||
|
||||
public interface BakeCache {
|
||||
ColumnBake get(int chunkX, int chunkZ);
|
||||
|
||||
ColumnBake getIfPresent(int chunkX, int chunkZ);
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package art.arcane.iris.generation.bake;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class BakeRegistry<T> {
|
||||
private final Object lock = new Object();
|
||||
private volatile IdentityHashMap<T, Integer> ids = new IdentityHashMap<>();
|
||||
private volatile Object[] values = new Object[16];
|
||||
private int size;
|
||||
|
||||
public short id(T value) {
|
||||
Objects.requireNonNull(value, "Registered value");
|
||||
Integer known = ids.get(value);
|
||||
if (known != null) {
|
||||
return known.shortValue();
|
||||
}
|
||||
synchronized (lock) {
|
||||
Integer existing = ids.get(value);
|
||||
if (existing != null) {
|
||||
return existing.shortValue();
|
||||
}
|
||||
if (size == Short.MAX_VALUE) {
|
||||
throw new IllegalStateException("Bake registry is full");
|
||||
}
|
||||
Object[] grown = size == values.length ? Arrays.copyOf(values, size * 2) : values;
|
||||
grown[size] = value;
|
||||
values = grown;
|
||||
IdentityHashMap<T, Integer> next = new IdentityHashMap<>(ids);
|
||||
next.put(value, size);
|
||||
ids = next;
|
||||
return (short) size++;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T value(int id) {
|
||||
return (T) values[id];
|
||||
}
|
||||
}
|
||||
@@ -5,47 +5,36 @@ import art.arcane.iris.generation.stream.ProvisionalSampling;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class ChunkBakeCache implements BakeCache {
|
||||
public final class ChunkBakeCache {
|
||||
public static final int MINIMUM_CAPACITY = 2_048;
|
||||
public static final int MAXIMUM_CAPACITY = 32_768;
|
||||
public static final long BYTES_PER_ENTRY = 13L * 1_024L;
|
||||
public static final int HEAP_SHARE = 32;
|
||||
private static final ThreadLocal<int[]> SPARSE = ThreadLocal.withInitial(() -> new int[1]);
|
||||
|
||||
private final PlaneSource source;
|
||||
private final ConcurrentClockCache<ChunkPlanes> entries;
|
||||
private final ThreadLocal<Recent> recent = ThreadLocal.withInitial(Recent::new);
|
||||
private volatile ConcurrentClockCache<ChunkPlanes> entries;
|
||||
private volatile int generation;
|
||||
|
||||
public ChunkBakeCache(PlaneSource source, int capacityChunks) {
|
||||
this.source = Objects.requireNonNull(source, "Bake plane source");
|
||||
this.entries = new ConcurrentClockCache<>(Math.max(16, capacityChunks));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnBake get(int chunkX, int chunkZ) {
|
||||
return planes(chunkX, chunkZ).columnBake(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnBake getIfPresent(int chunkX, int chunkZ) {
|
||||
ChunkPlanes planes = planesIfPresent(chunkX, chunkZ);
|
||||
return planes == null ? null : planes.columnBake();
|
||||
public static int capacityFor(long maxMemory) {
|
||||
long entries = Math.max(0L, maxMemory) / HEAP_SHARE / BYTES_PER_ENTRY;
|
||||
return (int) Math.max(MINIMUM_CAPACITY, Math.min(MAXIMUM_CAPACITY, entries));
|
||||
}
|
||||
|
||||
public int capacity() {
|
||||
return entries.capacity();
|
||||
}
|
||||
|
||||
public synchronized void ensureCapacity(int chunks) {
|
||||
ConcurrentClockCache<ChunkPlanes> current = entries;
|
||||
if (current.capacity() >= chunks) {
|
||||
return;
|
||||
}
|
||||
ConcurrentClockCache<ChunkPlanes> resized = new ConcurrentClockCache<>(chunks);
|
||||
current.forEach(planes -> resized.putIfAbsent(key(planes.chunkX(), planes.chunkZ()), planes));
|
||||
entries = resized;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
ConcurrentClockCache<ChunkPlanes> current = entries;
|
||||
entries = new ConcurrentClockCache<>(current.capacity());
|
||||
current.clear();
|
||||
public synchronized void clear() {
|
||||
generation++;
|
||||
entries.forEach(ChunkPlanes::release);
|
||||
entries.clear();
|
||||
}
|
||||
|
||||
public long[] census() {
|
||||
@@ -61,32 +50,19 @@ public final class ChunkBakeCache implements BakeCache {
|
||||
return counts;
|
||||
}
|
||||
|
||||
public ChunkPlanes planes(int chunkX, int chunkZ) {
|
||||
ConcurrentClockCache<ChunkPlanes> current = entries;
|
||||
long key = key(chunkX, chunkZ);
|
||||
ChunkPlanes existing = current.get(key);
|
||||
return existing != null ? existing : current.putIfAbsent(key, new ChunkPlanes(chunkX, chunkZ));
|
||||
}
|
||||
|
||||
public ChunkPlanes planesIfPresent(int chunkX, int chunkZ) {
|
||||
return entries.get(key(chunkX, chunkZ));
|
||||
}
|
||||
|
||||
public ChunkPlanes.Plane planeIfPresent(BakePlane plane, int chunkX, int chunkZ) {
|
||||
long key = key(chunkX, chunkZ);
|
||||
Recent last = recent.get();
|
||||
ConcurrentClockCache<ChunkPlanes> current = entries;
|
||||
int current = generation;
|
||||
ChunkPlanes planes;
|
||||
if (last.key == key && last.entries == current) {
|
||||
if (last.key == key && last.generation == current) {
|
||||
planes = last.planes;
|
||||
} else {
|
||||
planes = current.get(key);
|
||||
planes = entries.get(key);
|
||||
if (planes == null) {
|
||||
return null;
|
||||
}
|
||||
last.key = key;
|
||||
last.planes = planes;
|
||||
last.entries = current;
|
||||
last.remember(key, planes, current);
|
||||
}
|
||||
return planes.planeIfPresent(plane);
|
||||
}
|
||||
@@ -117,15 +93,16 @@ public final class ChunkBakeCache implements BakeCache {
|
||||
remembered(blockX >> 4, blockZ >> 4).plane(plane).storeObject(((blockZ & 15) << 4) | (blockX & 15), value);
|
||||
}
|
||||
|
||||
private ChunkPlanes remembered(int chunkX, int chunkZ) {
|
||||
ConcurrentClockCache<ChunkPlanes> current = entries;
|
||||
private ChunkPlanes planes(int chunkX, int chunkZ) {
|
||||
long key = key(chunkX, chunkZ);
|
||||
ChunkPlanes existing = current.get(key);
|
||||
ChunkPlanes planes = existing != null ? existing : current.putIfAbsent(key, new ChunkPlanes(chunkX, chunkZ));
|
||||
Recent last = recent.get();
|
||||
last.key = key;
|
||||
last.planes = planes;
|
||||
last.entries = current;
|
||||
ChunkPlanes existing = entries.get(key);
|
||||
return existing != null ? existing : entries.putIfAbsent(key, new ChunkPlanes(chunkX, chunkZ));
|
||||
}
|
||||
|
||||
private ChunkPlanes remembered(int chunkX, int chunkZ) {
|
||||
int current = generation;
|
||||
ChunkPlanes planes = planes(chunkX, chunkZ);
|
||||
recent.get().remember(key(chunkX, chunkZ), planes, current);
|
||||
return planes;
|
||||
}
|
||||
|
||||
@@ -147,7 +124,13 @@ public final class ChunkBakeCache implements BakeCache {
|
||||
|
||||
private static final class Recent {
|
||||
private long key;
|
||||
private int generation = -1;
|
||||
private ChunkPlanes planes;
|
||||
private ConcurrentClockCache<ChunkPlanes> entries;
|
||||
|
||||
private void remember(long key, ChunkPlanes planes, int generation) {
|
||||
this.key = key;
|
||||
this.planes = planes;
|
||||
this.generation = generation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ public final class ChunkPlanes {
|
||||
private final int chunkX;
|
||||
private final int chunkZ;
|
||||
private final AtomicReferenceArray<Plane> planes = new AtomicReferenceArray<>(BakePlane.PLANES.length);
|
||||
private volatile ColumnBake columnBake;
|
||||
|
||||
ChunkPlanes(int chunkX, int chunkZ) {
|
||||
this.chunkX = chunkX;
|
||||
@@ -70,21 +69,10 @@ public final class ChunkPlanes {
|
||||
}
|
||||
}
|
||||
|
||||
ColumnBake columnBake() {
|
||||
return columnBake;
|
||||
}
|
||||
|
||||
ColumnBake columnBake(PlaneSource source) {
|
||||
ColumnBake bake = columnBake;
|
||||
if (bake != null) {
|
||||
return bake;
|
||||
void release() {
|
||||
for (int slot = 0; slot < planes.length(); slot++) {
|
||||
planes.setRelease(slot, null);
|
||||
}
|
||||
long revision = ProvisionalSampling.revision();
|
||||
bake = source.assemble(this);
|
||||
if (revision == ProvisionalSampling.revision()) {
|
||||
columnBake = bake;
|
||||
}
|
||||
return bake;
|
||||
}
|
||||
|
||||
public static final class Plane {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package art.arcane.iris.generation.bake;
|
||||
|
||||
import art.arcane.iris.generation.terrain.Terrain3DColumn;
|
||||
|
||||
public final class ColumnBake {
|
||||
public static final int APRON_WIDTH = 18;
|
||||
|
||||
public final int chunkX;
|
||||
public final int chunkZ;
|
||||
public final short[] regionId;
|
||||
public final short[] baseBiomeId;
|
||||
public final short[] trueBiomeId;
|
||||
public final short[] caveBiomeId;
|
||||
public final double[] baseHeight;
|
||||
public final double[] naturalHeight;
|
||||
public final double[] height;
|
||||
public final int[] roundedHeight;
|
||||
public final float[] fluidSurface;
|
||||
public final short[] rockState;
|
||||
public final short[] fluidState;
|
||||
public final Terrain3DColumn[] terrainColumns;
|
||||
public final double[] apronHeight;
|
||||
|
||||
public ColumnBake(Planes planes) {
|
||||
this.chunkX = planes.chunkX();
|
||||
this.chunkZ = planes.chunkZ();
|
||||
this.regionId = planes.regionId();
|
||||
this.baseBiomeId = planes.baseBiomeId();
|
||||
this.trueBiomeId = planes.trueBiomeId();
|
||||
this.caveBiomeId = planes.caveBiomeId();
|
||||
this.baseHeight = planes.baseHeight();
|
||||
this.naturalHeight = planes.naturalHeight();
|
||||
this.height = planes.height();
|
||||
this.roundedHeight = planes.roundedHeight();
|
||||
this.fluidSurface = planes.fluidSurface();
|
||||
this.rockState = planes.rockState();
|
||||
this.fluidState = planes.fluidState();
|
||||
this.terrainColumns = planes.terrainColumns();
|
||||
this.apronHeight = planes.apronHeight();
|
||||
}
|
||||
|
||||
public static int index(int localX, int localZ) {
|
||||
return (localZ << 4) | localX;
|
||||
}
|
||||
|
||||
public static int apronIndex(int localX, int localZ) {
|
||||
return (localZ + 1) * APRON_WIDTH + localX + 1;
|
||||
}
|
||||
|
||||
public record Planes(int chunkX, int chunkZ, short[] regionId, short[] baseBiomeId, short[] trueBiomeId,
|
||||
short[] caveBiomeId, double[] baseHeight, double[] naturalHeight, double[] height,
|
||||
int[] roundedHeight, float[] fluidSurface, short[] rockState, short[] fluidState,
|
||||
Terrain3DColumn[] terrainColumns, double[] apronHeight) {
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,5 @@ package art.arcane.iris.generation.bake;
|
||||
public interface PlaneSource {
|
||||
void fill(BakePlane plane, int chunkX, int chunkZ, long[] missing, Object values);
|
||||
|
||||
ColumnBake assemble(ChunkPlanes planes);
|
||||
|
||||
boolean mainThread();
|
||||
}
|
||||
@@ -80,7 +80,6 @@ public final class HydrologyTileCache implements AutoCloseable {
|
||||
private volatile SharedCacheScope sharedCacheScope;
|
||||
private volatile PreparedHydrologyTileStore persistentStore;
|
||||
private volatile boolean neighbourPrefetchEnabled;
|
||||
private volatile Runnable terrainPreparation;
|
||||
private PregenerationScope pregenerationScope;
|
||||
private int demandBatches;
|
||||
private int activePrefetches;
|
||||
@@ -268,7 +267,6 @@ public final class HydrologyTileCache implements AutoCloseable {
|
||||
private List<HydrologyDiagnosticCandidate> planDiagnostics(HydrologyTileKey key, long epoch) {
|
||||
HydrologyTile tile = planInline(key, epoch);
|
||||
try (HydrologyPlanningAdmission.Permit ignored = HydrologyPlanningAdmission.acquireRoot(closed::get)) {
|
||||
prepareTerrain();
|
||||
return List.copyOf(planner.diagnosticCandidates(tile));
|
||||
}
|
||||
}
|
||||
@@ -459,7 +457,6 @@ public final class HydrologyTileCache implements AutoCloseable {
|
||||
/** Plans the tile under a root permit; a persisted plan is restored before any permit is taken. */
|
||||
private HydrologyTile planAdmitted(HydrologyTileKey key, long planningEpoch,
|
||||
SharedTileKey sharedKey, PreparedHydrologyTileStore store) {
|
||||
prepareTerrain();
|
||||
HydrologyTile planned = planner.plan(key);
|
||||
synchronized (publicationLock) {
|
||||
if (sharedKey != null && !closed.get() && planningEpoch == cacheEpoch.get()) {
|
||||
@@ -777,17 +774,6 @@ public final class HydrologyTileCache implements AutoCloseable {
|
||||
clear();
|
||||
}
|
||||
|
||||
public void setTerrainPreparation(Runnable preparation) {
|
||||
terrainPreparation = Objects.requireNonNull(preparation, "terrainPreparation");
|
||||
}
|
||||
|
||||
private void prepareTerrain() {
|
||||
Runnable preparation = terrainPreparation;
|
||||
if (preparation != null) {
|
||||
preparation.run();
|
||||
}
|
||||
}
|
||||
|
||||
private static void collectActiveLoad(List<CompletableFuture<?>> active, PendingLoad<?> load) {
|
||||
if (load.owner() == Thread.currentThread()) {
|
||||
throw new IllegalStateException("Hydrology tile cache cannot close from an active load.");
|
||||
|
||||
-1
@@ -123,7 +123,6 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
|
||||
IrisPlatforms.isBound() ? MultiBurst.hydrology : null,
|
||||
context.waitingForbidden()
|
||||
);
|
||||
this.cache.setTerrainPreparation(context.terrainPreparation());
|
||||
IrisLogging.debug("Hydrology runtime: tileSize=%d publicationRadius=%d planningThreads=%d",
|
||||
settings.routing().tileSize(), settings.publicationRadius(), MultiBurst.hydrology.parallelism());
|
||||
}
|
||||
|
||||
+1
-3
@@ -17,8 +17,7 @@ public record IrisHydrologyRuntimeContext(
|
||||
IrisHydrologyNaturalHeightDescriber naturalHeightDescriber,
|
||||
IrisHydrologyNaturalOceanClassifier naturalOceanClassifier,
|
||||
HydrologyCaveVoxelViewFactory caveViewFactory,
|
||||
BooleanSupplier waitingForbidden,
|
||||
Runnable terrainPreparation
|
||||
BooleanSupplier waitingForbidden
|
||||
) {
|
||||
/**
|
||||
* {@code waitingForbidden} is true on a thread that must never wait for a cold hydrology plan,
|
||||
@@ -37,6 +36,5 @@ public record IrisHydrologyRuntimeContext(
|
||||
Objects.requireNonNull(naturalOceanClassifier);
|
||||
Objects.requireNonNull(caveViewFactory);
|
||||
Objects.requireNonNull(waitingForbidden);
|
||||
Objects.requireNonNull(terrainPreparation);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package art.arcane.iris.generation.runtime;
|
||||
|
||||
final class HydrologyNoiseCacheBudget {
|
||||
static final int MAXIMUM_CHUNKS = 32_768;
|
||||
static final int BYTES_PER_CHUNK = 4_096;
|
||||
static final HydrologyNoiseCacheBudget SHARED = new HydrologyNoiseCacheBudget(Runtime.getRuntime().maxMemory() / 8L);
|
||||
|
||||
private final long maximumBytes;
|
||||
private long reservedBytes;
|
||||
|
||||
HydrologyNoiseCacheBudget(long maximumBytes) {
|
||||
if (maximumBytes < 0L) {
|
||||
throw new IllegalArgumentException("Cache budget must not be negative.");
|
||||
}
|
||||
this.maximumBytes = maximumBytes;
|
||||
}
|
||||
|
||||
synchronized Reservation reserve(int cacheCount, int requestedAdditionalChunks) {
|
||||
if (cacheCount <= 0 || requestedAdditionalChunks < 0 || requestedAdditionalChunks > MAXIMUM_CHUNKS) {
|
||||
throw new IllegalArgumentException("Invalid hydrology cache reservation.");
|
||||
}
|
||||
long bytesPerIncrement = (long) BYTES_PER_CHUNK * cacheCount;
|
||||
int additionalChunks = (int) Math.min(requestedAdditionalChunks, (maximumBytes - reservedBytes) / bytesPerIncrement);
|
||||
long bytes = additionalChunks * bytesPerIncrement;
|
||||
reservedBytes += bytes;
|
||||
return new Reservation(this, additionalChunks, bytes);
|
||||
}
|
||||
|
||||
synchronized long reservedBytes() {
|
||||
return reservedBytes;
|
||||
}
|
||||
|
||||
private synchronized void release(Reservation reservation) {
|
||||
if (!reservation.released) {
|
||||
reservedBytes -= reservation.bytes;
|
||||
reservation.released = true;
|
||||
}
|
||||
}
|
||||
|
||||
static final class Reservation implements AutoCloseable {
|
||||
private final HydrologyNoiseCacheBudget budget;
|
||||
private final int additionalChunks;
|
||||
private final long bytes;
|
||||
private boolean released;
|
||||
|
||||
private Reservation(HydrologyNoiseCacheBudget budget, int additionalChunks, long bytes) {
|
||||
this.budget = budget;
|
||||
this.additionalChunks = additionalChunks;
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
int additionalChunks() {
|
||||
return additionalChunks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
budget.release(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,21 +19,17 @@
|
||||
package art.arcane.iris.generation.runtime;
|
||||
|
||||
import art.arcane.iris.generation.bake.BakePlane;
|
||||
import art.arcane.iris.generation.bake.BakeRegistry;
|
||||
import art.arcane.iris.generation.bake.ChunkBakeCache;
|
||||
import art.arcane.iris.generation.bake.ChunkPlanes;
|
||||
import art.arcane.iris.generation.bake.ColumnBake;
|
||||
import art.arcane.iris.generation.bake.GeneratorEvaluations;
|
||||
import art.arcane.iris.generation.bake.PlaneSource;
|
||||
import art.arcane.iris.generation.bake.TerrainProgram;
|
||||
import art.arcane.iris.generation.noise.TerrainBatchScratch;
|
||||
import art.arcane.iris.generation.simd.SimdSupport;
|
||||
import art.arcane.iris.generation.stream.BakedDoubleStream;
|
||||
import art.arcane.iris.generation.stream.BakedHeightStream;
|
||||
import art.arcane.iris.generation.stream.BakedStream;
|
||||
import art.arcane.iris.generation.stream.GenerationStreams;
|
||||
import art.arcane.iris.generation.stream.RarityStream;
|
||||
import art.arcane.iris.generation.stream.CachedDoubleStream2D;
|
||||
import art.arcane.iris.generation.stream.CachedStream2D;
|
||||
import art.arcane.iris.generation.stream.ProvisionalSampling;
|
||||
import art.arcane.iris.generation.subterrain.SubterrainPlanner;
|
||||
@@ -132,16 +128,14 @@ import java.nio.file.Path;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(exclude = {"data", "gridBoundsCache", "sharedCornerBounds", "terrainProgram", "bakeCache", "biomeRegistry", "regionRegistry", "blockRegistry", "regionSource", "baseBiomeSource", "caveSource", "rockSource", "fluidSource", "childStyleSeed", "inferredByType", "naturalHeightSource", "naturalTrueBiomeSource", "trueBiomeSource", "frozenInterpolators", "frozenGenerators", "inferredBiomeStreams", "hydrologyRuntime", "imageMapRuntime", "terrainEngine", "terrain3D", "hydrologyBanks3D"})
|
||||
@ToString(exclude = {"data", "gridBoundsCache", "sharedCornerBounds", "terrainProgram", "bakeCache", "biomeRegistry", "regionRegistry", "blockRegistry", "regionSource", "baseBiomeSource", "caveSource", "rockSource", "fluidSource", "childStyleSeed", "inferredByType", "naturalHeightSource", "naturalTrueBiomeSource", "trueBiomeSource", "frozenInterpolators", "frozenGenerators", "inferredBiomeStreams", "hydrologyRuntime", "imageMapRuntime", "terrainEngine", "terrain3D", "hydrologyBanks3D"})
|
||||
@EqualsAndHashCode(exclude = {"data", "gridBoundsCache", "sharedCornerBounds", "terrainProgram", "bakeCache", "regionSource", "baseBiomeSource", "caveSource", "rockSource", "fluidSource", "childStyleSeed", "inferredByType", "naturalHeightSource", "naturalTrueBiomeSource", "trueBiomeSource", "frozenInterpolators", "frozenGenerators", "inferredBiomeStreams", "hydrologyRuntime", "imageMapRuntime", "terrainEngine", "terrain3D", "hydrologyBanks3D"})
|
||||
@ToString(exclude = {"data", "gridBoundsCache", "sharedCornerBounds", "terrainProgram", "bakeCache", "regionSource", "baseBiomeSource", "caveSource", "rockSource", "fluidSource", "childStyleSeed", "inferredByType", "naturalHeightSource", "naturalTrueBiomeSource", "trueBiomeSource", "frozenInterpolators", "frozenGenerators", "inferredBiomeStreams", "hydrologyRuntime", "imageMapRuntime", "terrainEngine", "terrain3D", "hydrologyBanks3D"})
|
||||
public class IrisComplex implements DataProvider {
|
||||
private static final NoiseBounds ZERO_NOISE_BOUNDS = new NoiseBounds(0D, 0D);
|
||||
private static final AtomicLong lastBoundsFailureLog = new AtomicLong(0L);
|
||||
private static final int GRID_BOUNDS_CACHE_SIZE = 8192;
|
||||
private static final int STUDIO_NOISE_CACHE_SIZE = 32_768;
|
||||
private static final int TERRAIN_COLUMN_CACHE_SIZE = 524_288;
|
||||
private static final int BAKE_CHUNKS_PER_NOISE_CHUNK = 8;
|
||||
private static final int HYDROLOGY_BAKE_BUDGET_SHARES = 2;
|
||||
/** One million corners: about 20 MB, roughly a 4000 by 4000 block area at the 4-block grid. */
|
||||
private static final int SHARED_CORNER_BOUNDS_CAPACITY = 1 << 20;
|
||||
/** The slope streams measure the rise across a run of this many blocks, so gradient is slope over run. */
|
||||
@@ -175,12 +169,6 @@ public class IrisComplex implements DataProvider {
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final transient ChunkBakeCache bakeCache;
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final transient BakeRegistry<IrisBiome> biomeRegistry = new BakeRegistry<>();
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final transient BakeRegistry<IrisRegion> regionRegistry = new BakeRegistry<>();
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final transient BakeRegistry<NativeBlockState> blockRegistry = new BakeRegistry<>();
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final transient long childStyleSeed;
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final transient Map<InferredType, ProceduralStream<IrisBiome>> inferredByType;
|
||||
@@ -209,18 +197,6 @@ public class IrisComplex implements DataProvider {
|
||||
private final TransitionGenerationPlan transitionGenerationPlan;
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final transient boolean detached;
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private transient List<NoiseCacheCapacity> hydrologyNoiseCaches = List.of();
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private transient HydrologyNoiseCacheBudget.Reservation hydrologyNoiseCacheReservation;
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private transient boolean hydrologyNoiseCachesClosed;
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private transient HydrologyNoiseCacheBudget.Reservation hydrologyBakeReservation;
|
||||
private RNG rng;
|
||||
private double fluidHeight;
|
||||
private IrisData data;
|
||||
@@ -415,7 +391,8 @@ public class IrisComplex implements DataProvider {
|
||||
|
||||
return value;
|
||||
});
|
||||
bakeCache = new ChunkBakeCache(new BakeSource(), cacheSize * BAKE_CHUNKS_PER_NOISE_CHUNK);
|
||||
bakeCache = new ChunkBakeCache(new BakeSource(), detached ? ChunkBakeCache.MINIMUM_CAPACITY
|
||||
: ChunkBakeCache.capacityFor(Runtime.getRuntime().maxMemory()));
|
||||
rockSource = engine.getDimension().getRockPalette().getLayerGenerator(rng.nextParallelRNG(45), data).stream()
|
||||
.select(engine.getDimension().getRockPalette().getBlockData(data));
|
||||
rockStream = new BakedStream<>(bakeCache, BakePlane.ROCK, rockSource);
|
||||
@@ -508,8 +485,7 @@ public class IrisComplex implements DataProvider {
|
||||
(x, z) -> describeNaturalHeight(engine, x, z),
|
||||
this::sampleNaturalOcean,
|
||||
footprint -> new MantleHydrologyCaveVoxelView(engine, this, footprint),
|
||||
() -> engine.getPlatformHooks().isMainThread(),
|
||||
this::expandHydrologyNoiseCaches
|
||||
() -> engine.getPlatformHooks().isMainThread()
|
||||
));
|
||||
hydrologyRuntime.setNeighbourPrefetchEnabled(!engine.isStudio());
|
||||
IrisRiverBank3DConfig banks3D = configuredHydrology.getRivers().getGeometry().getBanks3D();
|
||||
@@ -576,81 +552,6 @@ public class IrisComplex implements DataProvider {
|
||||
return engine.isStudio() && !detached ? Math.max(configuredSize, STUDIO_NOISE_CACHE_SIZE) : configuredSize;
|
||||
}
|
||||
|
||||
public void ensureTerrainNoiseCacheSize(int minimumChunks) {
|
||||
if (minimumChunks <= 0) {
|
||||
throw new IllegalArgumentException("Terrain noise cache size must be positive");
|
||||
}
|
||||
bakeCache.ensureCapacity(minimumChunks * BAKE_CHUNKS_PER_NOISE_CHUNK);
|
||||
}
|
||||
|
||||
void expandHydrologyNoiseCaches() {
|
||||
expandHydrologyNoiseCaches(HydrologyNoiseCacheBudget.SHARED);
|
||||
}
|
||||
|
||||
synchronized void expandHydrologyNoiseCaches(HydrologyNoiseCacheBudget budget) {
|
||||
if (detached || hydrologyRuntime == null || hydrologyNoiseCachesClosed || hydrologyNoiseCacheReservation != null) {
|
||||
return;
|
||||
}
|
||||
IdentityHashMap<ProceduralStream<?>, Boolean> selected = new IdentityHashMap<>();
|
||||
List<NoiseCacheCapacity> capacities = new ArrayList<>(5);
|
||||
int additionalChunks = HydrologyNoiseCacheBudget.MAXIMUM_CHUNKS;
|
||||
for (ProceduralStream<?> stream : new ProceduralStream<?>[]{
|
||||
baseBiomeStream, baseTerrainHeightStream, unblendedNaturalHeightStream, naturalHeightStream, regionStream, bridgeStream}) {
|
||||
if (!(stream instanceof CachedStream2D<?> || stream instanceof CachedDoubleStream2D)
|
||||
|| selected.put(stream, Boolean.TRUE) != null) {
|
||||
continue;
|
||||
}
|
||||
int originalChunks = Math.toIntExact(((MeteredCache) stream).getMaxSize() / 256L);
|
||||
if (originalChunks >= HydrologyNoiseCacheBudget.MAXIMUM_CHUNKS) {
|
||||
continue;
|
||||
}
|
||||
capacities.add(new NoiseCacheCapacity(stream, originalChunks));
|
||||
additionalChunks = Math.min(additionalChunks, HydrologyNoiseCacheBudget.MAXIMUM_CHUNKS - originalChunks);
|
||||
}
|
||||
if (capacities.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
hydrologyNoiseCaches = List.copyOf(capacities);
|
||||
hydrologyNoiseCacheReservation = budget.reserve(capacities.size(), additionalChunks);
|
||||
for (NoiseCacheCapacity capacity : hydrologyNoiseCaches) {
|
||||
capacity.resize(capacity.originalChunks() + hydrologyNoiseCacheReservation.additionalChunks());
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void releaseHydrologyNoiseCaches() {
|
||||
hydrologyNoiseCachesClosed = true;
|
||||
if (hydrologyNoiseCacheReservation == null) {
|
||||
return;
|
||||
}
|
||||
for (NoiseCacheCapacity capacity : hydrologyNoiseCaches) {
|
||||
capacity.resize(capacity.originalChunks());
|
||||
}
|
||||
hydrologyNoiseCacheReservation.close();
|
||||
hydrologyNoiseCacheReservation = null;
|
||||
hydrologyNoiseCaches = List.of();
|
||||
}
|
||||
|
||||
synchronized void reserveHydrologyBake(HydrologyNoiseCacheBudget budget) {
|
||||
if (detached || hydrologyRuntime == null || hydrologyNoiseCachesClosed || hydrologyBakeReservation != null) {
|
||||
return;
|
||||
}
|
||||
int capacity = bakeCache.capacity();
|
||||
int requested = HydrologyNoiseCacheBudget.MAXIMUM_CHUNKS - capacity;
|
||||
if (requested <= 0) {
|
||||
return;
|
||||
}
|
||||
hydrologyBakeReservation = budget.reserve(HYDROLOGY_BAKE_BUDGET_SHARES, requested);
|
||||
bakeCache.ensureCapacity(capacity + hydrologyBakeReservation.additionalChunks());
|
||||
}
|
||||
|
||||
private synchronized void releaseHydrologyBake() {
|
||||
if (hydrologyBakeReservation == null) {
|
||||
return;
|
||||
}
|
||||
hydrologyBakeReservation.close();
|
||||
hydrologyBakeReservation = null;
|
||||
}
|
||||
|
||||
void enablePreparedHydrologyCache(String runtimeIdentity, Path persistentRoot) {
|
||||
if (hydrologyRuntime != null) {
|
||||
hydrologyRuntime.enableSharedCache(runtimeIdentity, persistentRoot);
|
||||
@@ -1110,11 +1011,9 @@ public class IrisComplex implements DataProvider {
|
||||
|
||||
private final class HydrologyHeightProvider implements IrisHydrologyNaturalHeightProvider {
|
||||
private final boolean batch = supportsHeightBatch();
|
||||
private volatile boolean bakeReserved;
|
||||
|
||||
@Override
|
||||
public double sample(int blockX, int blockZ) {
|
||||
reserveBake();
|
||||
return naturalHeightStream.getDouble(blockX, blockZ);
|
||||
}
|
||||
|
||||
@@ -1129,17 +1028,8 @@ public class IrisComplex implements DataProvider {
|
||||
IrisHydrologyNaturalHeightProvider.super.sampleBatch(coordinates, count, heights);
|
||||
return;
|
||||
}
|
||||
reserveBake();
|
||||
sampleNaturalBatch(coordinates, count, heights);
|
||||
}
|
||||
|
||||
private void reserveBake() {
|
||||
if (bakeReserved) {
|
||||
return;
|
||||
}
|
||||
bakeReserved = true;
|
||||
reserveHydrologyBake(HydrologyNoiseCacheBudget.SHARED);
|
||||
}
|
||||
}
|
||||
|
||||
private final class BakeSource implements PlaneSource {
|
||||
@@ -1159,11 +1049,6 @@ public class IrisComplex implements DataProvider {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnBake assemble(ChunkPlanes planes) {
|
||||
return assembleColumnBake(planes.chunkX(), planes.chunkZ());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mainThread() {
|
||||
return terrainEngine.getPlatformHooks().isMainThread();
|
||||
@@ -1207,87 +1092,10 @@ public class IrisComplex implements DataProvider {
|
||||
}
|
||||
}
|
||||
|
||||
public Object bakePlane(BakePlane plane, int chunkX, int chunkZ) {
|
||||
return bakeCache.complete(plane, chunkX, chunkZ);
|
||||
}
|
||||
|
||||
public long[] bakeCensus() {
|
||||
return bakeCache.census();
|
||||
}
|
||||
|
||||
public ColumnBake columnBake(int chunkX, int chunkZ) {
|
||||
return bakeCache.get(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
public ColumnBake columnBakeIfPresent(int chunkX, int chunkZ) {
|
||||
return bakeCache.getIfPresent(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
public IrisBiome biome(int id) {
|
||||
return id < 0 ? null : biomeRegistry.value(id);
|
||||
}
|
||||
|
||||
public short biomeId(IrisBiome biome) {
|
||||
return biome == null ? -1 : biomeRegistry.id(biome);
|
||||
}
|
||||
|
||||
public IrisRegion region(int id) {
|
||||
return id < 0 ? null : regionRegistry.value(id);
|
||||
}
|
||||
|
||||
public short regionId(IrisRegion region) {
|
||||
return region == null ? -1 : regionRegistry.id(region);
|
||||
}
|
||||
|
||||
public NativeBlockState blockState(int id) {
|
||||
return id < 0 ? null : blockRegistry.value(id);
|
||||
}
|
||||
|
||||
private ColumnBake assembleColumnBake(int chunkX, int chunkZ) {
|
||||
double[] height = (double[]) bakeCache.complete(BakePlane.HEIGHT, chunkX, chunkZ);
|
||||
int[] rounded = new int[256];
|
||||
SimdSupport.kernels().roundToInt(height, rounded, 256);
|
||||
int originX = chunkX << 4;
|
||||
int originZ = chunkZ << 4;
|
||||
float[] fluidSurface = new float[256];
|
||||
Terrain3DColumn[] terrainColumns = new Terrain3DColumn[256];
|
||||
for (int index = 0; index < 256; index++) {
|
||||
int x = originX + (index & 15);
|
||||
int z = originZ + (index >> 4);
|
||||
fluidSurface[index] = (float) riverWaterSurfaceStream.getDouble(x, z);
|
||||
terrainColumns[index] = terrainColumn(x, z);
|
||||
}
|
||||
double[] apron = new double[ColumnBake.APRON_WIDTH * ColumnBake.APRON_WIDTH];
|
||||
for (int row = 0; row < ColumnBake.APRON_WIDTH; row++) {
|
||||
for (int column = 0; column < ColumnBake.APRON_WIDTH; column++) {
|
||||
apron[row * ColumnBake.APRON_WIDTH + column] = heightStream.getDouble(originX - 1 + column, originZ - 1 + row);
|
||||
}
|
||||
}
|
||||
return new ColumnBake(new ColumnBake.Planes(chunkX, chunkZ,
|
||||
ids((Object[]) bakeCache.complete(BakePlane.REGION, chunkX, chunkZ), regionRegistry),
|
||||
ids((Object[]) bakeCache.complete(BakePlane.BASE_BIOME, chunkX, chunkZ), biomeRegistry),
|
||||
ids((Object[]) bakeCache.complete(BakePlane.TRUE_BIOME, chunkX, chunkZ), biomeRegistry),
|
||||
ids((Object[]) bakeCache.complete(BakePlane.CAVE_BIOME, chunkX, chunkZ), biomeRegistry),
|
||||
(double[]) bakeCache.complete(BakePlane.BASE_HEIGHT, chunkX, chunkZ),
|
||||
(double[]) bakeCache.complete(BakePlane.NATURAL_HEIGHT, chunkX, chunkZ),
|
||||
height,
|
||||
rounded,
|
||||
fluidSurface,
|
||||
ids((Object[]) bakeCache.complete(BakePlane.ROCK, chunkX, chunkZ), blockRegistry),
|
||||
ids((Object[]) bakeCache.complete(BakePlane.FLUID, chunkX, chunkZ), blockRegistry),
|
||||
terrainColumns,
|
||||
apron));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> short[] ids(Object[] values, BakeRegistry<T> registry) {
|
||||
short[] ids = new short[values.length];
|
||||
for (int index = 0; index < values.length; index++) {
|
||||
ids[index] = values[index] == null ? -1 : registry.id((T) values[index]);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
private double sampleUnblendedNaturalTerrainHeight(double x, double z) {
|
||||
double proceduralHeight = proceduralTerrainHeight.sample(x, z);
|
||||
return imageMapRuntime.sampleTerrainHeight(x, z, proceduralHeight);
|
||||
@@ -3021,16 +2829,6 @@ public class IrisComplex implements DataProvider {
|
||||
record GeneratorGroup(IrisInterpolator interpolator, IrisGenerator[] generators) {
|
||||
}
|
||||
|
||||
private record NoiseCacheCapacity(ProceduralStream<?> stream, int originalChunks) {
|
||||
private void resize(int chunks) {
|
||||
if (stream instanceof CachedDoubleStream2D doubles) {
|
||||
doubles.setMaximumChunks(chunks);
|
||||
} else if (stream instanceof CachedStream2D<?> objects) {
|
||||
objects.setMaximumChunks(chunks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (terrainTransform != null) {
|
||||
terrainTransform.close();
|
||||
@@ -3044,8 +2842,6 @@ public class IrisComplex implements DataProvider {
|
||||
if (hydrologyRuntime != null) {
|
||||
hydrologyRuntime.close();
|
||||
}
|
||||
releaseHydrologyNoiseCaches();
|
||||
releaseHydrologyBake();
|
||||
bakeCache.clear();
|
||||
if (transitionDisplacement != null) {
|
||||
transitionDisplacement.clear();
|
||||
|
||||
@@ -18,15 +18,12 @@
|
||||
|
||||
package art.arcane.iris.world.pregen;
|
||||
|
||||
import art.arcane.iris.configuration.IrisSettings;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.platform.generation.PlatformChunkGenerator;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.generation.concurrent.MultiBurst;
|
||||
import art.arcane.volmlib.util.parallel.MultiBurstSupport;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public final class PregenPerformanceProfile {
|
||||
@@ -36,22 +33,11 @@ public final class PregenPerformanceProfile {
|
||||
}
|
||||
|
||||
public static boolean apply() {
|
||||
IrisSettings.IrisSettingsPerformance performance = IrisSettings.get().getPerformance();
|
||||
int previousNoiseCacheSize = performance.getNoiseCacheSize();
|
||||
int targetNoiseCacheSize = Math.max(previousNoiseCacheSize, 4_096);
|
||||
boolean changed = false;
|
||||
|
||||
if (targetNoiseCacheSize != previousNoiseCacheSize) {
|
||||
performance.setNoiseCacheSize(targetNoiseCacheSize);
|
||||
changed = true;
|
||||
if (!MultiBurst.burst.raiseParallelism(pregenBurstParallelism(MultiBurstSupport.performanceProcessors()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (MultiBurst.burst.raiseParallelism(pregenBurstParallelism(MultiBurstSupport.performanceProcessors()))) {
|
||||
PARALLELISM_HOLDERS.incrementAndGet();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
PARALLELISM_HOLDERS.incrementAndGet();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,13 +61,9 @@ public final class PregenPerformanceProfile {
|
||||
}
|
||||
|
||||
public static void apply(Engine engine) {
|
||||
if (engine != null) {
|
||||
prepareNoiseCacheInPlace(engine);
|
||||
}
|
||||
apply();
|
||||
if (engine != null) {
|
||||
IrisLogging.info("Pregen profile applied: noiseCacheSize="
|
||||
+ IrisSettings.get().getPerformance().getNoiseCacheSize());
|
||||
IrisLogging.info("Pregen profile applied: burstParallelism=" + MultiBurst.burst.parallelism());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,10 +74,4 @@ public final class PregenPerformanceProfile {
|
||||
static int pregenBurstParallelism(int performanceProcessors) {
|
||||
return Math.max(4, performanceProcessors * 2);
|
||||
}
|
||||
|
||||
private static void prepareNoiseCacheInPlace(Engine engine) {
|
||||
IrisComplex complex = Objects.requireNonNull(engine.getComplex(), "Pregeneration requires an active biome complex");
|
||||
int configuredChunks = Math.max(IrisSettings.get().getPerformance().getNoiseCacheSize(), 4_096);
|
||||
complex.ensureTerrainNoiseCacheSize(configuredChunks);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user