Perfection and ore lookups

- Perfection memoizes solid and spike flags per block state identity
- Terrain painting resolves the ore chance engine once per chunk
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-22 11:05:58 -04:00
1 parent 9cd470c7c8
commit d9c5d00be3
5 files changed
+127 -80

No files matched your search

@@ -1,5 +1,6 @@
package art.arcane.iris.generation.decoration; package art.arcane.iris.generation.decoration;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.pack.loading.IrisData; import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.pack.value.IrisRange; import art.arcane.iris.pack.value.IrisRange;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState; import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
@@ -94,10 +95,10 @@ public final class IrisOreBands {
/** /**
* The ore placed at a Y inside {@link #contains(int)}, or null when no generator places one. * The ore placed at a Y inside {@link #contains(int)}, or null when no generator places one.
*/ */
public NativeBlockState generate(int x, int y, int z, RNG rng, IrisData data) { public NativeBlockState generate(int x, int y, int z, RNG rng, IrisData data, Engine engine) {
IrisOreGenerator[] candidates = byY == null ? generators : byY[y - minY]; IrisOreGenerator[] candidates = byY == null ? generators : byY[y - minY];
for (IrisOreGenerator candidate : candidates) { for (IrisOreGenerator candidate : candidates) {
NativeBlockState ore = candidate.generate(x, y, z, rng, data); NativeBlockState ore = candidate.generate(x, y, z, rng, data, engine);
if (ore != null) { if (ore != null) {
return ore; return ore;
} }
@@ -65,6 +65,10 @@ public class IrisOreGenerator {
} }
public NativeBlockState generate(int x, int y, int z, RNG rng, IrisData data) { public NativeBlockState generate(int x, int y, int z, RNG rng, IrisData data) {
return generate(x, y, z, rng, data, data == null ? null : data.getEngine());
}
public NativeBlockState generate(int x, int y, int z, RNG rng, IrisData data, Engine engine) {
if (threshold <= 0 || !range.contains(y)) { if (threshold <= 0 || !range.contains(y)) {
return null; return null;
} }
@@ -73,7 +77,7 @@ public class IrisOreGenerator {
return null; return null;
} }
if (chance(rng, data).sample(x, y, z) > threshold) { if (chance(rng, data, engine).sample(x, y, z) > threshold) {
return null; return null;
} }
@@ -81,8 +85,11 @@ public class IrisOreGenerator {
} }
private OreChance chance(RNG rng, IrisData data) { private OreChance chance(RNG rng, IrisData data) {
return chance(rng, data, data == null ? null : data.getEngine());
}
private OreChance chance(RNG rng, IrisData data, Engine engine) {
long seed = rng.getSeed(); long seed = rng.getSeed();
Engine engine = data == null ? null : data.getEngine();
CachedChance recent = recentChance; CachedChance recent = recentChance;
if (recent != null && recent.key.seed == seed && recent.key.data == data && recent.key.engine == engine) { if (recent != null && recent.key.seed == seed && recent.key.data == data && recent.key.engine == engine) {
return recent.chance; return recent.chance;
@@ -38,6 +38,7 @@ import it.unimi.dsi.fastutil.ints.IntArrayList;
public class IrisPerfectionModifier extends EngineAssignedModifier<NativeBlockState> { public class IrisPerfectionModifier extends EngineAssignedModifier<NativeBlockState> {
private static final int SOLID = 1; private static final int SOLID = 1;
private static final int SPIKE = 2; private static final int SPIKE = 2;
private static final int SLICE_TASKS = 16;
private static final BoundBlockState AIR = BoundBlockState.of("AIR"); private static final BoundBlockState AIR = BoundBlockState.of("AIR");
private static final BoundBlockState WATER = BoundBlockState.of("WATER"); private static final BoundBlockState WATER = BoundBlockState.of("WATER");
private static final Map<String, BoundBlockState> ORE_BASES = buildOreBases(); private static final Map<String, BoundBlockState> ORE_BASES = buildOreBases();
@@ -87,70 +88,94 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<NativeBlockSt
if (getDimension().isHideOresForHiddenOre()) { if (getDimension().isHideOresForHiddenOre()) {
hideOres(output, multicore); hideOres(output, multicore);
} }
int tasks = multicore ? SLICE_TASKS : 1;
int slicesPerTask = 16 / tasks;
BurstExecutor burst = burst().burst(multicore); BurstExecutor burst = burst().burst(multicore);
for (int index = 0; index < 16; index++) { for (int task = 0; task < tasks; task++) {
int columnX = index; int firstSlice = task * slicesPerTask;
burst.queue(() -> perfectSlice(output, columnX)); burst.queue(() -> perfectSlices(output, firstSlice, firstSlice + slicesPerTask));
} }
burst.complete(); burst.complete();
getEngine().getMetrics().getPerfection().put(p.getMilliseconds()); getEngine().getMetrics().getPerfection().put(p.getMilliseconds());
} }
private void perfectSlice(Hunk<NativeBlockState> output, int x) { private void perfectSlices(Hunk<NativeBlockState> output, int firstSlice, int lastSlice) {
IntArrayList surfaces = new IntArrayList(); IntArrayList surfaces = new IntArrayList();
StateClasses classes = new StateClasses(); StateFlags states = new StateFlags();
for (int z = 0; z < 16; z++) { NativeBlockState air = AIR.get();
boolean changed; NativeBlockState water = WATER.get();
do { for (int x = firstSlice; x < lastSlice; x++) {
surfaces.clear(); for (int z = 0; z < 16; z++) {
int top = getHeight(output, x, z, classes); perfectColumn(output, x, z, surfaces, states, air, water);
boolean inside = true; }
surfaces.add(top);
for (int y = top; y >= 0; y--) {
NativeBlockState block = output.get(x, y, z);
int flags = classes.of(block);
if ((flags & SPIKE) != 0) {
block = normalizeSpike(block, output, x, z, y, AIR.get(), WATER.get());
flags = classes.of(block);
}
boolean now = (flags & SOLID) != 0;
if (now != inside) {
inside = now;
if (inside) {
surfaces.add(y);
}
}
}
changed = false;
for (int index = 0; index < surfaces.size(); index++) {
int y = surfaces.getInt(index);
NativeBlockState tip = output.get(x, y, z);
if (!B.isDecorant(tip)) {
continue;
}
NativeBlockState below = output.get(x, y - 1, z);
boolean remove = below == null || !B.canPlaceOnto(tip, below);
boolean removeBelow = false;
if (!remove && IrisProceduralBlocks.hasProperty(below, "half")) {
NativeBlockState support = output.get(x, y - 2, z);
if (support == null || !B.canPlaceOnto(below, support)) {
remove = true;
removeBelow = true;
}
}
if (remove) {
changed = true;
output.set(x, y, z, AIR.get());
if (removeBelow) {
output.set(x, y - 1, z, AIR.get());
}
}
}
} while (changed);
} }
} }
private static void perfectColumn(Hunk<NativeBlockState> output, int x, int z, IntArrayList surfaces,
StateFlags states, NativeBlockState air, NativeBlockState water) {
do {
surfaces.clear();
int top = getHeight(output, x, z, states);
boolean inside = true;
surfaces.add(top);
NativeBlockState classified = null;
int classifiedFlags = 0;
for (int y = top; y >= 0; y--) {
NativeBlockState block = output.get(x, y, z);
int flags = 0;
if (block != null) {
if (block != classified) {
classified = block;
classifiedFlags = states.of(block);
}
flags = classifiedFlags;
if ((flags & SPIKE) != 0) {
block = normalizeSpike(block, output, x, z, y, air, water);
flags = block == null ? 0 : states.of(block);
}
}
boolean now = (flags & SOLID) != 0;
if (now != inside) {
inside = now;
if (inside) {
surfaces.add(y);
}
}
}
} while (removeUnsupportedDecorants(output, x, z, surfaces, air));
}
private static boolean removeUnsupportedDecorants(Hunk<NativeBlockState> output, int x, int z,
IntArrayList surfaces, NativeBlockState air) {
boolean changed = false;
for (int index = 0; index < surfaces.size(); index++) {
int y = surfaces.getInt(index);
NativeBlockState tip = output.get(x, y, z);
if (!B.isDecorant(tip)) {
continue;
}
NativeBlockState below = output.get(x, y - 1, z);
boolean remove = below == null || !B.canPlaceOnto(tip, below);
boolean removeBelow = false;
if (!remove && IrisProceduralBlocks.hasProperty(below, "half")) {
NativeBlockState support = output.get(x, y - 2, z);
if (support == null || !B.canPlaceOnto(below, support)) {
remove = true;
removeBelow = true;
}
}
if (remove) {
changed = true;
output.set(x, y, z, air);
if (removeBelow) {
output.set(x, y - 1, z, air);
}
}
}
return changed;
}
static NativeBlockState normalizeSpike(NativeBlockState state, Hunk<NativeBlockState> output, static NativeBlockState normalizeSpike(NativeBlockState state, Hunk<NativeBlockState> output,
int x, int z, int y, NativeBlockState air, NativeBlockState water) { int x, int z, int y, NativeBlockState air, NativeBlockState water) {
if (IrisSpeleothems.isSupported(state, output, x, z, y)) { if (IrisSpeleothems.isSupported(state, output, x, z, y)) {
@@ -208,9 +233,19 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<NativeBlockSt
burst.complete(); burst.complete();
} }
private int getHeight(Hunk<NativeBlockState> output, int x, int z, StateClasses classes) { private static int getHeight(Hunk<NativeBlockState> output, int x, int z, StateFlags states) {
NativeBlockState classified = null;
int classifiedFlags = 0;
for (int i = Math.min(output.getHeight() - 1, ColumnExtent.highestStoredY(output, x, z)); i >= 0; i--) { for (int i = Math.min(output.getHeight() - 1, ColumnExtent.highestStoredY(output, x, z)); i >= 0; i--) {
if ((classes.of(output.get(x, i, z)) & SOLID) != 0) { NativeBlockState state = output.get(x, i, z);
if (state == null) {
continue;
}
if (state != classified) {
classified = state;
classifiedFlags = states.of(state);
}
if ((classifiedFlags & SOLID) != 0) {
return i; return i;
} }
} }
@@ -218,19 +253,21 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<NativeBlockSt
return 0; return 0;
} }
private static final class StateClasses { private static final class StateFlags {
private NativeBlockState last; private static final int SLOTS = 64;
private int lastFlags;
private final NativeBlockState[] states = new NativeBlockState[SLOTS];
private final byte[] flags = new byte[SLOTS];
private int of(NativeBlockState state) { private int of(NativeBlockState state) {
if (state == null) { int slot = System.identityHashCode(state) & (SLOTS - 1);
return 0; if (states[slot] == state) {
return flags[slot];
} }
if (state != last) { int computed = (state.isAir() || state.isFluid() ? 0 : SOLID) | (IrisSpeleothems.isSpike(state) ? SPIKE : 0);
last = state; states[slot] = state;
lastFlags = (state.isAir() || state.isFluid() ? 0 : SOLID) | (IrisSpeleothems.isSpike(state) ? SPIKE : 0); flags[slot] = (byte) computed;
} return computed;
return lastFlags;
} }
} }
} }
@@ -153,6 +153,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
private final int chunkHeight; private final int chunkHeight;
private final IrisDimension dimension; private final IrisDimension dimension;
private final IrisData data; private final IrisData data;
private final Engine oreEngine;
private final IrisComplex complex; private final IrisComplex complex;
private final ProceduralStream<Double> riverWaterSurfaceStream; private final ProceduralStream<Double> riverWaterSurfaceStream;
private final boolean bedrockEnabled; private final boolean bedrockEnabled;
@@ -195,6 +196,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
chunkHeight = h.getHeight(); chunkHeight = h.getHeight();
dimension = getDimension(); dimension = getDimension();
data = getData(); data = getData();
oreEngine = data == null ? null : data.getEngine();
complex = getComplex(); complex = getComplex();
riverWaterSurfaceStream = complex.getRiverWaterSurfaceStream(); riverWaterSurfaceStream = complex.getRiverWaterSurfaceStream();
bedrockEnabled = dimension.isBedrock(); bedrockEnabled = dimension.isBedrock();
@@ -459,13 +461,13 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
private NativeBlockState surfaceOre(int realX, int y, int realZ) { private NativeBlockState surfaceOre(int realX, int y, int realZ) {
NativeBlockState ore = null; NativeBlockState ore = null;
if (biomeSurfaceOres.contains(y)) { if (biomeSurfaceOres.contains(y)) {
ore = biomeSurfaceOres.generate(realX, y, realZ, rng, data); ore = biomeSurfaceOres.generate(realX, y, realZ, rng, data, oreEngine);
} }
if (ore == null && regionSurfaceOres.contains(y)) { if (ore == null && regionSurfaceOres.contains(y)) {
ore = regionSurfaceOres.generate(realX, y, realZ, rng, data); ore = regionSurfaceOres.generate(realX, y, realZ, rng, data, oreEngine);
} }
if (ore == null && dimensionSurfaceOres.contains(y)) { if (ore == null && dimensionSurfaceOres.contains(y)) {
ore = dimensionSurfaceOres.generate(realX, y, realZ, rng, data); ore = dimensionSurfaceOres.generate(realX, y, realZ, rng, data, oreEngine);
} }
return ore; return ore;
} }
@@ -473,13 +475,13 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
private NativeBlockState undergroundOre(int realX, int y, int realZ) { private NativeBlockState undergroundOre(int realX, int y, int realZ) {
NativeBlockState ore = null; NativeBlockState ore = null;
if (biomeUndergroundOres.contains(y)) { if (biomeUndergroundOres.contains(y)) {
ore = biomeUndergroundOres.generate(realX, y, realZ, rng, data); ore = biomeUndergroundOres.generate(realX, y, realZ, rng, data, oreEngine);
} }
if (ore == null && regionUndergroundOres.contains(y)) { if (ore == null && regionUndergroundOres.contains(y)) {
ore = regionUndergroundOres.generate(realX, y, realZ, rng, data); ore = regionUndergroundOres.generate(realX, y, realZ, rng, data, oreEngine);
} }
if (ore == null && dimensionUndergroundOres.contains(y)) { if (ore == null && dimensionUndergroundOres.contains(y)) {
ore = dimensionUndergroundOres.generate(realX, y, realZ, rng, data); ore = dimensionUndergroundOres.generate(realX, y, realZ, rng, data, oreEngine);
} }
return ore; return ore;
} }
@@ -48,7 +48,7 @@ public class IrisOreBandsTest {
} }
for (int x = -3; x <= 3; x++) { for (int x = -3; x <= 3; x++) {
assertSame("trial " + trial + " y " + y + " x " + x, assertSame("trial " + trial + " y " + y + " x " + x,
scan(ores, x, y, 5 - x), bands.generate(x, y, 5 - x, RNG_SOURCE, null)); scan(ores, x, y, 5 - x), bands.generate(x, y, 5 - x, RNG_SOURCE, null, null));
} }
} }
} }
@@ -110,10 +110,10 @@ public class IrisOreBandsTest {
ores.add(generator(1D, 57D, 1D, 2L)); ores.add(generator(1D, 57D, 1D, 2L));
ores.add(generator(58D, 58D, 1D, 3L)); ores.add(generator(58D, 58D, 1D, 3L));
IrisOreBands bands = IrisOreBands.of(ores); IrisOreBands bands = IrisOreBands.of(ores);
assertSame(scan(ores, 0, 0, 0), bands.generate(0, 0, 0, RNG_SOURCE, null)); assertSame(scan(ores, 0, 0, 0), bands.generate(0, 0, 0, RNG_SOURCE, null, null));
assertSame(scan(ores, 0, 30, 0), bands.generate(0, 30, 0, RNG_SOURCE, null)); assertSame(scan(ores, 0, 30, 0), bands.generate(0, 30, 0, RNG_SOURCE, null, null));
assertSame(scan(ores, 0, 58, 0), bands.generate(0, 58, 0, RNG_SOURCE, null)); assertSame(scan(ores, 0, 58, 0), bands.generate(0, 58, 0, RNG_SOURCE, null, null));
assertNull(bands.contains(59) ? bands.generate(0, 59, 0, RNG_SOURCE, null) : null); assertNull(bands.contains(59) ? bands.generate(0, 59, 0, RNG_SOURCE, null, null) : null);
} }
private static final class FixedPalette extends IrisMaterialPalette { private static final class FixedPalette extends IrisMaterialPalette {
private final NativeBlockState state; private final NativeBlockState state;