History capture fast paths

- Natural terrain capture scans columns as block state runs
- Biome samples reuse the palette index of an identical key
- Cave biome capture samples the subterrain planner directly when live
- Repeated object markers skip decoding
- Saved biome encoding hashes each distinct column once
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-22 11:05:58 -04:00
1 parent d9c5d00be3
commit f7f9f5f651
7 files changed
+395 -43

No files matched your search

@@ -0,0 +1,140 @@
package art.arcane.iris.world.history;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Random;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import static org.junit.Assert.assertArrayEquals;
public class SavedBiomeStoreEncodingTest {
@Test
public void encodedRecordsMatchTheReferenceEncoding() throws Exception {
Method encode = SavedBiomeStore.class.getDeclaredMethod("encode", SavedBiomeChunk.class);
encode.setAccessible(true);
Random random = new Random(20261022L);
for (int sample = 0; sample < 40; sample++) {
SavedBiomeChunk chunk = randomChunk(random, sample);
assertArrayEquals("sample " + sample, referenceEncode(chunk), (byte[]) encode.invoke(null, chunk));
}
}
private static SavedBiomeChunk randomChunk(Random random, int sample) {
long activation = 1L + random.nextInt(9);
int minimumY = -64;
int height = 384;
List<SavedBiomeChunk.Cell> cells = new ArrayList<>();
int distinctCells = 1 + random.nextInt(sample % 4 == 0 ? 3 : 24);
for (int index = 0; index < distinctCells; index++) {
cells.add(random.nextInt(9) == 0
? SavedBiomeChunk.Cell.unresolved(activation)
: new SavedBiomeChunk.Cell(activation, "biome_" + random.nextInt(12), "region_" + random.nextInt(3)));
}
List<SavedBiomeChunk.Column> shared = new ArrayList<>();
int distinctColumns = 1 + random.nextInt(sample % 3 == 0 ? 4 : 40);
for (int index = 0; index < distinctColumns; index++) {
shared.add(randomColumn(random, cells, minimumY, height));
}
SavedBiomeChunk.Builder builder = SavedBiomeChunk.builder(
new SavedBiomeChunk.Header(3, -7, activation, minimumY, height));
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
SavedBiomeChunk.Column column = shared.get(random.nextInt(shared.size()));
if (random.nextInt(4) == 0) {
column = new SavedBiomeChunk.Column(column.surface(), column.caveBase(), new ArrayList<>(column.vertical()));
}
builder.column(x, z, column);
}
}
return builder.build();
}
private static SavedBiomeChunk.Column randomColumn(Random random, List<SavedBiomeChunk.Cell> cells,
int minimumY, int height) {
List<SavedBiomeChunk.Span> spans = new ArrayList<>();
int start = minimumY;
int end = minimumY + height;
while (start < end) {
int top = Math.min(end, start + 1 + random.nextInt(96));
spans.add(new SavedBiomeChunk.Span(start, top, cells.get(random.nextInt(cells.size()))));
start = top;
}
return new SavedBiomeChunk.Column(cells.get(random.nextInt(cells.size())),
cells.get(random.nextInt(cells.size())), spans);
}
private static byte[] referenceEncode(SavedBiomeChunk chunk) throws IOException {
LinkedHashMap<SavedBiomeChunk.Cell, Integer> palette = new LinkedHashMap<>();
LinkedHashMap<SavedBiomeChunk.Column, Integer> columns = new LinkedHashMap<>();
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
SavedBiomeChunk.Column column = chunk.column(x, z);
columns.computeIfAbsent(column, ignored -> columns.size());
palette.computeIfAbsent(column.surface(), ignored -> palette.size());
palette.computeIfAbsent(column.caveBase(), ignored -> palette.size());
for (SavedBiomeChunk.Span span : column.vertical()) {
palette.computeIfAbsent(span.cell(), ignored -> palette.size());
}
}
}
ByteArrayOutputStream raw = new ByteArrayOutputStream();
try (DataOutputStream output = new DataOutputStream(raw)) {
output.writeLong(chunk.activationId());
output.writeInt(chunk.header().minimumY());
output.writeInt(chunk.header().height());
output.writeInt(palette.size());
for (SavedBiomeChunk.Cell cell : palette.keySet()) {
output.writeLong(cell.activationId());
output.writeByte(cell.isResolved() ? 0 : 1);
if (cell.isResolved()) {
output.writeUTF(cell.biomeKey());
output.writeUTF(cell.regionKey());
}
}
output.writeInt(columns.size());
for (SavedBiomeChunk.Column column : columns.keySet()) {
writeUnsigned(output, palette.get(column.surface()));
writeUnsigned(output, palette.get(column.caveBase()));
writeUnsigned(output, column.vertical().size());
for (SavedBiomeChunk.Span span : column.vertical()) {
writeUnsigned(output, span.maximumYExclusive() - chunk.header().minimumY());
writeUnsigned(output, palette.get(span.cell()));
}
}
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
output.writeByte(columns.get(chunk.column(x, z)));
}
}
}
ByteArrayOutputStream encoded = new ByteArrayOutputStream();
DataOutputStream output = new DataOutputStream(encoded);
output.writeInt(chunk.chunkX());
output.writeInt(chunk.chunkZ());
output.writeInt(raw.size());
Deflater deflater = new Deflater(Deflater.BEST_SPEED);
try (DeflaterOutputStream compressed = new DeflaterOutputStream(output, deflater)) {
raw.writeTo(compressed);
} finally {
deflater.end();
}
return encoded.toByteArray();
}
private static void writeUnsigned(DataOutputStream output, int value) throws IOException {
int remaining = value;
while ((remaining & ~0x7f) != 0) {
output.writeByte((remaining & 0x7f) | 0x80);
remaining >>>= 7;
}
output.writeByte(remaining);
}
}
@@ -56,6 +56,24 @@ public class SavedTerrainCaptureEquivalenceTest {
}
}
@Test
public void runScanningSourcesMatchThePerVoxelReference() throws Exception {
Random random = new Random(20261022L);
for (int sample = 0; sample < 32; sample++) {
RandomSource voxels = new RandomSource(random, sample);
SavedTerrainChunk.VoxelSource runs = new RunScanningSource(voxels, new Random(sample));
for (boolean boundaryOnly : new boolean[]{true, false}) {
SavedTerrainChunk expected = referenceCapture(-3, 7, voxels, boundaryOnly);
SavedTerrainChunk actual = boundaryOnly
? SavedTerrainChunk.captureBoundary(-3, 7, MINIMUM_Y, HEIGHT, "minecraft:noise", runs)
: SavedTerrainChunk.capture(-3, 7, MINIMUM_Y, HEIGHT, "minecraft:noise", runs);
assertSameTerrain(expected, actual);
assertArrayEquals(NativeTerrainReceipt.encode(expected, 1L, "epoch"),
NativeTerrainReceipt.encode(actual, 1L, "epoch"));
}
}
}
@Test
public void geometryMatchesTheReferenceOnWidePalettes() {
Random random = new Random(7L);
@@ -184,6 +202,46 @@ public class SavedTerrainCaptureEquivalenceTest {
return new BoundaryColumnGeometry.Voxel(state, phase, fluid, protectedContent);
}
private static final class RunScanningSource implements SavedTerrainChunk.VoxelSource {
private final RandomSource source;
private final Random splits;
private RunScanningSource(RandomSource source, Random splits) {
this.source = source;
this.splits = splits;
}
@Override
public OptionalInt groundSurface(int localX, int localZ) {
return source.groundSurface(localX, localZ);
}
@Override
public BoundaryColumnGeometry.Voxel voxel(int localX, int worldY, int localZ) {
return source.voxel(localX, worldY, localZ);
}
@Override
public String biome(int localX, int worldY, int localZ) {
return source.biome(localX, worldY, localZ);
}
@Override
public void scanColumn(int localX, int localZ, int minimumY, int height, SavedTerrainChunk.VoxelRuns runs) {
int start = 0;
BoundaryColumnGeometry.Voxel current = source.voxel(localX, minimumY, localZ);
for (int offset = 1; offset < height; offset++) {
BoundaryColumnGeometry.Voxel next = source.voxel(localX, minimumY + offset, localZ);
if (next != current || splits.nextInt(16) == 0) {
runs.add(start, offset, current);
start = offset;
current = next;
}
}
runs.add(start, height, current);
}
}
private static final class RandomSource implements SavedTerrainChunk.VoxelSource {
private final BoundaryColumnGeometry.Voxel[][] voxels = new BoundaryColumnGeometry.Voxel[256][HEIGHT];
private final String[][] biomes = new String[256][HEIGHT];