268 lines
16 KiB
Python
268 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""Compile only original unobfuscated-26.3 mod sources; never bundle game classes."""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import platform
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import zipfile
|
|
|
|
try:
|
|
from . import lab, runtime
|
|
except ImportError:
|
|
import lab
|
|
import runtime
|
|
|
|
ROOT = lab.ROOT
|
|
SOURCE = ROOT / "mods/horizon-lab/src"
|
|
BUILD = ROOT / ".local/mod-build"
|
|
|
|
|
|
def test_metrics():
|
|
destination = BUILD / "metric-tests"
|
|
destination.mkdir(parents=True, exist_ok=True)
|
|
sources = [SOURCE / "main/java/dev/horizonlab/metrics" / name
|
|
for name in ["SampleStats.java", "Recording.java", "ProcessMemoryMonitor.java"]]
|
|
sources.append(SOURCE / "test/java/dev/horizonlab/metrics/MetricsTest.java")
|
|
subprocess.run(["javac", "--release", "25", "-d", str(destination), *map(str, sources)], check=True)
|
|
subprocess.run(["java", "-cp", str(destination), "dev.horizonlab.metrics.MetricsTest"], check=True)
|
|
subprocess.run(["java", "-XX:+UseCompactObjectHeaders", "-cp", str(destination),
|
|
"dev.horizonlab.metrics.MetricsTest"], check=True)
|
|
subprocess.run(["java", "-Xms32M", "-Xmx256M", "-XX:+UseZGC", "-XX:+UseCompactObjectHeaders", "-cp", str(destination),
|
|
"dev.horizonlab.metrics.MetricsTest"], check=True)
|
|
budget_sources = [SOURCE / "main/java/dev/horizonlab/budget/TerrainBudget.java",
|
|
SOURCE / "test/java/dev/horizonlab/budget/TerrainBudgetTest.java"]
|
|
subprocess.run(["javac", "--release", "25", "-d", str(destination), *map(str, budget_sources)], check=True)
|
|
subprocess.run(["java", "-cp", str(destination), "dev.horizonlab.budget.TerrainBudgetTest"], check=True)
|
|
worker_sources = [SOURCE / "main/java/dev/horizonlab/budget/WorkerPacer.java",
|
|
SOURCE / "test/java/dev/horizonlab/budget/WorkerPacerTest.java"]
|
|
subprocess.run(["javac", "--release", "25", "-d", str(destination), *map(str, worker_sources)], check=True)
|
|
subprocess.run(["java", "-cp", str(destination), "dev.horizonlab.budget.WorkerPacerTest"], check=True)
|
|
debounce_sources = [SOURCE / "main/java/dev/horizonlab/budget/DebounceWait.java",
|
|
SOURCE / "test/java/dev/horizonlab/budget/DebounceWaitTest.java"]
|
|
subprocess.run(["javac", "--release", "25", "-d", str(destination), *map(str, debounce_sources)], check=True)
|
|
subprocess.run(["java", "-cp", str(destination), "dev.horizonlab.budget.DebounceWaitTest"], check=True)
|
|
idle_sources = [SOURCE / "main/java/dev/horizonlab/budget/WakeableIdleWait.java",
|
|
SOURCE / "test/java/dev/horizonlab/budget/WakeableIdleWaitTest.java"]
|
|
subprocess.run(["javac", "--release", "25", "-d", str(destination), *map(str, idle_sources)], check=True)
|
|
subprocess.run(["java", "-cp", str(destination), "dev.horizonlab.budget.WakeableIdleWaitTest"], check=True)
|
|
dispatch_sources = [SOURCE / "main/java/dev/horizonlab/budget/DispatchRetry.java",
|
|
SOURCE / "test/java/dev/horizonlab/budget/DispatchRetryTest.java"]
|
|
subprocess.run(["javac", "--release", "25", "-d", str(destination), *map(str, dispatch_sources)], check=True)
|
|
subprocess.run(["java", "-cp", str(destination), "dev.horizonlab.budget.DispatchRetryTest"], check=True)
|
|
|
|
|
|
def build():
|
|
lock = json.loads((ROOT / "packs/mods.lock.json").read_text())
|
|
_, jars, _ = runtime.prepare_client(assets=False)
|
|
with zipfile.ZipFile(jars[0]) as game_archive:
|
|
game_version = json.loads(game_archive.read("version.json"))
|
|
if game_version["world_version"] != lock["options_data_version"]:
|
|
raise ValueError("Pack options data version does not match the verified game JAR")
|
|
dh = lock["projects"]["distanthorizons"]
|
|
jars.append(runtime.fetch(dh["url"], ROOT / ".local/pack-cache" / dh["filename"],
|
|
dh["hashes"]["sha512"], "sha512"))
|
|
culling = lock["projects"]["entityculling"]
|
|
jars.append(runtime.fetch(culling["url"], ROOT / ".local/pack-cache" / culling["filename"],
|
|
culling["hashes"]["sha512"], "sha512"))
|
|
api = lock["projects"]["fabric-api"]
|
|
api_jar = runtime.fetch(api["url"], ROOT / ".local/pack-cache" / api["filename"],
|
|
api["hashes"]["sha512"], "sha512")
|
|
nested = BUILD / "fabric-api"
|
|
nested.mkdir(parents=True, exist_ok=True)
|
|
with zipfile.ZipFile(api_jar) as archive:
|
|
for name in archive.namelist():
|
|
if name.startswith("META-INF/jars/") and name.endswith(".jar"):
|
|
destination = nested / Path(name).name
|
|
destination.write_bytes(archive.read(name))
|
|
jars.append(destination)
|
|
classes = BUILD / "classes"
|
|
if classes.exists():
|
|
shutil.rmtree(classes)
|
|
classes.mkdir(parents=True)
|
|
sources = sorted((SOURCE / "main/java").rglob("*.java"))
|
|
command = ["--release", "25", "-proc:none", "-encoding", "UTF-8", "-d", str(classes),
|
|
"-classpath", os.pathsep.join(map(str, jars)), *map(str, sources)]
|
|
argfile = BUILD / "javac.args"
|
|
argfile.write_text("\n".join(json.dumps(arg) for arg in command) + "\n")
|
|
subprocess.run(["javac", "@" + str(argfile)], cwd=ROOT, check=True)
|
|
adapter_tests = BUILD / "adapter-tests"
|
|
adapter_tests.mkdir(parents=True, exist_ok=True)
|
|
test_classpath = os.pathsep.join(map(str, [classes, *jars]))
|
|
subprocess.run(["javac", "--release", "25", "-proc:none", "-classpath", test_classpath,
|
|
"-d", str(adapter_tests),
|
|
str(SOURCE / "test/java/dev/horizonlab/client/DistantTerrainBudgetTest.java"),
|
|
str(SOURCE / "test/java/dev/horizonlab/client/DistantTerrainSettingsTest.java"),
|
|
str(SOURCE / "test/java/dev/horizonlab/geometry/RelativeTerrainPositionTest.java"),
|
|
str(SOURCE / "test/java/dev/horizonlab/metrics/ResidentMemoryTest.java")], check=True)
|
|
subprocess.run(["java", "-cp", os.pathsep.join([str(adapter_tests), test_classpath]),
|
|
"dev.horizonlab.client.DistantTerrainBudgetTest"], check=True)
|
|
subprocess.run(["java", "-cp", os.pathsep.join([str(adapter_tests), test_classpath]),
|
|
"dev.horizonlab.client.DistantTerrainSettingsTest"], check=True)
|
|
subprocess.run(["java", "--enable-native-access=ALL-UNNAMED", "-cp", os.pathsep.join([str(adapter_tests), test_classpath]),
|
|
"dev.horizonlab.metrics.ResidentMemoryTest"], check=True)
|
|
subprocess.run(["java", "-cp", os.pathsep.join([str(adapter_tests), test_classpath]),
|
|
"dev.horizonlab.geometry.RelativeTerrainPositionTest"], check=True)
|
|
output = ROOT / "dist" / f"horizon-lab-{lock['pack_version']}.jar"
|
|
output.parent.mkdir(exist_ok=True)
|
|
entries = {path.relative_to(classes).as_posix(): path.read_bytes() for path in classes.rglob("*.class")}
|
|
if any(not name.startswith("dev/horizonlab/") for name in entries):
|
|
raise ValueError("Only original Horizon Lab classes may be packaged")
|
|
resources = SOURCE / "main/resources"
|
|
entries.update({path.relative_to(resources).as_posix(): path.read_bytes() for path in resources.rglob("*") if path.is_file()})
|
|
entries["LICENSE"] = (ROOT / "LICENSE").read_bytes()
|
|
with zipfile.ZipFile(output, "w", compression=zipfile.ZIP_DEFLATED) as archive:
|
|
for name in sorted(entries):
|
|
info = zipfile.ZipInfo(name, date_time=(2026, 1, 1, 0, 0, 0))
|
|
info.compress_type = zipfile.ZIP_DEFLATED
|
|
info.external_attr = 0o100644 << 16
|
|
archive.writestr(info, entries[name])
|
|
print(f"Built {output} ({output.stat().st_size} bytes, sha256 {lab.digest(output, 'sha256')})")
|
|
return output
|
|
|
|
|
|
def build_dispatch_fixture():
|
|
return build_integration_fixture("dispatch", "dev.horizonlab.dispatch.DispatchIntegration")
|
|
|
|
|
|
def build_tick_dedup_fixture():
|
|
return build_integration_fixture("tick-dedup", "dev.horizonlab.dispatch.TickDedupIntegration")
|
|
|
|
|
|
def build_culling_empty_fixture():
|
|
return build_integration_fixture("culling-empty", "dev.horizonlab.dispatch.CullingEmptyIntegration", ["entityculling"])
|
|
|
|
|
|
def build_integration_fixture(name, entrypoint, extra_projects=(), fixture_mixins=()):
|
|
"""Create a private integration-test mod; never include it in release packs."""
|
|
arguments = [json.loads(line) for line in (BUILD / "javac.args").read_text().splitlines()]
|
|
classpath = arguments[arguments.index("-classpath") + 1] + os.pathsep + str(BUILD / "classes")
|
|
lock = json.loads((ROOT / "packs/mods.lock.json").read_text())
|
|
for name_project in extra_projects:
|
|
project = lock["projects"][name_project]
|
|
jar = runtime.fetch(project["url"], ROOT / ".local/pack-cache" / project["filename"],
|
|
project["hashes"]["sha512"], "sha512")
|
|
classpath += os.pathsep + str(jar)
|
|
classes = BUILD / f"{name}-fixture"
|
|
if classes.exists():
|
|
shutil.rmtree(classes)
|
|
classes.mkdir(parents=True)
|
|
fixture_sources = [SOURCE / "test/java" / (entrypoint.replace(".", "/") + ".java")]
|
|
fixture_sources.extend(SOURCE / "test/java/dev/horizonlab/dispatch/fixture" / (mixin + ".java") for mixin in fixture_mixins)
|
|
subprocess.run(["javac", "--release", "25", "-proc:none", "-classpath", classpath,
|
|
"-d", str(classes), *map(str, fixture_sources)], check=True)
|
|
output = BUILD / f"{name}-fixture.jar"
|
|
metadata = {"schemaVersion": 1, "id": "horizon_" + name.replace("-", "_") + "_fixture",
|
|
"version": "1.0.0", "environment": "client", "entrypoints": {"client": [entrypoint]},
|
|
"depends": {"horizonlab": "*", "distanthorizons": "3.3.4"}}
|
|
if fixture_mixins:
|
|
metadata["mixins"] = [name + "-fixture.mixins.json"]
|
|
with zipfile.ZipFile(output, "w", compression=zipfile.ZIP_DEFLATED) as archive:
|
|
if fixture_mixins:
|
|
archive.writestr(metadata["mixins"][0], json.dumps({"required": True, "package": "dev.horizonlab.dispatch.fixture",
|
|
"compatibilityLevel": "JAVA_25", "client": list(fixture_mixins), "injectors": {"defaultRequire": 1}}))
|
|
for path in sorted(classes.rglob("*.class")):
|
|
archive.write(path, path.relative_to(classes).as_posix())
|
|
archive.writestr("fabric.mod.json", json.dumps(metadata))
|
|
print(f"Private {name} integration fixture: {output}")
|
|
return output
|
|
|
|
|
|
def benchmark_workers():
|
|
arguments = [json.loads(line) for line in (BUILD / "javac.args").read_text().splitlines()]
|
|
classpath = os.pathsep.join([str(BUILD / "classes"), arguments[arguments.index("-classpath") + 1]])
|
|
tests = BUILD / "worker-benchmark"
|
|
tests.mkdir(parents=True, exist_ok=True)
|
|
subprocess.run(["javac", "--release", "25", "-proc:none", "-cp", classpath,
|
|
"-d", str(tests), str(SOURCE / "test/java/dev/horizonlab/budget/WorkerPacingBenchmark.java")], check=True)
|
|
output = ROOT / "dist/worker-pacing-benchmark.txt"
|
|
with output.open("w", encoding="utf-8") as report:
|
|
report.write(f"Java {subprocess.check_output(['java', '--version'], text=True).splitlines()[0]}\n")
|
|
report.write(f"Platform {platform.system()} {platform.release()} {platform.machine()}; logical CPUs {os.cpu_count()}\n")
|
|
report.flush()
|
|
for repeat in range(3):
|
|
for ratio in ["0.5", "0.05"]:
|
|
for mode in ["upstream", "precise"]:
|
|
subprocess.run(["java", "-cp", os.pathsep.join([str(tests), classpath]),
|
|
"dev.horizonlab.budget.WorkerPacingBenchmark", ratio, "10000", "50000", mode],
|
|
stdout=report, check=True)
|
|
print(output.read_text(), end="")
|
|
|
|
|
|
def benchmark_debounce():
|
|
arguments = [json.loads(line) for line in (BUILD / "javac.args").read_text().splitlines()]
|
|
classpath = os.pathsep.join([str(BUILD / "classes"), arguments[arguments.index("-classpath") + 1]])
|
|
tests = BUILD / "debounce-benchmark"
|
|
tests.mkdir(parents=True, exist_ok=True)
|
|
subprocess.run(["javac", "--release", "25", "-proc:none", "-cp", classpath,
|
|
"-d", str(tests), str(SOURCE / "test/java/dev/horizonlab/budget/DebounceQueueBenchmark.java")], check=True)
|
|
output = ROOT / "dist/debounce-queue-benchmark.txt"
|
|
with output.open("w", encoding="utf-8") as report:
|
|
report.write(f"Java {subprocess.check_output(['java', '--version'], text=True).splitlines()[0]}\n")
|
|
report.write(f"Platform {platform.system()} {platform.release()} {platform.machine()}; logical CPUs {os.cpu_count()}\n")
|
|
report.flush()
|
|
subprocess.run(["java", "-cp", os.pathsep.join([str(tests), classpath]),
|
|
"dev.horizonlab.budget.DebounceQueueBenchmark"], stdout=report, check=True)
|
|
print(output.read_text(), end="")
|
|
|
|
|
|
def benchmark_headers():
|
|
arguments = [json.loads(line) for line in (BUILD / "javac.args").read_text().splitlines()]
|
|
classpath = arguments[arguments.index("-classpath") + 1]
|
|
tests = BUILD / "header-benchmark"
|
|
tests.mkdir(parents=True, exist_ok=True)
|
|
subprocess.run(["javac", "--release", "25", "-proc:none", "-cp", classpath,
|
|
"-d", str(tests), str(SOURCE / "test/java/dev/horizonlab/metrics/ObjectHeaderBenchmark.java")], check=True)
|
|
output = ROOT / "dist/object-header-benchmark.txt"
|
|
with output.open("w", encoding="utf-8") as report:
|
|
report.write(f"Java {subprocess.check_output(['java', '--version'], text=True).splitlines()[0]}\n")
|
|
report.write(f"Platform {platform.system()} {platform.release()} {platform.machine()}; logical CPUs {os.cpu_count()}\n")
|
|
report.flush()
|
|
for repeat in range(3):
|
|
for enabled in [False, True]:
|
|
report.write(f"fork={repeat} requested_compact_headers={enabled}\n")
|
|
report.flush()
|
|
subprocess.run(["java", "-Xms128M", "-Xmx512M",
|
|
"-XX:" + ("+" if enabled else "-") + "UseCompactObjectHeaders",
|
|
"-cp", os.pathsep.join([str(tests), classpath]),
|
|
"dev.horizonlab.metrics.ObjectHeaderBenchmark", str(enabled).lower()],
|
|
stdout=report, check=True)
|
|
print(output.read_text(), end="")
|
|
|
|
|
|
def benchmark_idle():
|
|
arguments = [json.loads(line) for line in (BUILD / "javac.args").read_text().splitlines()]
|
|
classpath = os.pathsep.join([str(BUILD / "classes"), arguments[arguments.index("-classpath") + 1]])
|
|
tests = BUILD / "idle-benchmark"
|
|
tests.mkdir(parents=True, exist_ok=True)
|
|
subprocess.run(["javac", "--release", "25", "-proc:none", "-cp", classpath,
|
|
"-d", str(tests), str(SOURCE / "test/java/dev/horizonlab/budget/IdleQueueBenchmark.java")], check=True)
|
|
output = ROOT / "dist/idle-queue-benchmark.txt"
|
|
with output.open("w", encoding="utf-8") as report:
|
|
report.write(f"Java {subprocess.check_output(['java', '--version'], text=True).splitlines()[0]}\n")
|
|
report.write(f"Platform {platform.system()} {platform.release()} {platform.machine()}; logical CPUs {os.cpu_count()}\n")
|
|
report.flush()
|
|
subprocess.run(["java", "-cp", os.pathsep.join([str(tests), classpath]),
|
|
"dev.horizonlab.budget.IdleQueueBenchmark"], stdout=report, check=True)
|
|
print(output.read_text(), end="")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--test-only", action="store_true")
|
|
parser.add_argument("--benchmark-workers", action="store_true", help="Measure tiny-job executor pacing; not Minecraft FPS")
|
|
parser.add_argument("--benchmark-headers", action="store_true", help="Measure object allocation with compact headers off/on; not game RAM")
|
|
args = parser.parse_args()
|
|
if args.test_only and (args.benchmark_workers or args.benchmark_headers):
|
|
parser.error("Benchmarks require the compiled mod; omit --test-only")
|
|
test_metrics()
|
|
if not args.test_only:
|
|
build()
|
|
if args.benchmark_workers:
|
|
benchmark_workers()
|
|
if args.benchmark_headers:
|
|
benchmark_headers()
|