diff --git a/core/src/main/java/art/arcane/iris/generation/bake/BakeCache.java b/core/src/main/java/art/arcane/iris/generation/bake/BakeCache.java deleted file mode 100644 index 5054c537c..000000000 --- a/core/src/main/java/art/arcane/iris/generation/bake/BakeCache.java +++ /dev/null @@ -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); -} diff --git a/core/src/main/java/art/arcane/iris/generation/bake/BakeRegistry.java b/core/src/main/java/art/arcane/iris/generation/bake/BakeRegistry.java deleted file mode 100644 index 205982885..000000000 --- a/core/src/main/java/art/arcane/iris/generation/bake/BakeRegistry.java +++ /dev/null @@ -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 { - private final Object lock = new Object(); - private volatile IdentityHashMap 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 next = new IdentityHashMap<>(ids); - next.put(value, size); - ids = next; - return (short) size++; - } - } - - @SuppressWarnings("unchecked") - public T value(int id) { - return (T) values[id]; - } -} diff --git a/core/src/main/java/art/arcane/iris/generation/bake/ChunkBakeCache.java b/core/src/main/java/art/arcane/iris/generation/bake/ChunkBakeCache.java index b1466b5a7..32e3fa0b4 100644 --- a/core/src/main/java/art/arcane/iris/generation/bake/ChunkBakeCache.java +++ b/core/src/main/java/art/arcane/iris/generation/bake/ChunkBakeCache.java @@ -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 SPARSE = ThreadLocal.withInitial(() -> new int[1]); private final PlaneSource source; + private final ConcurrentClockCache entries; private final ThreadLocal recent = ThreadLocal.withInitial(Recent::new); - private volatile ConcurrentClockCache 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 current = entries; - if (current.capacity() >= chunks) { - return; - } - ConcurrentClockCache resized = new ConcurrentClockCache<>(chunks); - current.forEach(planes -> resized.putIfAbsent(key(planes.chunkX(), planes.chunkZ()), planes)); - entries = resized; - } - - public void clear() { - ConcurrentClockCache 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 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 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 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 entries; + + private void remember(long key, ChunkPlanes planes, int generation) { + this.key = key; + this.planes = planes; + this.generation = generation; + } } } diff --git a/core/src/main/java/art/arcane/iris/generation/bake/ChunkPlanes.java b/core/src/main/java/art/arcane/iris/generation/bake/ChunkPlanes.java index 6a72dd756..42427fcec 100644 --- a/core/src/main/java/art/arcane/iris/generation/bake/ChunkPlanes.java +++ b/core/src/main/java/art/arcane/iris/generation/bake/ChunkPlanes.java @@ -13,7 +13,6 @@ public final class ChunkPlanes { private final int chunkX; private final int chunkZ; private final AtomicReferenceArray 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 { diff --git a/core/src/main/java/art/arcane/iris/generation/bake/ColumnBake.java b/core/src/main/java/art/arcane/iris/generation/bake/ColumnBake.java deleted file mode 100644 index 749263925..000000000 --- a/core/src/main/java/art/arcane/iris/generation/bake/ColumnBake.java +++ /dev/null @@ -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) { - } -} diff --git a/core/src/main/java/art/arcane/iris/generation/bake/PlaneSource.java b/core/src/main/java/art/arcane/iris/generation/bake/PlaneSource.java index 5e98ffd08..7bd29f799 100644 --- a/core/src/main/java/art/arcane/iris/generation/bake/PlaneSource.java +++ b/core/src/main/java/art/arcane/iris/generation/bake/PlaneSource.java @@ -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(); } diff --git a/core/src/main/java/art/arcane/iris/generation/hydrology/HydrologyTileCache.java b/core/src/main/java/art/arcane/iris/generation/hydrology/HydrologyTileCache.java index 7f025926b..7d16d2f56 100644 --- a/core/src/main/java/art/arcane/iris/generation/hydrology/HydrologyTileCache.java +++ b/core/src/main/java/art/arcane/iris/generation/hydrology/HydrologyTileCache.java @@ -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 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> active, PendingLoad load) { if (load.owner() == Thread.currentThread()) { throw new IllegalStateException("Hydrology tile cache cannot close from an active load."); diff --git a/core/src/main/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyRuntime.java b/core/src/main/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyRuntime.java index 1703625a7..2d56e47ec 100644 --- a/core/src/main/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyRuntime.java +++ b/core/src/main/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyRuntime.java @@ -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()); } diff --git a/core/src/main/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyRuntimeContext.java b/core/src/main/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyRuntimeContext.java index 005da8f08..784395ea4 100644 --- a/core/src/main/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyRuntimeContext.java +++ b/core/src/main/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyRuntimeContext.java @@ -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); } } diff --git a/core/src/main/java/art/arcane/iris/generation/runtime/HydrologyNoiseCacheBudget.java b/core/src/main/java/art/arcane/iris/generation/runtime/HydrologyNoiseCacheBudget.java deleted file mode 100644 index 47e3656e7..000000000 --- a/core/src/main/java/art/arcane/iris/generation/runtime/HydrologyNoiseCacheBudget.java +++ /dev/null @@ -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); - } - } -} diff --git a/core/src/main/java/art/arcane/iris/generation/runtime/IrisComplex.java b/core/src/main/java/art/arcane/iris/generation/runtime/IrisComplex.java index c995c2e27..baec4cf00 100644 --- a/core/src/main/java/art/arcane/iris/generation/runtime/IrisComplex.java +++ b/core/src/main/java/art/arcane/iris/generation/runtime/IrisComplex.java @@ -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 biomeRegistry = new BakeRegistry<>(); - @Getter(AccessLevel.NONE) - private final transient BakeRegistry regionRegistry = new BakeRegistry<>(); - @Getter(AccessLevel.NONE) - private final transient BakeRegistry blockRegistry = new BakeRegistry<>(); - @Getter(AccessLevel.NONE) private final transient long childStyleSeed; @Getter(AccessLevel.NONE) private final transient Map> 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 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, Boolean> selected = new IdentityHashMap<>(); - List 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 short[] ids(Object[] values, BakeRegistry 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(); diff --git a/core/src/main/java/art/arcane/iris/world/pregen/PregenPerformanceProfile.java b/core/src/main/java/art/arcane/iris/world/pregen/PregenPerformanceProfile.java index 3fc3c3b2c..a876c8c3a 100644 --- a/core/src/main/java/art/arcane/iris/world/pregen/PregenPerformanceProfile.java +++ b/core/src/main/java/art/arcane/iris/world/pregen/PregenPerformanceProfile.java @@ -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); - } } diff --git a/core/src/test/java/art/arcane/iris/generation/bake/ChunkBakeCacheTest.java b/core/src/test/java/art/arcane/iris/generation/bake/ChunkBakeCacheTest.java new file mode 100644 index 000000000..83a785507 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/generation/bake/ChunkBakeCacheTest.java @@ -0,0 +1,105 @@ +package art.arcane.iris.generation.bake; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +public class ChunkBakeCacheTest { + private static final long MEGABYTE = 1L << 20; + private static final long GIGABYTE = 1L << 30; + + @Test + public void capacityChargesTheGeneratedChunkFootprintAgainstAHeapShare() { + assertEquals(5_041, ChunkBakeCache.capacityFor(2L * GIGABYTE)); + assertEquals(10_082, ChunkBakeCache.capacityFor(4L * GIGABYTE)); + assertEquals(15_123, ChunkBakeCache.capacityFor(6L * GIGABYTE)); + for (long heap = 512L * MEGABYTE; heap <= 64L * GIGABYTE; heap += 256L * MEGABYTE) { + int capacity = ChunkBakeCache.capacityFor(heap); + if (capacity > ChunkBakeCache.MINIMUM_CAPACITY) { + assertTrue("heap " + heap, capacity * ChunkBakeCache.BYTES_PER_ENTRY <= heap / ChunkBakeCache.HEAP_SHARE); + } + } + } + + @Test + public void capacityStaysBetweenTheFloorAndTheCeilingAndGrowsWithTheHeap() { + assertEquals(ChunkBakeCache.MINIMUM_CAPACITY, ChunkBakeCache.capacityFor(0L)); + assertEquals(ChunkBakeCache.MINIMUM_CAPACITY, ChunkBakeCache.capacityFor(512L * MEGABYTE)); + assertEquals(ChunkBakeCache.MAXIMUM_CAPACITY, ChunkBakeCache.capacityFor(64L * GIGABYTE)); + assertEquals(ChunkBakeCache.MAXIMUM_CAPACITY, ChunkBakeCache.capacityFor(Long.MAX_VALUE)); + int previous = 0; + for (long heap = 0L; heap <= 32L * GIGABYTE; heap += 128L * MEGABYTE) { + int capacity = ChunkBakeCache.capacityFor(heap); + assertTrue(capacity >= previous); + previous = capacity; + } + } + + @Test + public void clearRetiresPlanesThatThreadsStillRemember() { + ChunkBakeCache cache = new ChunkBakeCache(new ConstantSource(), 64); + double[] before = (double[]) cache.complete(BakePlane.BASE_HEIGHT, 3, -5); + ChunkPlanes.Plane remembered = cache.planeIfPresent(BakePlane.BASE_HEIGHT, 3, -5); + assertNotNull(remembered); + assertSame(before, cache.complete(BakePlane.BASE_HEIGHT, 3, -5)); + + cache.clear(); + + assertNull(cache.planeIfPresent(BakePlane.BASE_HEIGHT, 3, -5)); + double[] after = (double[]) cache.complete(BakePlane.BASE_HEIGHT, 3, -5); + assertNotSame(before, after); + assertEquals(before[17], after[17], 0D); + assertNotSame(remembered, cache.planeIfPresent(BakePlane.BASE_HEIGHT, 3, -5)); + } + + @Test + public void releasedEntriesHoldNoPlanes() { + ChunkPlanes planes = new ChunkPlanes(1, 1); + ConstantSource source = new ConstantSource(); + for (BakePlane kind : BakePlane.PLANES) { + planes.complete(kind, source); + assertNotNull(planes.planeIfPresent(kind)); + } + + planes.release(); + + for (BakePlane kind : BakePlane.PLANES) { + assertNull(planes.planeIfPresent(kind)); + } + } + + @Test + public void clearLeavesAnEmptyCacheOfTheSameCapacity() { + ChunkBakeCache cache = new ChunkBakeCache(new ConstantSource(), 64); + for (int chunk = 0; chunk < 8; chunk++) { + cache.complete(BakePlane.HEIGHT, chunk, -chunk); + } + assertEquals(8L, cache.census()[BakePlane.PLANES.length]); + + cache.clear(); + + assertEquals(0L, cache.census()[BakePlane.PLANES.length]); + assertEquals(64, cache.capacity()); + } + + private static final class ConstantSource implements PlaneSource { + @Override + public void fill(BakePlane plane, int chunkX, int chunkZ, long[] missing, Object values) { + if (values instanceof double[] doubles) { + for (int index = 0; index < 256; index++) { + doubles[index] = chunkX * 1_000D + chunkZ + index; + } + } + } + + @Override + public boolean mainThread() { + return false; + } + } +} diff --git a/core/src/test/java/art/arcane/iris/generation/hydrology/HydrologyDiagnosticCacheTest.java b/core/src/test/java/art/arcane/iris/generation/hydrology/HydrologyDiagnosticCacheTest.java index 6f5ff054b..9dc04d301 100644 --- a/core/src/test/java/art/arcane/iris/generation/hydrology/HydrologyDiagnosticCacheTest.java +++ b/core/src/test/java/art/arcane/iris/generation/hydrology/HydrologyDiagnosticCacheTest.java @@ -44,8 +44,6 @@ public class HydrologyDiagnosticCacheTest { try (HydrologyTileCache cache = new HydrologyTileCache(planner, 4)) { assertSame(tile, cache.get(key)); verify(planner, never()).diagnosticCandidates(tile); - AtomicInteger preparations = new AtomicInteger(); - cache.setTerrainPreparation(preparations::incrementAndGet); List result = cache.diagnosticCandidates(key); supplied.clear(); assertEquals(List.of(candidate), result); @@ -53,7 +51,6 @@ public class HydrologyDiagnosticCacheTest { assertSame(result, cache.diagnosticCandidates(key)); verify(planner, times(1)).plan(key); verify(planner, times(1)).diagnosticCandidates(tile); - assertEquals(1, preparations.get()); } } diff --git a/core/src/test/java/art/arcane/iris/generation/hydrology/HydrologyTileCacheTest.java b/core/src/test/java/art/arcane/iris/generation/hydrology/HydrologyTileCacheTest.java index 817a6c7e7..224bb2738 100644 --- a/core/src/test/java/art/arcane/iris/generation/hydrology/HydrologyTileCacheTest.java +++ b/core/src/test/java/art/arcane/iris/generation/hydrology/HydrologyTileCacheTest.java @@ -99,7 +99,6 @@ public class HydrologyTileCacheTest { new PreparedHydrologyTileStore(root, scope, 64).save(tile); try (HydrologyTileCache cache = new HydrologyTileCache(planner, 4)) { cache.enableSharedCache(scope, root); - cache.setTerrainPreparation(() -> { throw new AssertionError("Restoration must not sample terrain"); }); HydrologyTile restored = cache.get(key); assertEquals(tile.key(), restored.key()); assertEquals(tile.footprint(), restored.footprint()); @@ -877,13 +876,6 @@ public class HydrologyTileCacheTest { when(tile.tileSize()).thenReturn(64); HydrologyTileCache first = new HydrologyTileCache(firstPlanner, 4, null, null, scope); HydrologyTileCache second = new HydrologyTileCache(secondPlanner, 4, null, null, scope); - AtomicInteger terrainPreparations = new AtomicInteger(); - first.setTerrainPreparation(terrainPreparations::incrementAndGet); - second.setTerrainPreparation(() -> { - throw new AssertionError("Shared tile reuse must not prepare terrain caches"); - }); - - assertEquals(0, terrainPreparations.get()); assertSame(tile, first.get(key)); assertSame(tile, second.get(key)); @@ -891,7 +883,6 @@ public class HydrologyTileCacheTest { verify(firstPlanner, times(1)).plan(key); verify(secondPlanner, never()).plan(key); verify(secondPlanner, times(1)).reuseResolvedTile(tile); - assertEquals(1, terrainPreparations.get()); } @Test(timeout = 10000L) diff --git a/core/src/test/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyPhysicalOceanTest.java b/core/src/test/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyPhysicalOceanTest.java index a7403c145..2f099846e 100644 --- a/core/src/test/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyPhysicalOceanTest.java +++ b/core/src/test/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyPhysicalOceanTest.java @@ -159,7 +159,7 @@ public class IrisHydrologyPhysicalOceanTest { shore.setLoadKey("shore"); return new IrisHydrologyRuntime(new IrisHydrologyRuntimeContext(17L, 768, dimension, data, (x, z, height) -> new IrisHydrologyNaturalSample(height, oceanIntent.isOcean(x, z), shore, region), - heights, (x, z) -> "coast", oceanIntent, footprint -> null, () -> false, () -> {})); + heights, (x, z) -> "coast", oceanIntent, footprint -> null, () -> false)); } private static IrisHydrologyRoutingTerrainSampler sampler(IrisHydrologyRuntime runtime) throws Exception { diff --git a/core/src/test/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyPolicyCacheTest.java b/core/src/test/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyPolicyCacheTest.java index 788e8d7a0..bc294a684 100644 --- a/core/src/test/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyPolicyCacheTest.java +++ b/core/src/test/java/art/arcane/iris/generation/hydrology/runtime/IrisHydrologyPolicyCacheTest.java @@ -229,7 +229,7 @@ public class IrisHydrologyPolicyCacheTest { private IrisHydrologyRuntime runtime() { return new IrisHydrologyRuntime(new IrisHydrologyRuntimeContext(17L, 768, dimension, data, (x, z, height) -> new IrisHydrologyNaturalSample(height, false, biome.get(), region.get()), - (x, z) -> 180D, (x, z) -> "constant", (x, z) -> false, footprint -> null, () -> false, () -> {})); + (x, z) -> 180D, (x, z) -> "constant", (x, z) -> false, footprint -> null, () -> false)); } } diff --git a/core/src/test/java/art/arcane/iris/generation/runtime/HydrologyNoiseCacheBudgetTest.java b/core/src/test/java/art/arcane/iris/generation/runtime/HydrologyNoiseCacheBudgetTest.java deleted file mode 100644 index 23df1cd58..000000000 --- a/core/src/test/java/art/arcane/iris/generation/runtime/HydrologyNoiseCacheBudgetTest.java +++ /dev/null @@ -1,96 +0,0 @@ -package art.arcane.iris.generation.runtime; - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; - -public class HydrologyNoiseCacheBudgetTest { - @Test - public void reservationsClampToTheRemainingSharedCapacity() { - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(4_096L * 15L + 4_095L); - HydrologyNoiseCacheBudget.Reservation first = budget.reserve(4, 10); - HydrologyNoiseCacheBudget.Reservation second = budget.reserve(2, 10); - assertEquals(3, first.additionalChunks()); - assertEquals(1, second.additionalChunks()); - assertEquals(14L * 4_096L, budget.reservedBytes()); - first.close(); - first.close(); - assertEquals(2L * 4_096L, budget.reservedBytes()); - second.close(); - assertEquals(0L, budget.reservedBytes()); - } - - @Test - public void emptyBudgetAndZeroDemandReserveNothing() { - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(0L); - try (HydrologyNoiseCacheBudget.Reservation reservation = budget.reserve(5, 32_768)) { - assertEquals(0, reservation.additionalChunks()); - } - HydrologyNoiseCacheBudget available = new HydrologyNoiseCacheBudget(1_000_000L); - try (HydrologyNoiseCacheBudget.Reservation reservation = available.reserve(5, 0)) { - assertEquals(0, reservation.additionalChunks()); - } - assertEquals(0L, budget.reservedBytes()); - assertEquals(0L, available.reservedBytes()); - } - - @Test - public void requestsCannotExceedTheCapacityLimit() { - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(Long.MAX_VALUE); - try (HydrologyNoiseCacheBudget.Reservation reservation = budget.reserve(5, 32_768)) { - assertEquals(32_768, reservation.additionalChunks()); - assertEquals(5L * 32_768L * 4_096L, budget.reservedBytes()); - } - assertThrows(IllegalArgumentException.class, () -> budget.reserve(0, 10)); - assertThrows(IllegalArgumentException.class, () -> budget.reserve(5, -1)); - assertThrows(IllegalArgumentException.class, () -> budget.reserve(5, 32_769)); - assertThrows(IllegalArgumentException.class, () -> new HydrologyNoiseCacheBudget(-1L)); - } - - @Test(timeout = 10_000L) - public void concurrentOwnersNeverOversubscribeAndReleaseIndependently() throws Exception { - long maximumBytes = 4_096L * 503L; - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(maximumBytes); - CountDownLatch start = new CountDownLatch(1); - List> pending = new ArrayList<>(); - try (ExecutorService workers = Executors.newFixedThreadPool(16)) { - for (int index = 0; index < 32; index++) { - pending.add(workers.submit(() -> { - assertTrue(start.await(5L, TimeUnit.SECONDS)); - return budget.reserve(5, 50); - })); - } - start.countDown(); - List reservations = new ArrayList<>(); - long expectedBytes = 0L; - for (Future future : pending) { - HydrologyNoiseCacheBudget.Reservation reservation = future.get(5L, TimeUnit.SECONDS); - reservations.add(reservation); - expectedBytes += reservation.additionalChunks() * 5L * 4_096L; - } - assertEquals(expectedBytes, budget.reservedBytes()); - assertTrue(expectedBytes <= maximumBytes); - List> releases = new ArrayList<>(); - for (HydrologyNoiseCacheBudget.Reservation reservation : reservations) { - releases.add(workers.submit(() -> { - reservation.close(); - reservation.close(); - })); - } - for (Future release : releases) { - release.get(5L, TimeUnit.SECONDS); - } - assertEquals(0L, budget.reservedBytes()); - } - } -} diff --git a/core/src/test/java/art/arcane/iris/generation/runtime/IrisComplexBakeParityTest.java b/core/src/test/java/art/arcane/iris/generation/runtime/IrisComplexBakeParityTest.java index ec36ad135..aee6fcf19 100644 --- a/core/src/test/java/art/arcane/iris/generation/runtime/IrisComplexBakeParityTest.java +++ b/core/src/test/java/art/arcane/iris/generation/runtime/IrisComplexBakeParityTest.java @@ -1,11 +1,11 @@ package art.arcane.iris.generation.runtime; import art.arcane.iris.configuration.IrisSettings; -import art.arcane.iris.generation.bake.BakePlane; import art.arcane.iris.generation.bake.GeneratorEvaluations; import art.arcane.iris.generation.block.B; import art.arcane.iris.generation.image.IrisImageMapApplication; import art.arcane.iris.generation.image.IrisImageMapRuntime; +import art.arcane.iris.generation.stream.ChunkFillableDoubleStream2D; import art.arcane.iris.generation.terrain.IrisDimension; import art.arcane.iris.pack.loading.IrisData; import art.arcane.iris.spi.IrisPlatform; @@ -67,7 +67,8 @@ public class IrisComplexBakeParityTest { for (int chunk = 0; chunk < 48; chunk++) { int chunkX = random.nextInt(-400, 400); int chunkZ = random.nextInt(-400, 400); - double[] baked = (double[]) batched.complex.bakePlane(BakePlane.BASE_HEIGHT, chunkX, chunkZ); + double[] baked = new double[256]; + batched.baseHeights().fillChunkDoubles(chunkX << 4, chunkZ << 4, baked); for (int index = 0; index < 256; index++) { int x = (chunkX << 4) + (index & 15); int z = (chunkZ << 4) + (index >> 4); @@ -88,7 +89,7 @@ public class IrisComplexBakeParityTest { int chunkX = random.nextInt(-400, 400); int chunkZ = random.nextInt(-400, 400); GeneratorEvaluations.Snapshot before = GeneratorEvaluations.snapshot(); - batched.complex.bakePlane(BakePlane.BASE_HEIGHT, chunkX, chunkZ); + batched.baseHeights().fillChunkDoubles(chunkX << 4, chunkZ << 4, new double[256]); GeneratorEvaluations.Snapshot afterBatch = GeneratorEvaluations.snapshot(); for (int index = 0; index < 256; index++) { scalar.complex.getBaseTerrainHeightStream().getDouble((chunkX << 4) + (index & 15), (chunkZ << 4) + (index >> 4)); @@ -195,6 +196,10 @@ public class IrisComplexBakeParityTest { doReturn(complex).when(engine).getComplex(); } + private ChunkFillableDoubleStream2D baseHeights() { + return (ChunkFillableDoubleStream2D) complex.getBaseTerrainHeightStream(); + } + @Override public void close() { complex.close(); diff --git a/core/src/test/java/art/arcane/iris/generation/runtime/IrisComplexHydrologyNoiseCacheTest.java b/core/src/test/java/art/arcane/iris/generation/runtime/IrisComplexHydrologyNoiseCacheTest.java deleted file mode 100644 index e18d0bc1e..000000000 --- a/core/src/test/java/art/arcane/iris/generation/runtime/IrisComplexHydrologyNoiseCacheTest.java +++ /dev/null @@ -1,398 +0,0 @@ -package art.arcane.iris.generation.runtime; - -import art.arcane.iris.configuration.IrisSettings; -import art.arcane.iris.world.pregen.PregenPerformanceProfile; -import art.arcane.iris.generation.bake.ChunkBakeCache; -import art.arcane.iris.generation.bake.PlaneSource; -import art.arcane.iris.generation.biome.IrisBiome; -import art.arcane.iris.generation.hydrology.HydrologyPlanner; -import art.arcane.iris.generation.hydrology.HydrologyPlannerSettings; -import art.arcane.iris.generation.hydrology.HydrologyTile; -import art.arcane.iris.generation.hydrology.HydrologyTileCache; -import art.arcane.iris.generation.hydrology.HydrologyTileKey; -import art.arcane.iris.generation.hydrology.runtime.IrisHydrologyRuntime; -import art.arcane.iris.generation.stream.CachedDoubleStream2D; -import art.arcane.iris.generation.stream.CachedStream2D; -import art.arcane.iris.generation.terrain.InferredType; -import art.arcane.iris.generation.terrain.IrisRegion; -import art.arcane.iris.spi.IrisServices; -import art.arcane.volmlib.util.stream.ProceduralStream; -import org.junit.Test; -import org.mockito.MockedStatic; - -import java.lang.reflect.Field; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertSame; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.CALLS_REAL_METHODS; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; - -public class IrisComplexHydrologyNoiseCacheTest { - @Test - public void pregenGrowsTheBackingHeightCacheWithoutReplacingItsThreadAwareStream() throws Exception { - IrisSettings previousSettings = IrisSettings.settings; - IrisSettings.settings = new IrisSettings(); - IrisSettings.get().getPerformance().setNoiseCacheSize(1_024); - try (Fixture fixture = new Fixture(1_024)) { - ProceduralStream wrapper = fixture.complex.getRawHeightStream(); - assertEquals(13D, fixture.height.getDouble(18, 5), 0D); - - PregenPerformanceProfile.apply(fixture.engine); - - assertTrue(fixture.bakes.capacity() >= 4_096); - assertEquals(1_024L * 256L, fixture.biomes.getMaxSize()); - assertSame(wrapper, fixture.complex.getRawHeightStream()); - assertEquals(47D, wrapper.getDouble(18, 5), 0D); - assertEquals(13D, fixture.height.getDouble(18, 5), 0D); - } finally { - PregenPerformanceProfile.restore(); - IrisSettings.settings = previousSettings; - } - } - - @Test - public void pregenPreservesActiveHydrologyGrants() throws Exception { - try (Fixture fixture = new Fixture(1_024)) { - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(Long.MAX_VALUE); - fixture.complex.expandHydrologyNoiseCaches(budget); - long reservedBytes = budget.reservedBytes(); - - fixture.complex.ensureTerrainNoiseCacheSize(4_096); - - fixture.assertCapacities(32_768); - assertTrue(fixture.bakes.capacity() >= 4_096); - assertEquals(reservedBytes, budget.reservedBytes()); - fixture.complex.close(); - assertEquals(0L, budget.reservedBytes()); - } - } - - @Test - public void pregenPreservesLargerRuntimeCacheCapacities() throws Exception { - try (Fixture fixture = new Fixture(65_536)) { - fixture.complex.ensureTerrainNoiseCacheSize(4_096); - - fixture.assertCapacities(65_536); - assertTrue(fixture.bakes.capacity() >= 65_536); - } - } - - @Test - public void idleRuntimeReservesOnlyOnDemandAndRestoresAfterHydrologyDrains() throws Exception { - try (Fixture fixture = new Fixture(1_024)) { - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(Long.MAX_VALUE); - HydrologyTileCache cache = fixture.planningCache(budget); - fixture.assertCapacities(1_024); - assertEquals(0L, budget.reservedBytes()); - cache.get(new HydrologyTileKey(0, 0)); - cache.get(new HydrologyTileKey(0, 0)); - fixture.assertCapacities(32_768); - assertEquals(4L * (32_768L - 1_024L) * 4_096L, budget.reservedBytes()); - doAnswer(invocation -> { - fixture.assertCapacities(32_768); - assertEquals(4L * (32_768L - 1_024L) * 4_096L, budget.reservedBytes()); - cache.close(); - return null; - }).when(fixture.hydrology).close(); - fixture.complex.close(); - fixture.assertCapacities(1_024); - assertEquals(0L, budget.reservedBytes()); - doNothing().when(fixture.hydrology).close(); - fixture.complex.close(); - fixture.complex.expandHydrologyNoiseCaches(budget); - fixture.assertCapacities(1_024); - assertEquals(0L, budget.reservedBytes()); - } - } - - @Test(timeout = 10_000L) - public void concurrentCacheExpansionsShareOneReservation() throws Exception { - try (Fixture fixture = new Fixture(1_024); - ExecutorService callers = Executors.newFixedThreadPool(2)) { - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(Long.MAX_VALUE); - CountDownLatch entered = new CountDownLatch(2); - CountDownLatch release = new CountDownLatch(1); - Runnable prepare = () -> { - entered.countDown(); - await(release); - fixture.complex.expandHydrologyNoiseCaches(budget); - }; - Future first = callers.submit(prepare); - Future second = callers.submit(prepare); - assertTrue(entered.await(5L, TimeUnit.SECONDS)); - assertEquals(0L, budget.reservedBytes()); - release.countDown(); - first.get(5L, TimeUnit.SECONDS); - second.get(5L, TimeUnit.SECONDS); - fixture.assertCapacities(32_768); - assertEquals(4L * (32_768L - 1_024L) * 4_096L, budget.reservedBytes()); - fixture.complex.close(); - assertEquals(0L, budget.reservedBytes()); - } - } - - @Test(timeout = 10_000L) - public void closeDrainsAnAdmittedFirstDemandBeforeReleasingItsGrant() throws Exception { - try (Fixture fixture = new Fixture(1_024); - ExecutorService callers = Executors.newFixedThreadPool(2)) { - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(Long.MAX_VALUE); - HydrologyTileCache cache = fixture.planningCache(budget); - CountDownLatch preparing = new CountDownLatch(1); - CountDownLatch release = new CountDownLatch(1); - CountDownLatch closing = new CountDownLatch(1); - cache.setTerrainPreparation(() -> { - preparing.countDown(); - await(release); - fixture.complex.expandHydrologyNoiseCaches(budget); - }); - doAnswer(invocation -> { - closing.countDown(); - cache.close(); - return null; - }).when(fixture.hydrology).close(); - Future demand = callers.submit(() -> cache.get(new HydrologyTileKey(0, 0))); - assertTrue(preparing.await(5L, TimeUnit.SECONDS)); - Future close = callers.submit(fixture.complex::close); - assertTrue(closing.await(5L, TimeUnit.SECONDS)); - try { - assertThrows(TimeoutException.class, () -> close.get(100L, TimeUnit.MILLISECONDS)); - assertEquals(0L, budget.reservedBytes()); - } finally { - release.countDown(); - } - demand.get(5L, TimeUnit.SECONDS); - close.get(5L, TimeUnit.SECONDS); - fixture.assertCapacities(1_024); - assertEquals(0L, budget.reservedBytes()); - } - } - - @Test - public void volumetricNaturalHeightReservesItsDistinctFifthCache() throws Exception { - try (Fixture fixture = new Fixture(1_024)) { - CachedDoubleStream2D natural = fixture.heightCache(1_024); - fixture.complex.setUnblendedNaturalHeightStream(natural); - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(Long.MAX_VALUE); - fixture.complex.expandHydrologyNoiseCaches(budget); - fixture.assertCapacities(32_768); - assertEquals(32_768L * 256L, natural.getMaxSize()); - assertEquals(5L * (32_768L - 1_024L) * 4_096L, budget.reservedBytes()); - fixture.complex.close(); - assertEquals(1_024L * 256L, natural.getMaxSize()); - assertEquals(0L, budget.reservedBytes()); - } - } - - @Test - public void partialGrantPreservesTheConfiguredFloorForEveryCache() throws Exception { - try (Fixture fixture = new Fixture(1_024)) { - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(4L * 80L * 4_096L + 4_095L); - fixture.complex.expandHydrologyNoiseCaches(budget); - fixture.assertCapacities(1_104); - assertEquals(4L * 80L * 4_096L, budget.reservedBytes()); - fixture.complex.close(); - fixture.assertCapacities(1_024); - assertEquals(0L, budget.reservedBytes()); - } - } - - @Test - public void configuredCapacityAboveTargetRemainsUnchanged() throws Exception { - try (Fixture fixture = new Fixture(65_536)) { - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(Long.MAX_VALUE); - fixture.complex.expandHydrologyNoiseCaches(budget); - fixture.assertCapacities(65_536); - assertEquals(0L, budget.reservedBytes()); - fixture.complex.close(); - fixture.assertCapacities(65_536); - } - } - - @Test - public void detachedAndNonHydrologyRuntimesDoNotReserve() throws Exception { - try (Fixture fixture = new Fixture(1_024)) { - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(Long.MAX_VALUE); - setField(fixture.complex, "detached", true); - fixture.complex.expandHydrologyNoiseCaches(budget); - fixture.complex.reserveHydrologyBake(budget); - fixture.assertCapacities(1_024); - assertEquals(1_024, fixture.bakes.capacity()); - setField(fixture.complex, "detached", false); - fixture.complex.setHydrologyRuntime(null); - fixture.complex.expandHydrologyNoiseCaches(budget); - fixture.complex.reserveHydrologyBake(budget); - fixture.assertCapacities(1_024); - assertEquals(1_024, fixture.bakes.capacity()); - assertEquals(0L, budget.reservedBytes()); - } - } - - @Test - public void plannerReservationGrowsTheBakeToTheHydrologyTarget() throws Exception { - try (Fixture fixture = new Fixture(1_024)) { - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(Long.MAX_VALUE); - fixture.complex.reserveHydrologyBake(budget); - fixture.complex.reserveHydrologyBake(budget); - assertEquals(32_768, fixture.bakes.capacity()); - assertEquals(2L * (32_768L - 1_024L) * 4_096L, budget.reservedBytes()); - fixture.complex.ensureTerrainNoiseCacheSize(4_096); - assertEquals(32_768, fixture.bakes.capacity()); - fixture.complex.close(); - assertEquals(0L, budget.reservedBytes()); - fixture.complex.reserveHydrologyBake(budget); - assertEquals(0L, budget.reservedBytes()); - } - } - - @Test - public void plannerReservationTakesOnlyTheGrantedShareOfTheBudget() throws Exception { - try (Fixture fixture = new Fixture(1_024)) { - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(2L * 80L * 4_096L + 4_095L); - fixture.complex.reserveHydrologyBake(budget); - assertEquals(1_104, fixture.bakes.capacity()); - assertEquals(2L * 80L * 4_096L, budget.reservedBytes()); - fixture.complex.close(); - assertEquals(0L, budget.reservedBytes()); - } - } - - @Test - public void plannerReservationLeavesLargerBakesUnchanged() throws Exception { - try (Fixture fixture = new Fixture(65_536)) { - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(Long.MAX_VALUE); - fixture.complex.reserveHydrologyBake(budget); - assertEquals(65_536, fixture.bakes.capacity()); - assertEquals(0L, budget.reservedBytes()); - } - } - - @Test - public void failedHydrologyDrainRetainsTheReservationUntilRetry() throws Exception { - try (Fixture fixture = new Fixture(1_024)) { - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(Long.MAX_VALUE); - HydrologyTileCache cache = fixture.planningCache(budget); - cache.get(new HydrologyTileKey(0, 0)); - doThrow(new IllegalStateException("Hydrology is still draining")).when(fixture.hydrology).close(); - assertThrows(IllegalStateException.class, fixture.complex::close); - fixture.assertCapacities(32_768); - assertEquals(4L * (32_768L - 1_024L) * 4_096L, budget.reservedBytes()); - doAnswer(invocation -> { - cache.close(); - return null; - }).when(fixture.hydrology).close(); - fixture.complex.close(); - fixture.assertCapacities(1_024); - assertEquals(0L, budget.reservedBytes()); - } - } - - @Test - public void mappedStreamRetainsOnlyTheFinalCacheAtTheExpandedCapacity() throws Exception { - try (Fixture fixture = new Fixture(1_024)) { - CachedStream2D inner = fixture.biomes; - CachedStream2D mapped = new CachedStream2D<>("mapped", fixture.engine, inner, 1_024); - fixture.complex.setBaseBiomeStream(mapped); - HydrologyNoiseCacheBudget budget = new HydrologyNoiseCacheBudget(Long.MAX_VALUE); - fixture.complex.expandHydrologyNoiseCaches(budget); - assertEquals(1_024L * 256L, inner.getMaxSize()); - assertEquals(32_768L * 256L, mapped.getMaxSize()); - fixture.complex.close(); - assertEquals(1_024L * 256L, mapped.getMaxSize()); - } - } - - private static void setField(IrisComplex complex, String name, Object value) throws Exception { - Field field = IrisComplex.class.getDeclaredField(name); - field.setAccessible(true); - field.set(complex, value); - } - - private static void await(CountDownLatch latch) { - try { - assertTrue(latch.await(5L, TimeUnit.SECONDS)); - } catch (InterruptedException failure) { - Thread.currentThread().interrupt(); - throw new IllegalStateException(failure); - } - } - - private static final class Fixture implements AutoCloseable { - private final MockedStatic services = mockStatic(IrisServices.class); - private final Engine engine = mock(Engine.class); - private final IrisComplex complex = mock(IrisComplex.class, CALLS_REAL_METHODS); - private final IrisHydrologyRuntime hydrology = mock(IrisHydrologyRuntime.class); - private final CachedStream2D biomes; - private final CachedStream2D regions; - private final CachedStream2D bridge; - private final CachedDoubleStream2D height; - private final ChunkBakeCache bakes; - - private Fixture(int capacity) throws Exception { - services.when(() -> IrisServices.get(PreservationRegistry.class)).thenReturn(mock(PreservationRegistry.class)); - biomes = objectCache(capacity); - regions = objectCache(capacity); - bridge = objectCache(capacity); - height = heightCache(capacity); - bakes = new ChunkBakeCache(mock(PlaneSource.class), capacity); - complex.setNaturalHeightStream(height); - complex.setHeightStream(ProceduralStream.ofDouble((x, z) -> 47D)); - setField(complex, "bakeCache", bakes); - when(engine.getComplex()).thenReturn(complex); - complex.setBaseBiomeStream(biomes); - complex.setRegionStream(regions); - complex.setBridgeStream(bridge); - complex.setBaseTerrainHeightStream(height); - complex.setUnblendedNaturalHeightStream(height); - complex.setHydrologyRuntime(hydrology); - } - - @Override - public void close() { - services.close(); - } - - private CachedStream2D objectCache(int capacity) { - @SuppressWarnings("unchecked") - ProceduralStream source = mock(ProceduralStream.class); - return new CachedStream2D<>("cache", engine, source, capacity); - } - - private CachedDoubleStream2D heightCache(int capacity) { - return new CachedDoubleStream2D("height", engine, ProceduralStream.ofDouble((x, z) -> x - z), capacity); - } - - private HydrologyTileCache planningCache(HydrologyNoiseCacheBudget budget) { - HydrologyPlanner planner = mock(HydrologyPlanner.class); - when(planner.settings()).thenReturn(HydrologyPlannerSettings.defaults()); - when(planner.plan(any(HydrologyTileKey.class))).thenReturn(mock(HydrologyTile.class)); - HydrologyTileCache cache = new HydrologyTileCache(planner, 4); - cache.setTerrainPreparation(() -> complex.expandHydrologyNoiseCaches(budget)); - doAnswer(invocation -> { - cache.close(); - return null; - }).when(hydrology).close(); - return cache; - } - - private void assertCapacities(int chunks) { - assertEquals(chunks * 256L, biomes.getMaxSize()); - assertEquals(chunks * 256L, regions.getMaxSize()); - assertEquals(chunks * 256L, bridge.getMaxSize()); - assertEquals(chunks * 256L, height.getMaxSize()); - } - } -} diff --git a/core/src/test/java/art/arcane/iris/generation/runtime/IrisEngineGenerationRuntimeScopeTest.java b/core/src/test/java/art/arcane/iris/generation/runtime/IrisEngineGenerationRuntimeScopeTest.java index 9b0f9177b..c8a0ea2c2 100644 --- a/core/src/test/java/art/arcane/iris/generation/runtime/IrisEngineGenerationRuntimeScopeTest.java +++ b/core/src/test/java/art/arcane/iris/generation/runtime/IrisEngineGenerationRuntimeScopeTest.java @@ -86,7 +86,7 @@ public class IrisEngineGenerationRuntimeScopeTest { when(historical.complex.getHydrologyRuntime()).thenReturn(hydrology); IrisHydrologyRuntimeContext context = new IrisHydrologyRuntimeContext(17L, 384, historical.dimension, historical.data, (x, z, height) -> null, (x, z) -> 80D, (x, z) -> "constant", - (x, z) -> false, footprint -> null, () -> false, () -> {}); + (x, z) -> false, footprint -> null, () -> false); Field runtimeContext = IrisHydrologyRuntime.class.getDeclaredField("context"); runtimeContext.setAccessible(true); runtimeContext.set(hydrology, context); diff --git a/core/src/test/java/art/arcane/iris/world/pregen/PregenPerformanceProfileTest.java b/core/src/test/java/art/arcane/iris/world/pregen/PregenPerformanceProfileTest.java index 21f574cc0..a2cff4331 100644 --- a/core/src/test/java/art/arcane/iris/world/pregen/PregenPerformanceProfileTest.java +++ b/core/src/test/java/art/arcane/iris/world/pregen/PregenPerformanceProfileTest.java @@ -15,7 +15,6 @@ import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -46,13 +45,17 @@ public class PregenPerformanceProfileTest { } @Test - public void directEngineProfileResizesActualTerrainCachesWithoutRebuildingHistory() { - PregenPerformanceProfile.apply(engine); + public void directEngineProfileLeavesTerrainCachesAndHistoryUntouched() { + try { + PregenPerformanceProfile.apply(engine); - assertEquals(4_096, IrisSettings.get().getPerformance().getNoiseCacheSize()); - verify(complex).ensureTerrainNoiseCacheSize(4_096); - verify(complex, never()).getHeightStream(); - verify(engine, never()).hotloadComplex(); + assertEquals(1_024, IrisSettings.get().getPerformance().getNoiseCacheSize()); + verify(engine, never()).getComplex(); + verify(complex, never()).getHeightStream(); + verify(engine, never()).hotloadComplex(); + } finally { + PregenPerformanceProfile.restore(); + } } @Test @@ -60,42 +63,26 @@ public class PregenPerformanceProfileTest { PlatformChunkGenerator generator = mock(PlatformChunkGenerator.class); when(generator.getEngine()).thenReturn(engine); - PregenPerformanceProfile.applyToGenerator(generator); + try { + PregenPerformanceProfile.applyToGenerator(generator); - verify(complex).ensureTerrainNoiseCacheSize(4_096); - verify(generator, never()).hotloadComplexAsync(30L, TimeUnit.SECONDS); - verify(engine, never()).hotloadComplex(); + verify(generator, never()).hotloadComplexAsync(30L, TimeUnit.SECONDS); + verify(engine, never()).hotloadComplex(); + } finally { + PregenPerformanceProfile.restore(); + } } @Test - public void largerConfiguredCachesRemainLarger() { - IrisSettings.get().getPerformance().setNoiseCacheSize(8_192); - - PregenPerformanceProfile.apply(engine); - - assertEquals(8_192, IrisSettings.get().getPerformance().getNoiseCacheSize()); - verify(complex).ensureTerrainNoiseCacheSize(8_192); - } - - @Test - public void failedCachePreparationDoesNotChangeGlobalTuningOrAcquireAParallelismHold() { - int holders = PregenPerformanceProfile.parallelismHolders(); - int parallelism = MultiBurst.burst.parallelism(); - doThrow(new IllegalStateException("Runtime closed")) - .when(complex).ensureTerrainNoiseCacheSize(4_096); - - assertThrows(IllegalStateException.class, () -> PregenPerformanceProfile.apply(engine)); + public void enginesCreatedDuringOrAfterPregenerationKeepTheConfiguredNoiseCacheSize() { + try { + PregenPerformanceProfile.applyToGenerator(null); + assertEquals(1_024, IrisSettings.get().getPerformance().getNoiseCacheSize()); + } finally { + PregenPerformanceProfile.restore(); + } assertEquals(1_024, IrisSettings.get().getPerformance().getNoiseCacheSize()); - assertEquals(parallelism, MultiBurst.burst.parallelism()); - assertEquals(holders, PregenPerformanceProfile.parallelismHolders()); - } - - @Test - public void profileBeforeWorldCreationStillAppliesGlobalTuning() { - PregenPerformanceProfile.applyToGenerator(null); - - assertEquals(4_096, IrisSettings.get().getPerformance().getNoiseCacheSize()); } @Test