Cached more water rasters

- Stored raster tiers fluids and feature ids as per-chunk palettes
- Derived cave ceilings from terrain heights and water heads
- Raised the water raster cache from 1024 to 8192 chunks
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-30 20:09:02 -04:00
1 parent c700138b66
commit a879985875
3 files changed
+97 -34

No files matched your search

@@ -56,7 +56,7 @@ import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
public final class HydrologyRuntime { public final class HydrologyRuntime {
private static final int RASTER_CAPACITY = 1024; private static final int RASTER_CAPACITY = 8192;
private static final int KEEP_SCOPES = 2; private static final int KEEP_SCOPES = 2;
private static final int CHUNKS_PER_WINDOW = WindowKey.SIZE >> 4; private static final int CHUNKS_PER_WINDOW = WindowKey.SIZE >> 4;
public static final int MAX_PREGENERATION_REGIONS = 4096; public static final int MAX_PREGENERATION_REGIONS = 4096;
@@ -2,6 +2,8 @@ package art.arcane.iris.generation.hydrology.raster;
import art.arcane.iris.generation.hydrology.water.HydrologyTier; import art.arcane.iris.generation.hydrology.water.HydrologyTier;
import java.util.Arrays;
public final class ChunkWaterRaster { public final class ChunkWaterRaster {
public static final int MAX_SHORE_DISTANCE = 32; public static final int MAX_SHORE_DISTANCE = 32;
public static final ChunkWaterRaster PROVISIONAL = new ChunkWaterRaster(true); public static final ChunkWaterRaster PROVISIONAL = new ChunkWaterRaster(true);
@@ -13,10 +15,13 @@ public final class ChunkWaterRaster {
private final int[] terrain; private final int[] terrain;
private final int[] head; private final int[] head;
private final byte[] role; private final byte[] role;
private final HydrologyTier[] tier; private final byte[] tier;
private final long[] featureId; private final HydrologyTier[] tiers;
private final String[] fluid; private final byte[] feature;
private final int[] caveCeiling; private final long[] features;
private final byte[] fluid;
private final String[] fluids;
private final boolean caveMask;
private final byte[] shoreDistance; private final byte[] shoreDistance;
private ChunkWaterRaster(boolean provisional) { private ChunkWaterRaster(boolean provisional) {
@@ -25,26 +30,32 @@ public final class ChunkWaterRaster {
this.head = null; this.head = null;
this.role = null; this.role = null;
this.tier = null; this.tier = null;
this.featureId = null; this.tiers = null;
this.feature = null;
this.features = null;
this.fluid = null; this.fluid = null;
this.caveCeiling = null; this.fluids = null;
this.caveMask = false;
this.shoreDistance = null; this.shoreDistance = null;
} }
ChunkWaterRaster(int[] terrain, int[] head, byte[] role, HydrologyTier[] tier, long[] featureId, String[] fluid, int[] caveCeiling, ChunkWaterRaster(int[] terrain, int[] head, byte[] role, Palette<HydrologyTier> tiers, LongPalette features, Palette<String> fluids,
byte[] shoreDistance) { boolean caveMask, byte[] shoreDistance) {
if (terrain.length != COLUMNS || head.length != COLUMNS || role.length != COLUMNS || tier.length != COLUMNS if (terrain.length != COLUMNS || head.length != COLUMNS || role.length != COLUMNS || tiers.indices.length != COLUMNS
|| featureId.length != COLUMNS || fluid.length != COLUMNS || caveCeiling.length != COLUMNS || shoreDistance.length != COLUMNS) { || features.indices.length != COLUMNS || fluids.indices.length != COLUMNS || shoreDistance.length != COLUMNS) {
throw new IllegalArgumentException("A chunk water raster needs " + COLUMNS + " entries per array"); throw new IllegalArgumentException("A chunk water raster needs " + COLUMNS + " entries per array");
} }
this.provisional = false; this.provisional = false;
this.terrain = terrain; this.terrain = terrain;
this.head = head; this.head = head;
this.role = role; this.role = role;
this.tier = tier; this.tier = tiers.indices;
this.featureId = featureId; this.tiers = tiers.values();
this.fluid = fluid; this.feature = features.indices;
this.caveCeiling = caveCeiling; this.features = features.values();
this.fluid = fluids.indices;
this.fluids = fluids.values();
this.caveMask = caveMask;
this.shoreDistance = shoreDistance; this.shoreDistance = shoreDistance;
} }
@@ -68,22 +79,22 @@ public final class ChunkWaterRaster {
public HydrologyTier tier(int lx, int lz) { public HydrologyTier tier(int lx, int lz) {
int index = index(lx, lz); int index = index(lx, lz);
return tier == null ? null : tier[index]; return tier == null ? null : tiers[tier[index] & 0xFF];
} }
public long featureId(int lx, int lz) { public long featureId(int lx, int lz) {
int index = index(lx, lz); int index = index(lx, lz);
return featureId == null ? 0L : featureId[index]; return feature == null ? 0L : features[feature[index] & 0xFF];
} }
public String fluid(int lx, int lz) { public String fluid(int lx, int lz) {
int index = index(lx, lz); int index = index(lx, lz);
return fluid == null ? null : fluid[index]; return fluid == null ? null : fluids[fluid[index] & 0xFF];
} }
public int caveCeiling(int lx, int lz) { public int caveCeiling(int lx, int lz) {
int index = index(lx, lz); int index = index(lx, lz);
return caveCeiling == null ? Integer.MAX_VALUE : caveCeiling[index]; return caveMask && head[index] != Integer.MIN_VALUE ? terrain[index] - 1 : Integer.MAX_VALUE;
} }
public int shoreDistance(int lx, int lz) { public int shoreDistance(int lx, int lz) {
@@ -105,4 +116,53 @@ public final class ChunkWaterRaster {
} }
return lz << 4 | lx; return lz << 4 | lx;
} }
static final class Palette<T> {
private final byte[] indices = new byte[COLUMNS];
private final T[] seen;
private int size;
Palette(T[] seen) {
if (seen.length != COLUMNS) {
throw new IllegalArgumentException("A raster palette needs room for " + COLUMNS + " values");
}
this.seen = seen;
}
void set(int column, T value) {
int found = 0;
while (found < size && seen[found] != value) {
found++;
}
if (found == size) {
seen[size++] = value;
}
indices[column] = (byte) found;
}
private T[] values() {
return Arrays.copyOf(seen, size);
}
}
static final class LongPalette {
private final byte[] indices = new byte[COLUMNS];
private final long[] seen = new long[COLUMNS];
private int size;
void set(int column, long value) {
int found = 0;
while (found < size && seen[found] != value) {
found++;
}
if (found == size) {
seen[size++] = value;
}
indices[column] = (byte) found;
}
private long[] values() {
return Arrays.copyOf(seen, size);
}
}
} }
@@ -385,34 +385,37 @@ final class RasterComposer {
private ChunkWaterRaster pack() { private ChunkWaterRaster pack() {
int columns = ChunkWaterRaster.COLUMNS; int columns = ChunkWaterRaster.COLUMNS;
boolean affected = false;
for (int lz = 0; lz < CHUNK && !affected; lz++) {
for (int lx = 0; lx < CHUNK && !affected; lx++) {
int cell = (lz + 1) * GRID + lx + 1;
affected = role[cell] != WaterRole.NONE || terrain[cell] != natural[cell] || shore(cell) < ChunkWaterRaster.MAX_SHORE_DISTANCE;
}
}
if (!affected) {
return ChunkWaterRaster.EMPTY;
}
int[] terrainOut = new int[columns]; int[] terrainOut = new int[columns];
int[] headOut = new int[columns]; int[] headOut = new int[columns];
byte[] roleOut = new byte[columns]; byte[] roleOut = new byte[columns];
HydrologyTier[] tierOut = new HydrologyTier[columns]; ChunkWaterRaster.Palette<HydrologyTier> tierOut = new ChunkWaterRaster.Palette<>(new HydrologyTier[columns]);
long[] featureOut = new long[columns]; ChunkWaterRaster.LongPalette featureOut = new ChunkWaterRaster.LongPalette();
String[] fluidOut = new String[columns]; ChunkWaterRaster.Palette<String> fluidOut = new ChunkWaterRaster.Palette<>(new String[columns]);
int[] caveOut = new int[columns];
byte[] shoreOut = new byte[columns]; byte[] shoreOut = new byte[columns];
boolean caveMask = settings.deformation().caveMask();
boolean affected = false;
for (int lz = 0; lz < CHUNK; lz++) { for (int lz = 0; lz < CHUNK; lz++) {
for (int lx = 0; lx < CHUNK; lx++) { for (int lx = 0; lx < CHUNK; lx++) {
int cell = (lz + 1) * GRID + lx + 1; int cell = (lz + 1) * GRID + lx + 1;
int index = lz << 4 | lx; int index = lz << 4 | lx;
int shore = shore(cell);
terrainOut[index] = terrain[cell]; terrainOut[index] = terrain[cell];
headOut[index] = head[cell]; headOut[index] = head[cell];
roleOut[index] = (byte) role[cell].ordinal(); roleOut[index] = (byte) role[cell].ordinal();
tierOut[index] = tier[cell]; tierOut.set(index, tier[cell]);
featureOut[index] = feature[cell]; featureOut.set(index, feature[cell]);
fluidOut[index] = fluid[cell]; fluidOut.set(index, fluid[cell]);
caveOut[index] = caveMask && head[cell] != Integer.MIN_VALUE ? terrain[cell] - 1 : Integer.MAX_VALUE; shoreOut[index] = (byte) shore(cell);
shoreOut[index] = (byte) shore;
affected |= role[cell] != WaterRole.NONE || terrain[cell] != natural[cell] || shore < ChunkWaterRaster.MAX_SHORE_DISTANCE;
} }
} }
return affected ? new ChunkWaterRaster(terrainOut, headOut, roleOut, tierOut, featureOut, fluidOut, caveOut, shoreOut) return new ChunkWaterRaster(terrainOut, headOut, roleOut, tierOut, featureOut, fluidOut, settings.deformation().caveMask(), shoreOut);
: ChunkWaterRaster.EMPTY;
} }
private int shore(int cell) { private int shore(int cell) {