mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +00:00
Probe history digest
- Adds an opt-in digest of semantic and saved biome records per chunk
This commit is contained in:
1 parent
f7f9f5f651
commit
18ba6e32ac
5 files changed
+111
-5
No files matched your search
@@ -180,6 +180,7 @@ tasks.register('genProbe', JavaExec) {
|
|||||||
systemProperty('iris.probe.pregenProfile', providers.gradleProperty('probePregenProfile').orElse('false').get())
|
systemProperty('iris.probe.pregenProfile', providers.gradleProperty('probePregenProfile').orElse('false').get())
|
||||||
systemProperty('iris.probe.hydrologyPreplan', providers.gradleProperty('probeHydrologyPreplan').orElse('false').get())
|
systemProperty('iris.probe.hydrologyPreplan', providers.gradleProperty('probeHydrologyPreplan').orElse('false').get())
|
||||||
systemProperty('iris.probe.historyRouter', providers.gradleProperty('probeHistoryRouter').orElse('false').get())
|
systemProperty('iris.probe.historyRouter', providers.gradleProperty('probeHistoryRouter').orElse('false').get())
|
||||||
|
systemProperty('iris.probe.historyDigest', providers.gradleProperty('probeHistoryDigest').orElse('false').get())
|
||||||
systemProperty('iris.probe.productionBuffers', providers.gradleProperty('probeProductionBuffers').orElse('false').get())
|
systemProperty('iris.probe.productionBuffers', providers.gradleProperty('probeProductionBuffers').orElse('false').get())
|
||||||
String probeCounters = providers.gradleProperty('probeCounters').orElse('false').get()
|
String probeCounters = providers.gradleProperty('probeCounters').orElse('false').get()
|
||||||
systemProperty('iris.generation.counters', probeCounters)
|
systemProperty('iris.generation.counters', probeCounters)
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ public final class GenerationProbe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
record ProbeOptions(int threads, Path dumpFile, boolean pregenProfile, boolean hydrologyPreplan,
|
record ProbeOptions(int threads, Path dumpFile, boolean pregenProfile, boolean hydrologyPreplan,
|
||||||
boolean historyRouter, boolean productionBuffers, boolean counters) {
|
boolean historyRouter, boolean productionBuffers, boolean counters, boolean historyDigest) {
|
||||||
ProbeOptions {
|
ProbeOptions {
|
||||||
if (threads < 1) {
|
if (threads < 1) {
|
||||||
throw new IllegalArgumentException("Thread count must be at least 1.");
|
throw new IllegalArgumentException("Thread count must be at least 1.");
|
||||||
@@ -196,7 +196,8 @@ public final class GenerationProbe {
|
|||||||
booleanProperty("iris.probe.hydrologyPreplan"),
|
booleanProperty("iris.probe.hydrologyPreplan"),
|
||||||
booleanProperty("iris.probe.historyRouter"),
|
booleanProperty("iris.probe.historyRouter"),
|
||||||
booleanProperty("iris.probe.productionBuffers"),
|
booleanProperty("iris.probe.productionBuffers"),
|
||||||
booleanProperty("iris.generation.counters"));
|
booleanProperty("iris.generation.counters"),
|
||||||
|
booleanProperty("iris.probe.historyDigest"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean booleanProperty(String key) {
|
private static boolean booleanProperty(String key) {
|
||||||
@@ -437,7 +438,8 @@ public final class GenerationProbe {
|
|||||||
+ " timeMs=" + String.format(Locale.ROOT, "%.3f", nanosToMillis(engineReadyNanos)));
|
+ " timeMs=" + String.format(Locale.ROOT, "%.3f", nanosToMillis(engineReadyNanos)));
|
||||||
System.out.println("IRIS_GENPROBE_MANTLE version=1 radius=" + engine.getMantle().getRadius()
|
System.out.println("IRIS_GENPROBE_MANTLE version=1 radius=" + engine.getMantle().getRadius()
|
||||||
+ " real_radius=" + engine.getMantle().getRealRadius());
|
+ " real_radius=" + engine.getMantle().getRealRadius());
|
||||||
ProbeChunkGenerator generator = new ProbeChunkGenerator(engine, router, options.productionBuffers());
|
ProbeChunkGenerator generator = new ProbeChunkGenerator(engine, router, options.productionBuffers(),
|
||||||
|
options.historyDigest());
|
||||||
long nativeSamplesBefore = FractalBillowSimplexNoise.nativeSampleCount();
|
long nativeSamplesBefore = FractalBillowSimplexNoise.nativeSampleCount();
|
||||||
GenerationResult generation = generateWithProfile(generator, configuration, options);
|
GenerationResult generation = generateWithProfile(generator, configuration, options);
|
||||||
verifyBackend(nativeSamplesBefore);
|
verifyBackend(nativeSamplesBefore);
|
||||||
@@ -445,6 +447,9 @@ public final class GenerationProbe {
|
|||||||
System.out.println("IRIS_GENPROBE_BUFFERS version=1 mode=production biome_expanded_chunks="
|
System.out.println("IRIS_GENPROBE_BUFFERS version=1 mode=production biome_expanded_chunks="
|
||||||
+ generator.expandedBiomeHunks());
|
+ generator.expandedBiomeHunks());
|
||||||
}
|
}
|
||||||
|
if (generator.historySummary() != null) {
|
||||||
|
System.out.println(generator.historySummary());
|
||||||
|
}
|
||||||
String status = generation.failedChunks() == 0 ? "PASS" : "FAIL";
|
String status = generation.failedChunks() == 0 ? "PASS" : "FAIL";
|
||||||
printGenerationFailures(generation);
|
printGenerationFailures(generation);
|
||||||
return new ProbeResult(
|
return new ProbeResult(
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 Arcane Arts (Volmit Software)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package art.arcane.iris.probe;
|
||||||
|
|
||||||
|
import art.arcane.iris.world.history.ChunkGenerationSemantics;
|
||||||
|
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
|
||||||
|
import art.arcane.iris.world.history.SavedBiomeChunk;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.HexFormat;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ConcurrentSkipListMap;
|
||||||
|
|
||||||
|
final class HistoryDigest {
|
||||||
|
private final GenerationHistoryRuntimeRouter router;
|
||||||
|
private final Map<Long, byte[]> chunks = new ConcurrentSkipListMap<>();
|
||||||
|
|
||||||
|
HistoryDigest(GenerationHistoryRuntimeRouter router) {
|
||||||
|
this.router = router;
|
||||||
|
}
|
||||||
|
|
||||||
|
void record(int chunkX, int chunkZ) throws IOException {
|
||||||
|
StringBuilder canonical = new StringBuilder(4096);
|
||||||
|
Optional<ChunkGenerationSemantics> semantics = router.history().semantics(chunkX, chunkZ);
|
||||||
|
canonical.append(semantics.map(value -> value + "|" + value.pointsOfInterest()).orElse("none")).append('\n');
|
||||||
|
Optional<SavedBiomeChunk> biomes = router.biomes().snapshot(chunkX, chunkZ);
|
||||||
|
if (biomes.isEmpty()) {
|
||||||
|
canonical.append("none");
|
||||||
|
} else {
|
||||||
|
SavedBiomeChunk chunk = biomes.get();
|
||||||
|
canonical.append(chunk.header());
|
||||||
|
for (int localZ = 0; localZ < 16; localZ++) {
|
||||||
|
for (int localX = 0; localX < 16; localX++) {
|
||||||
|
canonical.append('\n').append(chunk.column(localX, localZ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chunks.put(((long) chunkX << 32) | (chunkZ & 0xFFFFFFFFL), sha256(canonical.toString().getBytes(StandardCharsets.UTF_8)));
|
||||||
|
}
|
||||||
|
|
||||||
|
String summary() {
|
||||||
|
MessageDigest digest = newDigest();
|
||||||
|
ByteBuffer key = ByteBuffer.allocate(Long.BYTES);
|
||||||
|
for (Map.Entry<Long, byte[]> entry : chunks.entrySet()) {
|
||||||
|
key.clear();
|
||||||
|
key.putLong(entry.getKey());
|
||||||
|
digest.update(key.array());
|
||||||
|
digest.update(entry.getValue());
|
||||||
|
}
|
||||||
|
return "IRIS_GENPROBE_HISTORY version=1 chunks=" + chunks.size()
|
||||||
|
+ " digest=" + HexFormat.of().formatHex(digest.digest(), 0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] sha256(byte[] bytes) {
|
||||||
|
return newDigest().digest(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MessageDigest newDigest() {
|
||||||
|
try {
|
||||||
|
return MessageDigest.getInstance("SHA-256");
|
||||||
|
} catch (NoSuchAlgorithmException missing) {
|
||||||
|
throw new IllegalStateException("SHA-256 is unavailable", missing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,12 +36,19 @@ final class ProbeChunkGenerator {
|
|||||||
private final Engine engine;
|
private final Engine engine;
|
||||||
private final GenerationHistoryRuntimeRouter router;
|
private final GenerationHistoryRuntimeRouter router;
|
||||||
private final boolean productionBuffers;
|
private final boolean productionBuffers;
|
||||||
|
private final HistoryDigest historyDigest;
|
||||||
private final AtomicInteger expandedBiomeHunks = new AtomicInteger();
|
private final AtomicInteger expandedBiomeHunks = new AtomicInteger();
|
||||||
|
|
||||||
ProbeChunkGenerator(Engine engine, GenerationHistoryRuntimeRouter router, boolean productionBuffers) {
|
ProbeChunkGenerator(Engine engine, GenerationHistoryRuntimeRouter router, boolean productionBuffers,
|
||||||
|
boolean historyDigest) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.router = router;
|
this.router = router;
|
||||||
this.productionBuffers = productionBuffers;
|
this.productionBuffers = productionBuffers;
|
||||||
|
this.historyDigest = historyDigest && router != null ? new HistoryDigest(router) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String historySummary() {
|
||||||
|
return historyDigest == null ? null : historyDigest.summary();
|
||||||
}
|
}
|
||||||
|
|
||||||
Engine engine() {
|
Engine engine() {
|
||||||
@@ -64,6 +71,13 @@ final class ProbeChunkGenerator {
|
|||||||
failures.add(e);
|
failures.add(e);
|
||||||
}
|
}
|
||||||
long elapsed = System.nanoTime() - started;
|
long elapsed = System.nanoTime() - started;
|
||||||
|
if (historyDigest != null && failures.isEmpty()) {
|
||||||
|
try {
|
||||||
|
historyDigest.record(coordinate.x(), coordinate.z());
|
||||||
|
} catch (Throwable e) {
|
||||||
|
failures.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (biomes instanceof ProductionBiomeHunk production && production.expanded()) {
|
if (biomes instanceof ProductionBiomeHunk production && production.expanded()) {
|
||||||
expandedBiomeHunks.incrementAndGet();
|
expandedBiomeHunks.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ public final class GenerationProbeTest {
|
|||||||
assertEquals(1, defaults.threads());
|
assertEquals(1, defaults.threads());
|
||||||
assertNull(defaults.dumpFile());
|
assertNull(defaults.dumpFile());
|
||||||
assertThrows(IllegalArgumentException.class,
|
assertThrows(IllegalArgumentException.class,
|
||||||
() -> new GenerationProbe.ProbeOptions(0, null, false, false, false, false, false));
|
() -> new GenerationProbe.ProbeOptions(0, null, false, false, false, false, false, false));
|
||||||
assertFalse(defaults.hydrologyPreplan());
|
assertFalse(defaults.hydrologyPreplan());
|
||||||
assertFalse(defaults.historyRouter());
|
assertFalse(defaults.historyRouter());
|
||||||
assertFalse(defaults.productionBuffers());
|
assertFalse(defaults.productionBuffers());
|
||||||
|
|||||||
Reference in new issue
Block a user