Merged bench harness

- Probe stage timings, counters, hydrology preplan, router and production buffer options

# Conflicts:
#	core/src/main/java/art/arcane/iris/generation/context/ChunkContext.java
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-21 23:03:48 -04:00
commit 6cc3adf76b
17 files changed
+1097 -103

No files matched your search

@@ -1,6 +1,7 @@
package art.arcane.iris.generation.hydrology;
import art.arcane.iris.generation.runtime.GenerationClosedException;
import art.arcane.iris.generation.runtime.GenerationCounter;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.generation.concurrent.MultiBurst;
import art.arcane.iris.generation.stream.ProvisionalSampling;
@@ -499,6 +500,9 @@ public final class HydrologyTileCache implements AutoCloseable {
}
public Optional<HydrologyColumnSample> columnAt(int blockX, int blockZ) {
if (GenerationCounter.ENABLED) {
GenerationCounter.HYDROLOGY_LOOKUPS.increment();
}
return chunkColumns(blockX, blockZ).columnAt(blockX, blockZ);
}
@@ -507,6 +511,9 @@ public final class HydrologyTileCache implements AutoCloseable {
if (!waitsForbidden()) {
return HydrologyColumnSnapshot.ready(columnAt(blockX, blockZ).orElse(null));
}
if (GenerationCounter.ENABLED) {
GenerationCounter.HYDROLOGY_LOOKUPS.increment();
}
int chunkX = Math.floorDiv(blockX, CHUNK_SIZE);
int chunkZ = Math.floorDiv(blockZ, CHUNK_SIZE);
ChunkColumns local = localChunks(cacheEpoch.get()).get(chunkX, chunkZ);
@@ -24,63 +24,65 @@ import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.DoubleAdder;
import java.util.concurrent.atomic.LongAdder;
@Data
public class EngineMetrics {
private final AtomicRollingSequence total;
private final AtomicRollingSequence updates;
private final AtomicRollingSequence terrain;
private final AtomicRollingSequence biome;
private final AtomicRollingSequence parallax;
private final AtomicRollingSequence parallaxInsert;
private final AtomicRollingSequence post;
private final AtomicRollingSequence perfection;
private final AtomicRollingSequence api;
private final AtomicRollingSequence decoration;
private final AtomicRollingSequence cave;
private final AtomicRollingSequence ravine;
private final AtomicRollingSequence deposit;
private final AtomicRollingSequence carveResolve;
private final AtomicRollingSequence carveApply;
private final AtomicRollingSequence noiseHeightInterpolate;
private final AtomicRollingSequence noiseHeightGenerator;
private final AtomicRollingSequence contextPrefill;
private final AtomicRollingSequence contextPrefillHeight;
private final AtomicRollingSequence contextPrefillBiome;
private final AtomicRollingSequence contextPrefillRock;
private final AtomicRollingSequence contextPrefillFluid;
private final AtomicRollingSequence contextPrefillRegion;
private final AtomicRollingSequence contextPrefillCave;
private final AtomicRollingSequence pregenWaitPermit;
private final AtomicRollingSequence pregenWaitAdaptive;
private final StageTimer total;
private final StageTimer updates;
private final StageTimer terrain;
private final StageTimer biome;
private final StageTimer parallax;
private final StageTimer parallaxInsert;
private final StageTimer post;
private final StageTimer perfection;
private final StageTimer api;
private final StageTimer decoration;
private final StageTimer cave;
private final StageTimer ravine;
private final StageTimer deposit;
private final StageTimer carveResolve;
private final StageTimer carveApply;
private final StageTimer noiseHeightInterpolate;
private final StageTimer noiseHeightGenerator;
private final StageTimer contextPrefill;
private final StageTimer contextPrefillHeight;
private final StageTimer contextPrefillBiome;
private final StageTimer contextPrefillRock;
private final StageTimer contextPrefillFluid;
private final StageTimer contextPrefillRegion;
private final StageTimer contextPrefillCave;
private final StageTimer pregenWaitPermit;
private final StageTimer pregenWaitAdaptive;
public EngineMetrics(int mem) {
this.total = new AtomicRollingSequence(mem);
this.terrain = new AtomicRollingSequence(mem);
this.api = new AtomicRollingSequence(mem);
this.biome = new AtomicRollingSequence(mem);
this.perfection = new AtomicRollingSequence(mem);
this.parallax = new AtomicRollingSequence(mem);
this.parallaxInsert = new AtomicRollingSequence(mem);
this.post = new AtomicRollingSequence(mem);
this.decoration = new AtomicRollingSequence(mem);
this.updates = new AtomicRollingSequence(mem);
this.cave = new AtomicRollingSequence(mem);
this.ravine = new AtomicRollingSequence(mem);
this.deposit = new AtomicRollingSequence(mem);
this.carveResolve = new AtomicRollingSequence(mem);
this.carveApply = new AtomicRollingSequence(mem);
this.noiseHeightInterpolate = new AtomicRollingSequence(mem);
this.noiseHeightGenerator = new AtomicRollingSequence(mem);
this.contextPrefill = new AtomicRollingSequence(mem);
this.contextPrefillHeight = new AtomicRollingSequence(mem);
this.contextPrefillBiome = new AtomicRollingSequence(mem);
this.contextPrefillRock = new AtomicRollingSequence(mem);
this.contextPrefillFluid = new AtomicRollingSequence(mem);
this.contextPrefillRegion = new AtomicRollingSequence(mem);
this.contextPrefillCave = new AtomicRollingSequence(mem);
this.pregenWaitPermit = new AtomicRollingSequence(mem);
this.pregenWaitAdaptive = new AtomicRollingSequence(mem);
this.total = new StageTimer(mem);
this.terrain = new StageTimer(mem);
this.api = new StageTimer(mem);
this.biome = new StageTimer(mem);
this.perfection = new StageTimer(mem);
this.parallax = new StageTimer(mem);
this.parallaxInsert = new StageTimer(mem);
this.post = new StageTimer(mem);
this.decoration = new StageTimer(mem);
this.updates = new StageTimer(mem);
this.cave = new StageTimer(mem);
this.ravine = new StageTimer(mem);
this.deposit = new StageTimer(mem);
this.carveResolve = new StageTimer(mem);
this.carveApply = new StageTimer(mem);
this.noiseHeightInterpolate = new StageTimer(mem);
this.noiseHeightGenerator = new StageTimer(mem);
this.contextPrefill = new StageTimer(mem);
this.contextPrefillHeight = new StageTimer(mem);
this.contextPrefillBiome = new StageTimer(mem);
this.contextPrefillRock = new StageTimer(mem);
this.contextPrefillFluid = new StageTimer(mem);
this.contextPrefillRegion = new StageTimer(mem);
this.contextPrefillCave = new StageTimer(mem);
this.pregenWaitPermit = new StageTimer(mem);
this.pregenWaitAdaptive = new StageTimer(mem);
}
public KMap<String, Double> pull() {
@@ -134,4 +136,61 @@ public class EngineMetrics {
averages.entrySet().removeIf(entry -> !Double.isFinite(entry.getValue()) || entry.getValue() < 0D);
return Map.copyOf(averages);
}
public Map<String, StageTotal> stageTotals() {
Map<String, StageTotal> totals = new LinkedHashMap<>();
totals.put("total", total.snapshot());
totals.put("terrain", terrain.snapshot());
totals.put("biome", biome.snapshot());
totals.put("parallax", parallax.snapshot());
totals.put("parallax.insert", parallaxInsert.snapshot());
totals.put("post", post.snapshot());
totals.put("perfection", perfection.snapshot());
totals.put("decoration", decoration.snapshot());
totals.put("api", api.snapshot());
totals.put("updates", updates.snapshot());
totals.put("cave", cave.snapshot());
totals.put("ravine", ravine.snapshot());
totals.put("deposit", deposit.snapshot());
totals.put("carve.resolve", carveResolve.snapshot());
totals.put("carve.apply", carveApply.snapshot());
totals.put("noise.height.interpolate", noiseHeightInterpolate.snapshot());
totals.put("noise.height.generator", noiseHeightGenerator.snapshot());
totals.put("context.prefill", contextPrefill.snapshot());
totals.put("context.prefill.height", contextPrefillHeight.snapshot());
totals.put("context.prefill.biome", contextPrefillBiome.snapshot());
totals.put("context.prefill.rock", contextPrefillRock.snapshot());
totals.put("context.prefill.fluid", contextPrefillFluid.snapshot());
totals.put("context.prefill.region", contextPrefillRegion.snapshot());
totals.put("context.prefill.cave", contextPrefillCave.snapshot());
totals.put("pregen.wait.permit", pregenWaitPermit.snapshot());
totals.put("pregen.wait.adaptive", pregenWaitAdaptive.snapshot());
return totals;
}
public static final class StageTimer extends AtomicRollingSequence {
private final DoubleAdder totalMillis = new DoubleAdder();
private final LongAdder samples = new LongAdder();
public StageTimer(int size) {
super(size);
}
@Override
public void put(double millis) {
super.put(millis);
totalMillis.add(millis);
samples.increment();
}
public StageTotal snapshot() {
return new StageTotal(totalMillis.sum(), samples.sum());
}
}
public record StageTotal(double millis, long samples) {
public StageTotal since(StageTotal earlier) {
return new StageTotal(millis - earlier.millis(), samples - earlier.samples());
}
}
}
@@ -0,0 +1,34 @@
package art.arcane.iris.generation.runtime;
import java.util.concurrent.atomic.LongAdder;
public enum GenerationCounter {
GENERATOR_EVALUATIONS("generator_evaluations"),
CORNER_STARCASTS("corner_starcasts"),
HYDROLOGY_LOOKUPS("hydrology_lookups");
public static final boolean ENABLED = Boolean.getBoolean("iris.generation.counters");
private final String key;
private final LongAdder count = new LongAdder();
GenerationCounter(String key) {
this.key = key;
}
public String key() {
return key;
}
public void add(long amount) {
count.add(amount);
}
public void increment() {
count.increment();
}
public long sum() {
return count.sum();
}
}
@@ -836,6 +836,9 @@ public class IrisComplex implements DataProvider {
break;
}
generator.getHeights(activeXs, activeZs, seed, noise, activeCount);
if (GenerationCounter.ENABLED) {
GenerationCounter.GENERATOR_EVALUATIONS.add(activeCount);
}
for (int sample = 0; sample < activeCount; sample++) {
int index = active[sample];
if (!Double.isFinite(noise[sample])) {
@@ -1822,6 +1825,9 @@ public class IrisComplex implements DataProvider {
long sharedKey = SharedCornerBounds.key(gx, gz);
long packed = shared.get(sharedKey, interpolatorIndex);
if (packed == Long.MIN_VALUE) {
if (GenerationCounter.ENABLED) {
GenerationCounter.CORNER_STARCASTS.increment();
}
packed = computePackedBounds(cache, engine, interpolator, generators, gx, gz);
if (!Double.isFinite(boundsLow(packed)) || !Double.isFinite(boundsHigh(packed))) {
// A non-finite corner is a transient sampling fault, not a fact about the terrain:
@@ -2185,6 +2191,9 @@ public class IrisComplex implements DataProvider {
}
return height / generators.length;
}
if (GenerationCounter.ENABLED) {
GenerationCounter.GENERATOR_EVALUATIONS.add(generators.length);
}
for (IrisGenerator generator : generators) {
double noise = generator.getHeight(x, z, seed);
if (!Double.isFinite(noise)) {
@@ -0,0 +1,47 @@
package art.arcane.iris.generation.runtime;
import org.junit.Test;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.assertEquals;
public final class EngineMetricsStageTotalsTest {
@Test
public void stageTotalsAccumulatePastTheRollingWindow() {
EngineMetrics metrics = new EngineMetrics(2);
metrics.getTerrain().put(1.5D);
metrics.getTerrain().put(2.5D);
metrics.getTerrain().put(4D);
EngineMetrics.StageTotal terrain = metrics.stageTotals().get("terrain");
assertEquals(8D, terrain.millis(), 0D);
assertEquals(3L, terrain.samples());
assertEquals(3.25D, metrics.getTerrain().getAverage(), 0D);
}
@Test
public void stageTotalsSinceAnEarlierSnapshotCoverOnlyTheLaterPuts() {
EngineMetrics metrics = new EngineMetrics(4);
metrics.getCarveResolve().put(3D);
EngineMetrics.StageTotal before = metrics.stageTotals().get("carve.resolve");
metrics.getCarveResolve().put(5D);
metrics.getCarveResolve().put(7D);
EngineMetrics.StageTotal window = metrics.stageTotals().get("carve.resolve").since(before);
assertEquals(12D, window.millis(), 0D);
assertEquals(2L, window.samples());
}
@Test
public void stageTotalsNameEveryPulledTimer() {
EngineMetrics metrics = new EngineMetrics(1);
Map<String, EngineMetrics.StageTotal> totals = metrics.stageTotals();
assertEquals(Set.copyOf(metrics.pull().keySet()), Set.copyOf(totals.keySet()));
assertEquals(metrics.pull().size(), totals.size());
}
}