mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-11 19:14:11 +00:00
Enable native noise generation
- Enable exact Rust Billow sampling and report the selected backend. - Add generation parity probes and native comparison tooling.
This commit is contained in:
@@ -76,3 +76,8 @@ BUILDS/
|
||||
.compatwt/
|
||||
|
||||
.worktrees/
|
||||
|
||||
/VolmLib/
|
||||
/docs/
|
||||
/.perf/
|
||||
/.codegraph/
|
||||
|
||||
@@ -54,6 +54,7 @@ import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||
import art.arcane.volmlib.util.hunk.Hunk;
|
||||
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.volmlib.util.noise.FractalBillowSimplexNoise;
|
||||
import art.arcane.volmlib.util.matter.MatterStructurePOI;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
@@ -312,7 +313,8 @@ public class IrisEngine implements Engine {
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
IrisLogging.notice("Engine init: " + requiredTarget.getWorld().name() + "/" + requiredTarget.getDimension().getLoadKey() + " seed=" + getSeedManager().getSeed());
|
||||
IrisLogging.notice("Engine init: " + requiredTarget.getWorld().name() + "/" + requiredTarget.getDimension().getLoadKey()
|
||||
+ " seed=" + getSeedManager().getSeed() + " billow2D=" + FractalBillowSimplexNoise.nativeBackendStatus());
|
||||
_t0 = M.ms();
|
||||
phaseStartedAt = System.nanoTime();
|
||||
EngineRuntime initialRuntime = runtimeBuilder.buildRuntime(cacheIdentity);
|
||||
|
||||
+17
-2
@@ -154,10 +154,24 @@ tasks.register('terrain3DProbe', JavaExec) {
|
||||
|
||||
tasks.register('genProbe', JavaExec) {
|
||||
group = 'verification'
|
||||
description = 'Constructs a real Iris engine over a real pack and generates chunks into buffers on a Bukkit-free JVM.'
|
||||
description = 'Constructs a real Iris engine over a real pack and verifies generation hashes and optional native backend usage.'
|
||||
mainClass = 'art.arcane.iris.probe.GenerationProbe'
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
jvmArgs('--add-modules', 'jdk.incubator.vector')
|
||||
minHeapSize = '2g'
|
||||
maxHeapSize = '6g'
|
||||
if (providers.gradleProperty('probeNativeAccess').orElse('false').get().toBoolean()) {
|
||||
jvmArgs('--enable-native-access=ALL-UNNAMED')
|
||||
}
|
||||
if (providers.gradleProperty('probeNativeBillow').isPresent()) {
|
||||
systemProperty('volmlib.noise.nativeBillow', providers.gradleProperty('probeNativeBillow').get())
|
||||
}
|
||||
systemProperty('volmlib.noise.nativeBillowDiagnostics', providers.gradleProperty('probeNativeDiagnostics').orElse('false').get())
|
||||
systemProperty('iris.probe.expectedBackend', providers.gradleProperty('probeExpectedBackend').orElse('').get())
|
||||
systemProperty('iris.probe.requireNativeSamples', providers.gradleProperty('probeRequireNativeSamples').orElse('false').get())
|
||||
if (providers.gradleProperty('probeNativeLibrary').isPresent()) {
|
||||
systemProperty('volmlib.noise.nativeBillowLibrary', providers.gradleProperty('probeNativeLibrary').get())
|
||||
}
|
||||
dependsOn(':core:compileJava')
|
||||
doFirst {
|
||||
if (!probePack.isPresent() || probePack.get().isBlank()) {
|
||||
@@ -174,7 +188,8 @@ tasks.register('genProbe', JavaExec) {
|
||||
probeCenterChunkX.get(),
|
||||
probeCenterChunkZ.get(),
|
||||
probeMulticore.get(),
|
||||
probeStudio.get())
|
||||
probeStudio.get(),
|
||||
probeSeed.orElse('1337').get())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import art.arcane.iris.spi.IrisServices;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBiome;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.volmlib.util.hunk.Hunk;
|
||||
import art.arcane.volmlib.util.noise.FractalBillowSimplexNoise;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -58,7 +59,6 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class GenerationProbe {
|
||||
private static final long SEED = 1337L;
|
||||
private static final List<Throwable> REPORTED = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
private static final class InertPreservation implements PreservationRegistry {
|
||||
@@ -126,7 +126,7 @@ public final class GenerationProbe {
|
||||
}
|
||||
|
||||
record ProbeConfiguration(File packSource, String dimensionKey, int warmupChunks, int measuredChunks,
|
||||
int centerChunkX, int centerChunkZ, boolean multicore, boolean studio) {
|
||||
int centerChunkX, int centerChunkZ, boolean multicore, boolean studio, long seed) {
|
||||
ProbeConfiguration {
|
||||
if (packSource == null) {
|
||||
throw new IllegalArgumentException("Pack folder is required.");
|
||||
@@ -143,8 +143,8 @@ public final class GenerationProbe {
|
||||
}
|
||||
|
||||
static ProbeConfiguration parse(String[] args) {
|
||||
if (args.length != 8) {
|
||||
throw new IllegalArgumentException("Expected: <pack> <dimension> <warmupChunks> <measuredChunks> <centerChunkX> <centerChunkZ> <multicore> <studio>");
|
||||
if (args.length != 9) {
|
||||
throw new IllegalArgumentException("Expected: <pack> <dimension> <warmupChunks> <measuredChunks> <centerChunkX> <centerChunkZ> <multicore> <studio> <seed>");
|
||||
}
|
||||
return new ProbeConfiguration(
|
||||
new File(args[0]),
|
||||
@@ -154,7 +154,8 @@ public final class GenerationProbe {
|
||||
Integer.parseInt(args[4]),
|
||||
Integer.parseInt(args[5]),
|
||||
Boolean.parseBoolean(args[6]),
|
||||
Boolean.parseBoolean(args[7]));
|
||||
Boolean.parseBoolean(args[7]),
|
||||
Long.parseLong(args[8]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +278,9 @@ public final class GenerationProbe {
|
||||
throw new IllegalArgumentException("Pack folder not found: " + configuration.packSource().getAbsolutePath());
|
||||
}
|
||||
|
||||
File workRoot = Files.createTempDirectory("iris-genprobe-").toFile();
|
||||
Path temporaryRoot = Path.of("build", "generation-probe").toAbsolutePath();
|
||||
Files.createDirectories(temporaryRoot);
|
||||
File workRoot = Files.createTempDirectory(temporaryRoot, "iris-genprobe-").toFile();
|
||||
IrisData data = null;
|
||||
Engine engine = null;
|
||||
Throwable executionFailure = null;
|
||||
@@ -288,6 +291,7 @@ public final class GenerationProbe {
|
||||
|
||||
System.out.println("[genprobe] pack: " + configuration.packSource().getAbsolutePath());
|
||||
System.out.println("[genprobe] dimension: " + configuration.dimensionKey());
|
||||
System.out.println("[genprobe] seed: " + configuration.seed());
|
||||
System.out.println("[genprobe] warmup chunks: " + configuration.warmupChunks());
|
||||
System.out.println("[genprobe] measured chunks: " + configuration.measuredChunks());
|
||||
System.out.println("[genprobe] center chunk: " + configuration.centerChunkX() + "," + configuration.centerChunkZ());
|
||||
@@ -304,7 +308,7 @@ public final class GenerationProbe {
|
||||
IrisWorld world = IrisWorld.builder()
|
||||
.platformIdentity("iris:probe")
|
||||
.name("probe")
|
||||
.seed(SEED)
|
||||
.seed(configuration.seed())
|
||||
.worldFolder(new File(workRoot, "world"))
|
||||
.minHeight(dimension.getMinHeight())
|
||||
.maxHeight(dimension.getMaxHeight())
|
||||
@@ -323,7 +327,9 @@ public final class GenerationProbe {
|
||||
+ " seed=" + engine.getSeedManager().getSeed()
|
||||
+ " minY=" + engine.getMinHeight() + " maxY=" + engine.getMaxHeight()
|
||||
+ " timeMs=" + String.format(Locale.ROOT, "%.3f", nanosToMillis(engineReadyNanos)));
|
||||
long nativeSamplesBefore = FractalBillowSimplexNoise.nativeSampleCount();
|
||||
GenerationResult generation = generate(engine, configuration);
|
||||
verifyBackend(nativeSamplesBefore);
|
||||
String status = generation.failedChunks() == 0 ? "PASS" : "FAIL";
|
||||
printGenerationFailures(generation);
|
||||
return new ProbeResult(
|
||||
@@ -354,6 +360,20 @@ public final class GenerationProbe {
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyBackend(long nativeSamplesBefore) {
|
||||
String backend = FractalBillowSimplexNoise.nativeBackendStatus();
|
||||
long nativeSamples = FractalBillowSimplexNoise.nativeSampleCount() - nativeSamplesBefore;
|
||||
System.out.println("IRIS_GENPROBE_BACKEND status=" + backend + " native_samples=" + nativeSamples
|
||||
+ " diagnostics=" + Boolean.getBoolean("volmlib.noise.nativeBillowDiagnostics"));
|
||||
String expected = System.getProperty("iris.probe.expectedBackend", "");
|
||||
if (!expected.isEmpty() && !expected.equals(backend)) {
|
||||
throw new IllegalStateException("Expected backend " + expected + ", received " + backend);
|
||||
}
|
||||
if (Boolean.getBoolean("iris.probe.requireNativeSamples") && nativeSamples <= 0L) {
|
||||
throw new IllegalStateException("Real chunk generation did not report native Billow samples.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void configureProbeRuntime(File workRoot) {
|
||||
REPORTED.clear();
|
||||
StubPlatform.bindGenerationStateHandlers();
|
||||
|
||||
@@ -24,7 +24,8 @@ public final class GenerationProbeTest {
|
||||
"2048",
|
||||
"-2048",
|
||||
"true",
|
||||
"true"
|
||||
"true",
|
||||
"-1"
|
||||
});
|
||||
|
||||
assertEquals(new File("/tmp/pack"), configuration.packSource());
|
||||
@@ -35,6 +36,7 @@ public final class GenerationProbeTest {
|
||||
assertEquals(-2048, configuration.centerChunkZ());
|
||||
assertTrue(configuration.multicore());
|
||||
assertTrue(configuration.studio());
|
||||
assertEquals(-1L, configuration.seed());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -42,11 +44,25 @@ public final class GenerationProbeTest {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> GenerationProbe.ProbeConfiguration.parse(new String[]{"/tmp/pack"}));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> new GenerationProbe.ProbeConfiguration(new File("/tmp/pack"), " ", 1, 1, 0, 0, false, false));
|
||||
() -> new GenerationProbe.ProbeConfiguration(new File("/tmp/pack"), " ", 1, 1, 0, 0, false, false, 1337L));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> new GenerationProbe.ProbeConfiguration(new File("/tmp/pack"), "overworld", 0, 1, 0, 0, false, false));
|
||||
() -> new GenerationProbe.ProbeConfiguration(new File("/tmp/pack"), "overworld", 0, 1, 0, 0, false, false, 1337L));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> new GenerationProbe.ProbeConfiguration(new File("/tmp/pack"), "overworld", 1, 0, 0, 0, false, false));
|
||||
() -> new GenerationProbe.ProbeConfiguration(new File("/tmp/pack"), "overworld", 1, 0, 0, 0, false, false, 1337L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsesSignedBorderCoordinatesAndFullWidthSeeds() {
|
||||
GenerationProbe.ProbeConfiguration configuration = GenerationProbe.ProbeConfiguration.parse(new String[]{
|
||||
"/tmp/pack", "overworld", "32", "96", "1874000", "-1874000", "false", "false",
|
||||
Long.toString(Long.MIN_VALUE)
|
||||
});
|
||||
assertEquals(Long.MIN_VALUE, configuration.seed());
|
||||
assertEquals(-1874000, configuration.centerChunkZ());
|
||||
assertThrows(NumberFormatException.class,
|
||||
() -> GenerationProbe.ProbeConfiguration.parse(new String[]{
|
||||
"/tmp/pack", "overworld", "32", "96", "0", "0", "false", "false", "invalid"
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import argparse
|
||||
import fcntl
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
|
||||
def fields(line):
|
||||
return dict(part.split('=', 1) for part in line.split()[1:])
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--pack', required=True)
|
||||
parser.add_argument('--dimension', required=True)
|
||||
parser.add_argument('--volmlib', required=True)
|
||||
parser.add_argument('--seed', type=int, default=1337)
|
||||
parser.add_argument('--center-x', type=int, default=2048)
|
||||
parser.add_argument('--center-z', type=int, default=2048)
|
||||
parser.add_argument('--warmup', type=int, default=64)
|
||||
parser.add_argument('--measured', type=int, default=192)
|
||||
parser.add_argument('--pairs', type=int, default=2)
|
||||
parser.add_argument('--diagnostics', action='store_true')
|
||||
parser.add_argument('--output', required=True)
|
||||
args = parser.parse_args()
|
||||
if args.pairs < 1:
|
||||
parser.error('--pairs must be positive')
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
output = Path(args.output).resolve()
|
||||
if not any(output.is_relative_to(root / allowed) for allowed in ('.perf', 'build')):
|
||||
parser.error('--output must be inside this checkout\'s .perf or build directory')
|
||||
output.mkdir(parents=True, exist_ok=False)
|
||||
environment = os.environ.copy()
|
||||
environment.pop('GIT_CONFIG_COUNT', None)
|
||||
for variable in ('JAVA_TOOL_OPTIONS', '_JAVA_OPTIONS', 'JDK_JAVA_OPTIONS'):
|
||||
environment.pop(variable, None)
|
||||
results = []
|
||||
expected_hashes = None
|
||||
with open('/tmp/iris-acceleration-integration-benchmark.lock', 'a') as lock:
|
||||
print('Waiting for generation benchmark lock', flush=True)
|
||||
fcntl.flock(lock, fcntl.LOCK_EX)
|
||||
for pair in range(args.pairs):
|
||||
for native in ([False, True] if pair % 2 == 0 else [True, False]):
|
||||
backend = 'native' if native else 'java'
|
||||
command = [str(root / 'gradlew'), ':probe:genProbe', '--no-daemon',
|
||||
'-PincludeNativeBillow=true', f'-PlocalVolmLibDirectory={args.volmlib}',
|
||||
f'-PprobePack={Path(args.pack).resolve()}', f'-PprobeDimension={args.dimension}',
|
||||
f'-PprobeSeed={args.seed}', f'-PprobeCenterChunkX={args.center_x}',
|
||||
f'-PprobeCenterChunkZ={args.center_z}', f'-PprobeWarmupChunks={args.warmup}',
|
||||
f'-PprobeMeasuredChunks={args.measured}', '-PprobeMulticore=false',
|
||||
'-PprobeStudio=false', f'-PprobeNativeBillow={str(native).lower()}',
|
||||
'-PprobeNativeAccess=true',
|
||||
f'-PprobeNativeDiagnostics={str(args.diagnostics).lower()}',
|
||||
f'-PprobeRequireNativeSamples={str(native and args.diagnostics).lower()}',
|
||||
f'-PprobeExpectedBackend={"native" if native else "java-disabled"}']
|
||||
label = f'{pair + 1}-{backend}'
|
||||
(output / f'{label}.command.json').write_text(json.dumps(command, indent=2) + '\n')
|
||||
print(f'Running {label}', flush=True)
|
||||
started = time.monotonic()
|
||||
with (output / f'{label}.log').open('w') as log:
|
||||
completed = subprocess.run(command, cwd=root, env=environment, stdout=log,
|
||||
stderr=subprocess.STDOUT, check=False)
|
||||
text = (output / f'{label}.log').read_text()
|
||||
lines = text.splitlines()
|
||||
hashes = [line for line in lines if line.startswith('GENPROBE_CHUNK_HASH ')]
|
||||
result_lines = [line for line in lines if line.startswith('IRIS_GENPROBE_RESULT ')]
|
||||
backend_lines = [line for line in lines if line.startswith('IRIS_GENPROBE_BACKEND ')]
|
||||
if completed.returncode != 0 or len(result_lines) != 1 or len(backend_lines) != 1:
|
||||
raise RuntimeError(f'{label} failed; see {output / (label + ".log")}')
|
||||
result = fields(result_lines[0])
|
||||
status = fields(backend_lines[0])
|
||||
if result['status'] != 'PASS' or len(hashes) != args.warmup + args.measured:
|
||||
raise RuntimeError(f'{label} did not generate every chunk successfully')
|
||||
expected_backend = 'native' if native else 'java-disabled'
|
||||
if status['status'] != expected_backend:
|
||||
raise RuntimeError(f'{label} backend mismatch: {status}')
|
||||
if expected_hashes is None:
|
||||
expected_hashes = hashes
|
||||
elif hashes != expected_hashes:
|
||||
raise RuntimeError(f'{label} chunk block/biome hashes differ')
|
||||
results.append({'pair': pair + 1, 'backend': backend, 'result': result,
|
||||
'backend_status': status, 'wall_seconds': time.monotonic() - started})
|
||||
(output / 'results.json').write_text(json.dumps(results, indent=2) + '\n')
|
||||
rates = {backend: statistics.median(float(run['result']['measured_cps']) for run in results
|
||||
if run['backend'] == backend) for backend in ('java', 'native')}
|
||||
summary = {'median_chunks_per_second': rates,
|
||||
'native_throughput_change_percent': (rates['native'] / rates['java'] - 1) * 100,
|
||||
'all_chunk_hashes_equal': True, 'diagnostics_enabled': args.diagnostics,
|
||||
'run_count': len(results), 'seed': args.seed,
|
||||
'unique_chunks': args.warmup + args.measured}
|
||||
(output / 'summary.json').write_text(json.dumps(summary, indent=2) + '\n')
|
||||
print(json.dumps(summary, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user