mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-08 08:07:53 -10:00
Compare commits
24
Commits
No files matched your search
@@ -150,7 +150,7 @@ jobs:
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libasound2-dev libudev-dev
|
||||
- name: Build the client, horizon-ctl, horizon-prepare and horizon-server
|
||||
run: cargo build --release --locked ${{ matrix.target && format('--target {0}', matrix.target) || '' }} -p horizon-client --bin horizon-client --bin horizon-ctl -p horizon-prepare --bin horizon-prepare -p horizon-server --bin horizon-server
|
||||
run: cargo build --release --locked ${{ matrix.target && format('--target {0}', matrix.target) || '' }} -p horizon-client -p horizon-client-lib --bin horizon-client --bin horizon-ctl -p horizon-prepare --bin horizon-prepare -p horizon-server --bin horizon-server
|
||||
- name: Every program the release folder takes was built
|
||||
run: |
|
||||
for binary in horizon-client horizon-ctl horizon-prepare horizon-server; do
|
||||
|
||||
@@ -39,7 +39,8 @@ Game workspace (root `Cargo.toml`); `launcher/` and `crates/horizon-telemetry-se
|
||||
|
||||
| Crate | Owns |
|
||||
|---|---|
|
||||
| `horizon-client` | The game: a lib (`src/lib.rs`, ~100 flat modules: gameplay, networking, title screens) and the bin (`src/main.rs` → `game.rs` + `game/`), plus `horizon-ctl` (`src/bin/horizon-ctl/`). Also the dedicated server (`--dedicated`) |
|
||||
| `horizon-client` | Gameplay library (`src/lib.rs`: `game.rs` + `game/`, `entry.rs`) and thin executable (`src/main.rs`). Also the dedicated server (`--dedicated`). Re-exports `horizon-client-lib` for existing mod paths. |
|
||||
| `horizon-client-lib` | Reusable client systems, `horizon-ctl`, probes and their tests. Its manifest points to the existing sources under `crates/horizon-client/`; the library root is `src/client_lib.rs`. It never depends on gameplay. |
|
||||
| `horizon-items` | Blocks and items below gameplay: `blocks.rs` (block facts), `items.rs`, `item_data.rs` (components), `enchant.rs`, `cooldowns.rs`, `bundle.rs`, `item_nbt.rs`. Re-exported as `horizon_client::items`... |
|
||||
| `horizon-command` | Command parsing (Brigadier): the vanilla tree, argument types, selectors, suggestions, the tree's network form. Re-exported as `horizon_client::command` |
|
||||
| `horizon-client-core` | The client lib's bottom layer: `text.rs`, `lang.rs`, `snbt.rs`, `nbt_path.rs`, `component.rs`, `scoreboard.rs`, `bossbar.rs`, `gamerules.rs`, `block_entity_nbt.rs`, registry/particle/sound tables. Re-exported under the old paths (`horizon_client::text`...) |
|
||||
@@ -84,8 +85,8 @@ reads `README.md`, `launcher/links.json`, `docs/api/`, `roadmap/roadmap.json` an
|
||||
```sh
|
||||
python3 tools/pr_ready.py # the self-check: fmt, clippy -D warnings, touched crates' tests, harnesses; paste its block in the PR
|
||||
python3 tools/pr_ready.py --quick # same without harnesses
|
||||
cargo test -p horizon-client --lib --bins --tests # skips the 104 examples (pr_ready does this); `--doc` separately
|
||||
cargo test -p horizon-client --lib mod_content::tests:: -- --ignored # the 5 slow content-root tests, #[ignore]d; pr_ready runs them when mod content, assets, blocks or items change
|
||||
cargo test -p horizon-client -p horizon-client-lib --lib --bins --tests # skips the 104 examples (pr_ready does this); `--doc` separately
|
||||
cargo test -p horizon-client-lib --lib mod_content::tests:: -- --ignored # the 5 slow content-root tests, #[ignore]d; pr_ready runs them when mod content, assets, blocks or items change
|
||||
cargo build --profile fast -p horizon-client # optimized, quick to link: use it to play or run lan_e2e
|
||||
python3 tools/parity.py --changed origin/main # the harnesses your branch can affect (CI's parity job)
|
||||
python3 tools/parity.py --changed-files paths.txt --list # which harnesses cover these files, without running them
|
||||
@@ -102,8 +103,11 @@ python3 tools/agent_control_e2e.py # horizon-ctl / MCP end to end
|
||||
or the game binary with a fixture env var (`game/dispense_fixture.rs`, `game/sculk_fixture.rs`,
|
||||
`game/command_harness.rs`, `game/net_harness.rs`). Copy a nearby harness for a new one.
|
||||
- Probes build into `.local/parity-target/`; harnesses find binaries via `lab.cargo_target()`, never `target/`.
|
||||
- What a change reruns (of 329 harnesses, measured 2026-10-23): a file of the game binary (`game.rs`, `game/`)
|
||||
~25, a file of the client lib ~103, of `horizon-world` ~103–109, of `horizon-core` ~119. Plan for it.
|
||||
- Harness selection uses Cargo dep-info and reachable-code narrowing (`tools/parity_reach.py`).
|
||||
`horizon-client-lib` builds the control tool and client probes without gameplay; their dep-info
|
||||
excludes `game.rs` and `game/`, so gameplay edits do not rebuild or select those probes.
|
||||
Source paths remain in `crates/horizon-client/`. Measure actual harness selection on refreshed
|
||||
records with `tools/parity.py --changed-files paths.txt --list`; historical counts are not a guarantee.
|
||||
- Rust integration tests: `crates/horizon-client/tests/` (`lan_bot.rs`, `generation_saves.rs`, `streaming_meshes.rs`).
|
||||
- Linux needs `libasound2-dev libudev-dev`; `--no-default-features` drops audio and controllers (the server build).
|
||||
- Run and drive the game (starts the `horizon-client` beside `horizon-ctl`; other arguments go to the game):
|
||||
@@ -128,8 +132,11 @@ python3 tools/agent_control_e2e.py # horizon-ctl / MCP end to end
|
||||
## horizon-client: where things live
|
||||
|
||||
`L:` = lib (`crates/horizon-client/src/`, or `crates/horizon-client-render/src/` for the modules that crate holds, under
|
||||
the same `horizon_client::<module>` paths), `G:` = bin (`crates/horizon-client/src/game/`). The lib can't see
|
||||
`Game`; bin code reaches the lib as `horizon_client::<module>`.
|
||||
the same `horizon_client::<module>` paths), `G:` = gameplay (`crates/horizon-client/src/game/`), also in the lib in normal builds.
|
||||
Gameplay keeps its `horizon_client::<module>` imports through a self-crate alias. The executable
|
||||
contains only the allocator, mod API marker and a call to `entry::main`. For unit tests, the lib
|
||||
excludes `entry` and `game`, and the executable test target owns `game` so its process-wide test
|
||||
state stays separate from the library tests. This compiles gameplay twice for executable tests.
|
||||
|
||||
| Subsystem | Files |
|
||||
|---|---|
|
||||
@@ -160,7 +167,7 @@ the same `horizon_client::<module>` paths), `G:` = bin (`crates/horizon-client/s
|
||||
|
||||
- `struct Game` (game.rs, ~290 fields) holds everything. Its methods are spread over ~180 files: game.rs keeps
|
||||
the core (tick, render, input, inventory, world open/close) and each `game/<feature>.rs` adds an
|
||||
`impl Game<'_>` block for one feature, with its own state types. Fields are private to the bin.
|
||||
`impl Game<'_>` block for one feature, with its own state types. Fields stay private to the gameplay module and its descendants. Gameplay tests run in the gameplay library, separate from reusable-library tests.
|
||||
- Find a feature's code: `grep -rn "fn tick_<thing>\|fn <verb>_<thing>" crates/horizon-client/src/game*`;
|
||||
the file is usually `game/<thing>s.rs`. Who calls it: grep the name in game.rs's `fn tick` / `fn tick_world`.
|
||||
- Fields that say which mode runs: `remote` (this window is a client of a server: multiplayer, LAN guest, or
|
||||
|
||||
Generated
+57
-1
@@ -1314,6 +1314,7 @@ dependencies = [
|
||||
"horizon-chat-filter",
|
||||
"horizon-client-core",
|
||||
"horizon-client-input",
|
||||
"horizon-client-lib",
|
||||
"horizon-client-render",
|
||||
"horizon-command",
|
||||
"horizon-core",
|
||||
@@ -1323,7 +1324,6 @@ dependencies = [
|
||||
"horizon-items",
|
||||
"horizon-mods",
|
||||
"horizon-narrator",
|
||||
"horizon-optifine",
|
||||
"horizon-protocol",
|
||||
"horizon-server-files",
|
||||
"horizon-shader-api",
|
||||
@@ -1374,6 +1374,62 @@ dependencies = [
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "horizon-client-lib"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"bytemuck",
|
||||
"chrono",
|
||||
"flate2",
|
||||
"fs4",
|
||||
"gilrs",
|
||||
"glam",
|
||||
"horizon-anticheat",
|
||||
"horizon-assets",
|
||||
"horizon-bench",
|
||||
"horizon-chat-filter",
|
||||
"horizon-client-core",
|
||||
"horizon-client-input",
|
||||
"horizon-client-render",
|
||||
"horizon-command",
|
||||
"horizon-core",
|
||||
"horizon-dualsense",
|
||||
"horizon-friends",
|
||||
"horizon-hdr",
|
||||
"horizon-items",
|
||||
"horizon-mods",
|
||||
"horizon-narrator",
|
||||
"horizon-optifine",
|
||||
"horizon-protocol",
|
||||
"horizon-server-files",
|
||||
"horizon-shader-api",
|
||||
"horizon-shaders",
|
||||
"horizon-telemetry",
|
||||
"horizon-theme",
|
||||
"horizon-threads",
|
||||
"horizon-world",
|
||||
"log",
|
||||
"miniz_oxide 0.8.9",
|
||||
"naga",
|
||||
"objc2 0.6.5",
|
||||
"objc2-app-kit 0.3.2",
|
||||
"objc2-foundation 0.3.2",
|
||||
"open",
|
||||
"png",
|
||||
"pollster",
|
||||
"rustix 1.1.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"unicode-bidi",
|
||||
"ureq",
|
||||
"wgpu",
|
||||
"winit",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "horizon-client-render"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -8,6 +8,7 @@ members = [
|
||||
"crates/horizon-hdr",
|
||||
"crates/horizon-world",
|
||||
"crates/horizon-client",
|
||||
"crates/horizon-client-lib",
|
||||
"crates/horizon-client-core",
|
||||
"crates/horizon-client-input",
|
||||
"crates/horizon-client-render",
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
[package]
|
||||
name = "horizon-client-lib"
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version = "1.90"
|
||||
description = "Reusable client systems, control tool and parity probes"
|
||||
|
||||
[lib]
|
||||
path = "../horizon-client/src/client_lib.rs"
|
||||
|
||||
[dependencies]
|
||||
horizon-assets = { path = "../horizon-assets" }
|
||||
horizon-client-core = { path = "../horizon-client-core" }
|
||||
horizon-client-input = { path = "../horizon-client-input" }
|
||||
horizon-client-render = { path = "../horizon-client-render" }
|
||||
horizon-command = { path = "../horizon-command" }
|
||||
horizon-items = { path = "../horizon-items" }
|
||||
horizon-core = { path = "../horizon-core" }
|
||||
# The server thread ahead of the workers (MinecraftServer.spin's Thread.setPriority).
|
||||
horizon-optifine = { path = "../horizon-optifine" }
|
||||
horizon-threads = { path = "../horizon-threads" }
|
||||
horizon-hdr = { path = "../horizon-hdr" }
|
||||
horizon-world = { path = "../horizon-world" }
|
||||
horizon-protocol = { path = "../horizon-protocol" }
|
||||
# server.properties, eula.txt and the user lists, shared with horizon-server.
|
||||
horizon-server-files = { path = "../horizon-server-files" }
|
||||
# The dedicated server's anticheat, off unless server.toml's [anticheat] turns it on (docs/anticheat.md).
|
||||
horizon-anticheat = { path = "../horizon-anticheat" }
|
||||
# The dedicated server's chat filter: [chat_filter], the slur list, mutes (docs/chat-filter.md).
|
||||
horizon-chat-filter = { path = "../horizon-chat-filter" }
|
||||
# Shader packs: reading them and turning their GLSL into naga modules.
|
||||
horizon-shaders = { path = "../horizon-shaders" }
|
||||
# Horizon's own shader packs: the format, the stages and the check (docs/shaders.md).
|
||||
horizon-shader-api = { path = "../horizon-shader-api" }
|
||||
horizon-friends = { path = "../horizon-friends" }
|
||||
# The narrator (GameNarrator): text-to-speech of chat, system messages and screens.
|
||||
horizon-narrator = { path = "../horizon-narrator" }
|
||||
# Horizon mods: sandboxed WebAssembly components, off unless installed and turned on (docs/mods.md).
|
||||
# Their runtime is the `mods` feature.
|
||||
horizon-mods = { path = "../horizon-mods", default-features = false, features = ["index"] }
|
||||
wgpu = { version = "30.0.1", default-features = false, features = ["std", "parking_lot", "vulkan", "metal", "wgsl", "naga-ir"] }
|
||||
winit = "0.30.13"
|
||||
pollster = "0.4"
|
||||
bytemuck = { version = "1.24", features = ["derive"] }
|
||||
glam = "0.30"
|
||||
png = "0.18"
|
||||
sha2 = "0.10.9"
|
||||
# Server resource packs are checked and cached by their SHA-1, as vanilla does.
|
||||
sha1 = "0.10"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { version = "1.0.151", features = ["raw_value"] }
|
||||
# hlsl-out: the gfx.rs tests translate every shader as wgpu's DirectX 12 backend does. A normal feature,
|
||||
# not a dev-dependency's, so builds, tests and examples share one build of naga, wgpu and this crate.
|
||||
naga = { version = "30.0.1", features = ["wgsl-in", "hlsl-out"] }
|
||||
miniz_oxide = "0.8"
|
||||
ab_glyph = "0.2.32"
|
||||
log = "0.4"
|
||||
chrono = { version = "0.4.42", default-features = false, features = ["clock", "std"] }
|
||||
flate2 = { version = "1.1.10", default-features = false, features = ["zlib-rs", "runtime_detection"] }
|
||||
horizon-theme = { path = "../horizon-theme" }
|
||||
# Opt-in data collection: the game keeps, the launcher sends (docs/telemetry.md); `live` is
|
||||
# the anonymous "I'm playing" ping the game sends itself (`heartbeat.rs`).
|
||||
horizon-telemetry = { path = "../horizon-telemetry", default-features = false, features = ["live"] }
|
||||
horizon-bench = { path = "../horizon-bench" }
|
||||
zip = { version = "9.1.3", default-features = false, features = ["deflate"] }
|
||||
# Controllers (evdev + libudev on Linux, IOKit on macOS, Windows.Gaming.Input); the `controllers` feature.
|
||||
gilrs = { version = "0.11", optional = true }
|
||||
# The DualSense's rumble, adaptive triggers and lights over HID (gilrs only reads it).
|
||||
horizon-dualsense = { path = "../horizon-dualsense", optional = true }
|
||||
# HTTPS to the Minecraft services (online-mode session join).
|
||||
ureq = { version = "2", default-features = false, features = ["tls"] }
|
||||
unicode-bidi = "0.3.18"
|
||||
# `LevelStorageAccess.estimateDiskSpace`: usable disk space, without unsafe code on Windows.
|
||||
fs4 = { version = "1.1.0", default-features = false, features = ["sync"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
# The server thread's CPU time per tick (`HORIZON_SPIKES`, spans.rs).
|
||||
rustix = { version = "1", default-features = false, features = ["std", "time", "process", "fs"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
# Use the registered URI handler without routing links through Explorer/cmd.
|
||||
open = { version = "5.4.5", features = ["shellexecute-on-windows"] }
|
||||
# DirectX 12, where Vulkan has no adapter or device, or by choice (`graphicsApi`, graphics.rs).
|
||||
# Shaders compile with FXC (d3dcompiler_47.dll, part of Windows), or DXC when dxcompiler.dll is
|
||||
# on the PATH.
|
||||
wgpu = { version = "30.0.1", default-features = false, features = ["dx12"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
# Splitscreen: a window filling a monitor goes over its menu bar (game/split/windows.rs
|
||||
# `above_menu_bar`); all already in the tree through winit and wgpu.
|
||||
objc2 = "0.6"
|
||||
objc2-app-kit = { version = "0.3", default-features = false, features = ["std", "NSApplication", "NSResponder", "NSWindow", "NSScreen"] }
|
||||
objc2-foundation = { version = "0.3", default-features = false, features = ["std", "NSArray", "NSString"] }
|
||||
|
||||
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
||||
# OpenGL (WGL on Windows, EGL on Linux), the last fallback, for GPUs with no Vulkan or
|
||||
# DirectX 12 driver (`graphicsApi`, graphics.rs).
|
||||
wgpu = { version = "30.0.1", default-features = false, features = ["gles"] }
|
||||
|
||||
[features]
|
||||
default = ["audio", "controllers"]
|
||||
# Sound output. Without it (and `controllers`) the program links neither libasound nor libudev on
|
||||
# Linux: the server build (`--no-default-features`, released as horizon-engine; docs/server.md).
|
||||
audio = ["horizon-client-render/audio"]
|
||||
# Controllers: gilrs, and the DualSense's own output. Without it only `horizon-ctl pad`'s virtual pad.
|
||||
controllers = ["dep:gilrs", "dep:horizon-dualsense", "horizon-client-input/controllers"]
|
||||
# Records the call sites of mob random draws (`mob_ai_probe` traces).
|
||||
rng-trace = ["horizon-core/rng-trace"]
|
||||
# Compatibility with earlier experiment commands: main already uses mimalloc.
|
||||
windows-mimalloc = []
|
||||
# Opt-in Windows heap experiment; see docs/windows-allocators.md.
|
||||
windows-segment-heap = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
horizon-client-render = { path = "../horizon-client-render", features = ["test-support"] }
|
||||
horizon-items = { path = "../horizon-items", features = ["test-support"] }
|
||||
# Every WGSL shader translated as wgpu's DirectX 12 backend does (gfx.rs tests).
|
||||
naga = { version = "30.0.1", features = ["wgsl-in", "hlsl-out", "msl-out", "spv-out"] }
|
||||
|
||||
[[bin]]
|
||||
name = "horizon-ctl"
|
||||
path = "../horizon-client/src/bin/horizon-ctl/main.rs"
|
||||
|
||||
[[example]]
|
||||
name = "chunk_load_bench"
|
||||
path = "../horizon-client/examples/chunk_load_bench.rs"
|
||||
|
||||
[[example]]
|
||||
name = "gameplay_decode_profile"
|
||||
path = "../horizon-client/examples/gameplay_decode_profile.rs"
|
||||
|
||||
[[example]]
|
||||
name = "json_load_bench"
|
||||
path = "../horizon-client/examples/json_load_bench.rs"
|
||||
|
||||
[[example]]
|
||||
name = "mod_port_probe"
|
||||
path = "../horizon-client/examples/mod_port_probe.rs"
|
||||
|
||||
[[example]]
|
||||
name = "model_library_bench"
|
||||
path = "../horizon-client/examples/model_library_bench.rs"
|
||||
|
||||
[[example]]
|
||||
name = "model_states_bench"
|
||||
path = "../horizon-client/examples/model_states_bench.rs"
|
||||
|
||||
[[example]]
|
||||
name = "old_world_fixture"
|
||||
path = "../horizon-client/examples/old_world_fixture.rs"
|
||||
|
||||
[[example]]
|
||||
name = "portal_scan_bench"
|
||||
path = "../horizon-client/examples/portal_scan_bench.rs"
|
||||
|
||||
[[example]]
|
||||
name = "random_tick_eligibility_bench"
|
||||
path = "../horizon-client/examples/random_tick_eligibility_bench.rs"
|
||||
|
||||
[[example]]
|
||||
name = "resource_memory"
|
||||
path = "../horizon-client/examples/resource_memory.rs"
|
||||
|
||||
[[example]]
|
||||
name = "save_batch_bench"
|
||||
path = "../horizon-client/examples/save_batch_bench.rs"
|
||||
|
||||
[[example]]
|
||||
name = "shared_json_startup_profile"
|
||||
path = "../horizon-client/examples/shared_json_startup_profile.rs"
|
||||
|
||||
[[example]]
|
||||
name = "sprite_cache_parity"
|
||||
path = "../horizon-client/examples/sprite_cache_parity.rs"
|
||||
|
||||
[[example]]
|
||||
name = "client_probe"
|
||||
path = "../horizon-client/examples/probe/main.rs"
|
||||
|
||||
[[test]]
|
||||
name = "custom_component_api"
|
||||
path = "../horizon-client/tests/custom_component_api.rs"
|
||||
|
||||
[[test]]
|
||||
name = "custom_component_unregistered"
|
||||
path = "../horizon-client/tests/custom_component_unregistered.rs"
|
||||
|
||||
[[test]]
|
||||
name = "custom_component_wire"
|
||||
path = "../horizon-client/tests/custom_component_wire.rs"
|
||||
|
||||
[[test]]
|
||||
name = "forced_chunks"
|
||||
path = "../horizon-client/tests/forced_chunks.rs"
|
||||
|
||||
[[test]]
|
||||
name = "generation_saves"
|
||||
path = "../horizon-client/tests/generation_saves.rs"
|
||||
|
||||
[[test]]
|
||||
name = "lan_bot"
|
||||
path = "../horizon-client/tests/lan_bot.rs"
|
||||
|
||||
[[test]]
|
||||
name = "streaming_meshes"
|
||||
path = "../horizon-client/tests/streaming_meshes.rs"
|
||||
|
||||
[[test]]
|
||||
name = "tall_column"
|
||||
path = "../horizon-client/tests/tall_column.rs"
|
||||
|
||||
[[test]]
|
||||
name = "wide_states"
|
||||
path = "../horizon-client/tests/wide_states.rs"
|
||||
@@ -6,8 +6,12 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version = "1.90"
|
||||
description = "Horizon native development client"
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
|
||||
[dependencies]
|
||||
horizon-client-lib = { path = "../horizon-client-lib", default-features = false }
|
||||
horizon-assets = { path = "../horizon-assets" }
|
||||
horizon-client-core = { path = "../horizon-client-core" }
|
||||
horizon-client-input = { path = "../horizon-client-input" }
|
||||
@@ -16,7 +20,6 @@ horizon-command = { path = "../horizon-command" }
|
||||
horizon-items = { path = "../horizon-items" }
|
||||
horizon-core = { path = "../horizon-core" }
|
||||
# The server thread ahead of the workers (MinecraftServer.spin's Thread.setPriority).
|
||||
horizon-optifine = { path = "../horizon-optifine" }
|
||||
horizon-threads = { path = "../horizon-threads" }
|
||||
horizon-hdr = { path = "../horizon-hdr" }
|
||||
horizon-world = { path = "../horizon-world" }
|
||||
@@ -104,20 +107,23 @@ winresource = "0.1"
|
||||
default = ["audio", "controllers"]
|
||||
# Sound output. Without it (and `controllers`) the program links neither libasound nor libudev on
|
||||
# Linux: the server build (`--no-default-features`, released as horizon-engine; docs/server.md).
|
||||
audio = ["horizon-client-render/audio"]
|
||||
audio = ["horizon-client-render/audio", "horizon-client-lib/audio"]
|
||||
# Controllers: gilrs, and the DualSense's own output. Without it only `horizon-ctl pad`'s virtual pad.
|
||||
controllers = ["dep:gilrs", "dep:horizon-dualsense", "horizon-client-input/controllers"]
|
||||
controllers = ["horizon-client-lib/controllers", "dep:gilrs", "dep:horizon-dualsense", "horizon-client-input/controllers"]
|
||||
# Records the call sites of mob random draws (`mob_ai_probe` traces).
|
||||
rng-trace = ["horizon-core/rng-trace"]
|
||||
rng-trace = ["horizon-core/rng-trace", "horizon-client-lib/rng-trace"]
|
||||
# Compatibility with earlier experiment commands: main already uses mimalloc.
|
||||
windows-mimalloc = []
|
||||
# Opt-in Windows heap experiment; see docs/windows-allocators.md.
|
||||
windows-segment-heap = []
|
||||
|
||||
# Its own name, not `probe`: Cargo copies every crate's examples into one target/<profile>/examples.
|
||||
[[example]]
|
||||
name = "client_probe"
|
||||
path = "examples/probe/main.rs"
|
||||
[[bin]]
|
||||
name = "horizon-client"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[test]]
|
||||
name = "launch_api"
|
||||
path = "tests/launch_api.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
//! this thread's CPU time in `Mesher::mesh`, the best of `--rounds`, so other
|
||||
//! work on the machine changes it less. The checksum of the meshes shows that an
|
||||
//! A/B pair of builds made the same ones.
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{
|
||||
assets::Assets,
|
||||
biome::Biomes,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Profile the two mandatory passes over gameplay-facts.json independently.
|
||||
//! Build with `cargo build --release -p horizon-client --example gameplay_decode_profile`.
|
||||
//! Each invocation runs one phase in a separate process for CPU/RSS measurements.
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
#[allow(dead_code)]
|
||||
#[path = "../../horizon-items/src/blocks/decode.rs"]
|
||||
mod decode;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Compare the read + Value parse stages in BlockFacts::load and Assets::load.
|
||||
//! Usage: json_load_bench RESOURCE_ROOT [REPETITIONS]; see tools/json_load_fixtures.py.
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//!
|
||||
//! WORK is a `horizon-port-pack` work folder: `oracle/facts.json` and
|
||||
//! `mods/<id>/carry/`. The root is built in `WORK/horizon-root`.
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{
|
||||
Result,
|
||||
assets::Assets,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! for live requested heap bytes; leave it unset for latency measurements.
|
||||
//! cargo run --profile fast -p horizon-client --example model_library_bench -- ROOT [--parity]
|
||||
//! Run fresh processes, warm up twice, then compare multiple alternating baseline/candidate runs.
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{assets::Assets, item_render::ItemRenderer};
|
||||
use std::{
|
||||
alloc::{GlobalAlloc, Layout, System},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Compare the old two-parse loading path with catalog construction from shared facts.
|
||||
//! Run: cargo run -p horizon-client --profile fast --example model_states_bench -- 15
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{Result, models::StateModels, sprites::Sprites};
|
||||
use horizon_core::position::BlockPos;
|
||||
use horizon_world::{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
//! generated around them blend with them (`Blender.of`).
|
||||
//!
|
||||
//! old_world_fixture <source world> <destination world> <x0,z0,x1,z1>
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_core::position::ChunkPos;
|
||||
use horizon_world::{
|
||||
nbt::{Limits, Tag},
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! entity reaches, with the per-tick palette map against the resident cache.
|
||||
//! Run with prepared client resources:
|
||||
//! cargo run --profile fast -p horizon-client --example portal_scan_bench -- RESOURCES
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{
|
||||
blocks::BlockFacts,
|
||||
chunk::{Chunk, MIN_Y, SECTIONS, Section},
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//!
|
||||
//! This file only lists the probes, so that adding one reruns no harness: tools/parity.py leaves it out of
|
||||
//! their dependencies. How a probe runs is in dispatch.rs.
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
#[macro_use]
|
||||
mod dispatch;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Selection cost only (not block simulation). Run with prepared client resources.
|
||||
//! cargo run --profile fast -p horizon-client --example random_tick_eligibility_bench -- RESOURCES
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{
|
||||
blocks::BlockFacts,
|
||||
chunk::{Chunk, MIN_Y, Section},
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
//! and the process RSS (what the allocator holds on to). On macOS it ends
|
||||
//! with `footprint`'s physical footprint, the number the OS charges: RSS also
|
||||
//! counts freed pages the allocator marked reusable.
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{
|
||||
assets::Assets, biome::Biomes, blocks::BlockFacts, collisions, crafting::Recipes, items::Items,
|
||||
light::LightTables, mesher::MeshTables, models, motion::MotionResources,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! full chunk NBT encoder. Run with `cargo run --profile fast -p horizon-client
|
||||
//! --example save_batch_bench`. Add `-- --nbt` to also time serialization.
|
||||
//! Exact NBT equality is always validated. No GPU, compression or disk I/O timed.
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{
|
||||
biome::Biomes,
|
||||
chunk::{Chunk, block_strategy},
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! Run each mode in a separate process to capture CPU usage and peak RSS.
|
||||
//! The deferred mode evaluates both phases contiguously for fair loader comparison;
|
||||
//! actual client startup performs other initialization between these phases.
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{assets::Assets, blocks::BlockFacts};
|
||||
use horizon_world::level::Behaviours;
|
||||
use std::{path::Path, time::Instant};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! to the ones decoded from the PNGs. Deletes the cache file in ROOT, loads (decodes
|
||||
//! and writes it), loads again (reads it) and compares the sprite arrays.
|
||||
//! cargo run --profile fast -p horizon-client --example sprite_cache_parity -- ROOT
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::assets::{Assets, RenderAssets};
|
||||
use std::{
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! horizon-ctl: play and script a running Horizon client through its control
|
||||
//! socket, from a shell, a script or an AI agent (`horizon-ctl mcp`).
|
||||
#![forbid(unsafe_code)]
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
mod commands;
|
||||
mod launch;
|
||||
mod mcp;
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
//! Horizon native client: resources, world streaming, rendering and the
|
||||
//! bindings between the validated simulation pieces and the game loop.
|
||||
#![forbid(unsafe_code)]
|
||||
extern crate self as horizon_client;
|
||||
pub mod advancements;
|
||||
pub mod ambience;
|
||||
pub mod animate_tick;
|
||||
pub mod arrows;
|
||||
pub mod block_contact;
|
||||
pub use horizon_client_render::{
|
||||
assets, atlas_sources, audio, auth, ban_notice, bidi, biome, block_entities, block_entity_anim,
|
||||
block_entity_render, border_render, breaking, brewing, camera, chat, chunk, chunk_map, clouds,
|
||||
collisions, command_block, containers, crash, datapack, dynamic_light, effects, environment,
|
||||
extras, fast_hash, fog, font, gfx, graphics, graphics_style, graphics_ui, hdr_calibration, hud,
|
||||
hud_layout, hud_overlays, inventory_effects, java_text, jigsaw_block, level_loading, light,
|
||||
lod, logging, menu_blur, mesher, minimap, mipmap, mod_list, model_library, models,
|
||||
multiline_edit, native_emissive, options_file, options_fixes, options_ui, overlay, pack_screen,
|
||||
packs, par, particles, poi_sections, post_chain, potent_sulfur, preedit, providers,
|
||||
screen_draw, screen_effects, sculk, section_mask, settings, shader_options, shader_pack_list,
|
||||
signs, skins, sound_engine, sound_events, sound_preview, spans, split_layout, split_profiles,
|
||||
sprites, streaming, structure_block, tab_list, telemetry, test_block, test_instance_block,
|
||||
testgen, title_edition, trims, ui, vault, weather, widgets, world, worldborder, zip,
|
||||
};
|
||||
pub mod attack_strength;
|
||||
pub mod attribute_updates;
|
||||
pub mod auto_jump;
|
||||
pub mod banner;
|
||||
pub mod beehive;
|
||||
pub mod benchmark;
|
||||
pub mod block_entity_item;
|
||||
pub mod block_pattern;
|
||||
pub mod block_transform;
|
||||
pub mod boss_overlay;
|
||||
pub mod cauldron;
|
||||
pub mod chunk_io;
|
||||
pub mod clock;
|
||||
pub mod command_storage;
|
||||
pub mod confirm_screen;
|
||||
pub mod container_screen;
|
||||
pub mod content_identity;
|
||||
pub mod control;
|
||||
pub mod crafting;
|
||||
pub mod creative;
|
||||
pub mod cushion;
|
||||
pub mod data_cmd;
|
||||
pub mod debug_chart;
|
||||
pub mod debug_entries;
|
||||
pub mod debug_keys;
|
||||
pub mod debug_overlay;
|
||||
pub mod debugworld;
|
||||
pub mod dialog;
|
||||
pub mod dialog_screen;
|
||||
pub mod discord_presence;
|
||||
pub mod end_crystal;
|
||||
pub mod end_gateway;
|
||||
pub mod entities;
|
||||
pub mod entity_culling;
|
||||
pub mod entity_nbt;
|
||||
pub mod extras_hud;
|
||||
pub mod falling_block;
|
||||
pub mod fire_render;
|
||||
pub mod first_person;
|
||||
pub mod flatgen;
|
||||
pub mod friends_screen;
|
||||
pub mod furnace;
|
||||
pub mod game_mode_switcher;
|
||||
pub mod game_rules_screen;
|
||||
pub mod gizmos;
|
||||
pub mod gossip;
|
||||
pub mod health;
|
||||
pub mod heartbeat;
|
||||
pub mod hotbar;
|
||||
pub mod hunger;
|
||||
pub mod inv_sort;
|
||||
pub mod item_activation;
|
||||
pub mod item_render;
|
||||
pub mod item_sync;
|
||||
pub mod item_tooltip;
|
||||
pub mod lan;
|
||||
pub mod landing;
|
||||
pub mod locator_bar;
|
||||
pub mod lod_net;
|
||||
pub mod menu;
|
||||
pub mod merchant;
|
||||
pub mod mobs;
|
||||
pub mod mod_content;
|
||||
pub mod motion;
|
||||
pub mod multiplayer;
|
||||
pub mod music;
|
||||
pub mod native_data;
|
||||
pub mod net;
|
||||
pub mod nether_portal;
|
||||
pub mod no_resources;
|
||||
pub mod offline_name;
|
||||
pub mod old_world;
|
||||
pub mod osk;
|
||||
pub mod patrol;
|
||||
pub mod perf_overlay;
|
||||
pub mod pick_item;
|
||||
pub mod player_sync;
|
||||
pub mod portal_sections;
|
||||
pub mod pose;
|
||||
pub mod pregen;
|
||||
pub mod projectiles;
|
||||
pub mod raid;
|
||||
pub mod random_tick_sections;
|
||||
pub mod recipe_book;
|
||||
pub mod recipe_book_screen;
|
||||
pub mod reconnect;
|
||||
pub mod save;
|
||||
pub mod scoreboard_cmd;
|
||||
pub mod screen_layout;
|
||||
pub mod screen_narrator;
|
||||
pub use horizon_server_files as server;
|
||||
pub mod server_packs;
|
||||
pub mod shadows;
|
||||
pub mod spear;
|
||||
pub mod spectator_menu;
|
||||
pub mod sprint;
|
||||
pub mod stations;
|
||||
pub mod stopwatches;
|
||||
pub mod structure_blocks;
|
||||
pub mod text_display;
|
||||
pub mod tick_chunk_membership;
|
||||
pub mod ticket_levels;
|
||||
pub mod title;
|
||||
pub mod toasts;
|
||||
pub mod ui_theme;
|
||||
pub mod vanillagen;
|
||||
pub mod win_screen;
|
||||
pub mod world_spawn;
|
||||
pub use horizon_client_core::Result;
|
||||
pub use horizon_client_core::block_entity_nbt;
|
||||
pub use horizon_client_core::bossbar;
|
||||
pub use horizon_client_core::component;
|
||||
pub use horizon_client_core::fastutil_order;
|
||||
pub use horizon_client_core::gamerules;
|
||||
pub use horizon_client_core::lang;
|
||||
pub use horizon_client_core::nbt_path;
|
||||
pub use horizon_client_core::scoreboard;
|
||||
pub use horizon_client_core::snbt;
|
||||
pub use horizon_client_core::text;
|
||||
pub use horizon_client_input::clipboard;
|
||||
pub use horizon_client_input::gamepad;
|
||||
pub use horizon_client_input::input;
|
||||
pub use horizon_client_input::interaction_input;
|
||||
pub use horizon_client_input::key_names;
|
||||
pub use horizon_client_input::keybinds;
|
||||
pub use horizon_client_input::keymapping;
|
||||
pub use horizon_client_input::mouse;
|
||||
pub use horizon_client_input::window;
|
||||
pub use horizon_command as command;
|
||||
pub use horizon_items::blocks;
|
||||
pub use horizon_items::bundle;
|
||||
pub use horizon_items::cooldowns;
|
||||
pub use horizon_items::enchant;
|
||||
pub use horizon_items::flowers;
|
||||
pub use horizon_items::item_data;
|
||||
pub use horizon_items::items;
|
||||
pub use horizon_items::random_sequences;
|
||||
@@ -0,0 +1,107 @@
|
||||
//! Startup shared by the gameplay library and its executable.
|
||||
use crate::{crash, game, logging};
|
||||
|
||||
/// Start the game. The executable supplies the allocator and retains the mod API marker.
|
||||
pub fn main() {
|
||||
horizon_threads::configure_allocator();
|
||||
horizon_client::telemetry::mark_start();
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let game_args = args.iter().take_while(|a| *a != "--dedicated");
|
||||
let help = game_args
|
||||
.clone()
|
||||
.any(|a| a == "--help" || a == "-h" || a == "--launch-api");
|
||||
// --home before the log opens in it.
|
||||
if let Some(dir) = game_args.clone().skip_while(|a| *a != "--home").nth(1) {
|
||||
logging::set_game_home(std::path::absolute(dir).unwrap_or_else(|_| dir.into()));
|
||||
}
|
||||
// The dedicated server logs in its own folder (--game-dir's), as vanilla's does; the game in its home.
|
||||
let (home, side, thread) = if args.iter().any(|a| a == "--dedicated") {
|
||||
let folder = game_args
|
||||
.clone()
|
||||
.skip_while(|a| *a != "--game-dir" && *a != "--gameDir")
|
||||
.nth(1)
|
||||
.and_then(|dir| std::path::absolute(dir).ok())
|
||||
.unwrap_or_else(|| ".".into());
|
||||
(folder, "server", "Server thread")
|
||||
} else {
|
||||
// --player-dir too (a game's, not a server's): a player of a shared game folder keeps
|
||||
// its log and crash reports in its own; crash telemetry stays with the home's consent.
|
||||
if let Some(dir) = game_args
|
||||
.clone()
|
||||
.skip_while(|a| *a != "--player-dir")
|
||||
.nth(1)
|
||||
{
|
||||
logging::set_player_dir(std::path::absolute(dir).unwrap_or_else(|_| dir.into()));
|
||||
}
|
||||
(logging::game_home(), "client", "Render thread")
|
||||
};
|
||||
if !help {
|
||||
logging::init(
|
||||
logging::player_dir_override().unwrap_or(home.as_path()),
|
||||
thread,
|
||||
);
|
||||
// The log's first line names the build, so a bug report that carries the log says which one it was.
|
||||
log::info!("Horizon {}", crash::version());
|
||||
crash::install(home.clone(), side);
|
||||
}
|
||||
raise_open_file_limit();
|
||||
if let Err(error) = game::run() {
|
||||
// A bad command line is the caller's mistake, not a crash: no report.
|
||||
let usage = error.is::<game::UsageError>();
|
||||
log::error!("Horizon: {error}");
|
||||
logging::flush();
|
||||
// The report's path is the last line on stderr (crash::SAVED_LINE).
|
||||
if !help && !usage {
|
||||
crash::report_error(&home, side, &*error);
|
||||
}
|
||||
// Started from Explorer, the console window closes with the game:
|
||||
// keep the message on screen until it is read.
|
||||
#[cfg(windows)]
|
||||
if std::io::IsTerminal::is_terminal(&std::io::stdin()) {
|
||||
eprintln!("Press Enter to close.");
|
||||
let _ = std::io::stdin().read_line(&mut String::new());
|
||||
}
|
||||
std::process::exit(if usage {
|
||||
game::exit_code::USAGE
|
||||
} else {
|
||||
game::exit_code::ERROR
|
||||
});
|
||||
}
|
||||
logging::flush();
|
||||
}
|
||||
|
||||
/// The JVM raises its open-file limit to the hard limit as it starts
|
||||
/// (`-XX:+MaxFDLimit`, on by default), so a vanilla server may hold as many
|
||||
/// sockets and region files as the host allows, not the soft default (often
|
||||
/// 1024 in a container). The same here; macOS caps it at `OPEN_MAX`, as the
|
||||
/// JVM does there.
|
||||
fn raise_open_file_limit() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use rustix::process::{Resource, Rlimit, getrlimit, setrlimit};
|
||||
let limit = getrlimit(Resource::Nofile);
|
||||
let hard = if cfg!(target_os = "macos") {
|
||||
Some(limit.maximum.map_or(10_240, |h| h.min(10_240)))
|
||||
} else {
|
||||
limit.maximum
|
||||
};
|
||||
let raised = match (limit.current, hard) {
|
||||
(Some(soft), Some(hard)) => hard > soft,
|
||||
(Some(_), None) => true,
|
||||
(None, _) => false,
|
||||
};
|
||||
if raised {
|
||||
let to = Rlimit {
|
||||
current: hard,
|
||||
maximum: limit.maximum,
|
||||
};
|
||||
match setrlimit(Resource::Nofile, to) {
|
||||
Ok(()) => log::info!(
|
||||
"Open file limit raised from {:?} to {hard:?}",
|
||||
limit.current
|
||||
),
|
||||
Err(e) => log::warn!("Open file limit stays at {:?}: {e}", limit.current),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5001,7 +5001,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn loot_table_load_stays_hookable() {
|
||||
let source = include_str!("../../../horizon-world/src/loot.rs");
|
||||
let source = include_str!("../../../horizon-world/src/loot.rs").replace("\r\n", "\n");
|
||||
// From its doc comment's last line, so an attribute above it counts.
|
||||
let hook = source.find("pub fn native_loot_table_load(").unwrap();
|
||||
let start = source[..hook].rfind("///").unwrap();
|
||||
|
||||
@@ -1,159 +1,6 @@
|
||||
//! Horizon native client: resources, world streaming, rendering and the
|
||||
//! bindings between the validated simulation pieces and the game loop.
|
||||
//! Gameplay and startup, above the reusable client library.
|
||||
#![forbid(unsafe_code)]
|
||||
pub mod advancements;
|
||||
pub mod ambience;
|
||||
pub mod animate_tick;
|
||||
pub mod arrows;
|
||||
pub mod block_contact;
|
||||
pub use horizon_client_render::{
|
||||
assets, atlas_sources, audio, auth, ban_notice, bidi, biome, block_entities, block_entity_anim,
|
||||
block_entity_render, border_render, breaking, brewing, camera, chat, chunk, chunk_map, clouds,
|
||||
collisions, command_block, containers, crash, datapack, dynamic_light, effects, environment,
|
||||
extras, fast_hash, fog, font, gfx, graphics, graphics_style, graphics_ui, hdr_calibration, hud,
|
||||
hud_layout, hud_overlays, inventory_effects, java_text, jigsaw_block, level_loading, light,
|
||||
lod, logging, menu_blur, mesher, minimap, mipmap, mod_list, model_library, models,
|
||||
multiline_edit, native_emissive, options_file, options_fixes, options_ui, overlay, pack_screen,
|
||||
packs, par, particles, poi_sections, post_chain, potent_sulfur, preedit, providers,
|
||||
screen_draw, screen_effects, sculk, section_mask, settings, shader_options, shader_pack_list,
|
||||
signs, skins, sound_engine, sound_events, sound_preview, spans, split_layout, split_profiles,
|
||||
sprites, streaming, structure_block, tab_list, telemetry, test_block, test_instance_block,
|
||||
testgen, title_edition, trims, ui, vault, weather, widgets, world, worldborder, zip,
|
||||
};
|
||||
pub mod attack_strength;
|
||||
pub mod attribute_updates;
|
||||
pub mod auto_jump;
|
||||
pub mod banner;
|
||||
pub mod beehive;
|
||||
pub mod benchmark;
|
||||
pub mod block_entity_item;
|
||||
pub mod block_pattern;
|
||||
pub mod block_transform;
|
||||
pub mod boss_overlay;
|
||||
pub mod cauldron;
|
||||
pub mod chunk_io;
|
||||
pub mod clock;
|
||||
pub mod command_storage;
|
||||
pub mod confirm_screen;
|
||||
pub mod container_screen;
|
||||
pub mod content_identity;
|
||||
pub mod control;
|
||||
pub mod crafting;
|
||||
pub mod creative;
|
||||
pub mod cushion;
|
||||
pub mod data_cmd;
|
||||
pub mod debug_chart;
|
||||
pub mod debug_entries;
|
||||
pub mod debug_keys;
|
||||
pub mod debug_overlay;
|
||||
pub mod debugworld;
|
||||
pub mod dialog;
|
||||
pub mod dialog_screen;
|
||||
pub mod discord_presence;
|
||||
pub mod end_crystal;
|
||||
pub mod end_gateway;
|
||||
pub mod entities;
|
||||
pub mod entity_culling;
|
||||
pub mod entity_nbt;
|
||||
pub mod extras_hud;
|
||||
pub mod falling_block;
|
||||
pub mod fire_render;
|
||||
pub mod first_person;
|
||||
pub mod flatgen;
|
||||
pub mod friends_screen;
|
||||
pub mod furnace;
|
||||
pub mod game_mode_switcher;
|
||||
pub mod game_rules_screen;
|
||||
pub mod gizmos;
|
||||
pub mod gossip;
|
||||
pub mod health;
|
||||
pub mod heartbeat;
|
||||
pub mod hotbar;
|
||||
pub mod hunger;
|
||||
pub mod inv_sort;
|
||||
pub mod item_activation;
|
||||
pub mod item_render;
|
||||
pub mod item_sync;
|
||||
pub mod item_tooltip;
|
||||
pub mod lan;
|
||||
pub mod landing;
|
||||
pub mod locator_bar;
|
||||
pub mod lod_net;
|
||||
pub mod menu;
|
||||
pub mod merchant;
|
||||
pub mod mobs;
|
||||
pub mod mod_content;
|
||||
pub mod motion;
|
||||
pub mod multiplayer;
|
||||
pub mod music;
|
||||
pub mod native_data;
|
||||
pub mod net;
|
||||
pub mod nether_portal;
|
||||
pub mod no_resources;
|
||||
pub mod offline_name;
|
||||
pub mod old_world;
|
||||
pub mod osk;
|
||||
pub mod patrol;
|
||||
pub mod perf_overlay;
|
||||
pub mod pick_item;
|
||||
pub mod player_sync;
|
||||
pub mod portal_sections;
|
||||
pub mod pose;
|
||||
pub mod pregen;
|
||||
pub mod projectiles;
|
||||
pub mod raid;
|
||||
pub mod random_tick_sections;
|
||||
pub mod recipe_book;
|
||||
pub mod recipe_book_screen;
|
||||
pub mod reconnect;
|
||||
pub mod save;
|
||||
pub mod scoreboard_cmd;
|
||||
pub mod screen_layout;
|
||||
pub mod screen_narrator;
|
||||
pub use horizon_server_files as server;
|
||||
pub mod server_packs;
|
||||
pub mod shadows;
|
||||
pub mod spear;
|
||||
pub mod spectator_menu;
|
||||
pub mod sprint;
|
||||
pub mod stations;
|
||||
pub mod stopwatches;
|
||||
pub mod structure_blocks;
|
||||
pub mod text_display;
|
||||
pub mod tick_chunk_membership;
|
||||
pub mod ticket_levels;
|
||||
pub mod title;
|
||||
pub mod toasts;
|
||||
pub mod ui_theme;
|
||||
pub mod vanillagen;
|
||||
pub mod win_screen;
|
||||
pub mod world_spawn;
|
||||
pub use horizon_client_core::Result;
|
||||
pub use horizon_client_core::block_entity_nbt;
|
||||
pub use horizon_client_core::bossbar;
|
||||
pub use horizon_client_core::component;
|
||||
pub use horizon_client_core::fastutil_order;
|
||||
pub use horizon_client_core::gamerules;
|
||||
pub use horizon_client_core::lang;
|
||||
pub use horizon_client_core::nbt_path;
|
||||
pub use horizon_client_core::scoreboard;
|
||||
pub use horizon_client_core::snbt;
|
||||
pub use horizon_client_core::text;
|
||||
pub use horizon_client_input::clipboard;
|
||||
pub use horizon_client_input::gamepad;
|
||||
pub use horizon_client_input::input;
|
||||
pub use horizon_client_input::interaction_input;
|
||||
pub use horizon_client_input::key_names;
|
||||
pub use horizon_client_input::keybinds;
|
||||
pub use horizon_client_input::keymapping;
|
||||
pub use horizon_client_input::mouse;
|
||||
pub use horizon_client_input::window;
|
||||
pub use horizon_command as command;
|
||||
pub use horizon_items::blocks;
|
||||
pub use horizon_items::bundle;
|
||||
pub use horizon_items::cooldowns;
|
||||
pub use horizon_items::enchant;
|
||||
pub use horizon_items::flowers;
|
||||
pub use horizon_items::item_data;
|
||||
pub use horizon_items::items;
|
||||
pub use horizon_items::random_sequences;
|
||||
extern crate self as horizon_client;
|
||||
pub use horizon_client_lib::*;
|
||||
pub mod entry;
|
||||
pub mod game;
|
||||
@@ -1,8 +1,5 @@
|
||||
//! Horizon: native Rust client for Minecraft Java Edition 26.3 gameplay.
|
||||
#![forbid(unsafe_code)]
|
||||
mod game;
|
||||
|
||||
use horizon_client::{crash, logging};
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: horizon_threads::Allocator = horizon_threads::Allocator;
|
||||
@@ -13,106 +10,6 @@ static ALLOCATOR: horizon_threads::Allocator = horizon_threads::Allocator;
|
||||
static MOD_API_LEVEL: [u8; 27] = horizon_mods::api_level::MARK;
|
||||
|
||||
fn main() {
|
||||
horizon_threads::configure_allocator();
|
||||
std::hint::black_box(&MOD_API_LEVEL);
|
||||
horizon_client::telemetry::mark_start();
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let game_args = args.iter().take_while(|a| *a != "--dedicated");
|
||||
let help = game_args
|
||||
.clone()
|
||||
.any(|a| a == "--help" || a == "-h" || a == "--launch-api");
|
||||
// --home before the log opens in it.
|
||||
if let Some(dir) = game_args.clone().skip_while(|a| *a != "--home").nth(1) {
|
||||
logging::set_game_home(std::path::absolute(dir).unwrap_or_else(|_| dir.into()));
|
||||
}
|
||||
// The dedicated server logs in its own folder (--game-dir's), as vanilla's does; the game in its home.
|
||||
let (home, side, thread) = if args.iter().any(|a| a == "--dedicated") {
|
||||
let folder = game_args
|
||||
.clone()
|
||||
.skip_while(|a| *a != "--game-dir" && *a != "--gameDir")
|
||||
.nth(1)
|
||||
.and_then(|dir| std::path::absolute(dir).ok())
|
||||
.unwrap_or_else(|| ".".into());
|
||||
(folder, "server", "Server thread")
|
||||
} else {
|
||||
// --player-dir too (a game's, not a server's): a player of a shared game folder keeps
|
||||
// its log and crash reports in its own; crash telemetry stays with the home's consent.
|
||||
if let Some(dir) = game_args
|
||||
.clone()
|
||||
.skip_while(|a| *a != "--player-dir")
|
||||
.nth(1)
|
||||
{
|
||||
logging::set_player_dir(std::path::absolute(dir).unwrap_or_else(|_| dir.into()));
|
||||
}
|
||||
(logging::game_home(), "client", "Render thread")
|
||||
};
|
||||
if !help {
|
||||
logging::init(
|
||||
logging::player_dir_override().unwrap_or(home.as_path()),
|
||||
thread,
|
||||
);
|
||||
// The log's first line names the build, so a bug report that carries the log says which one it was.
|
||||
log::info!("Horizon {}", crash::version());
|
||||
crash::install(home.clone(), side);
|
||||
}
|
||||
raise_open_file_limit();
|
||||
if let Err(error) = game::run() {
|
||||
// A bad command line is the caller's mistake, not a crash: no report.
|
||||
let usage = error.is::<game::UsageError>();
|
||||
log::error!("Horizon: {error}");
|
||||
logging::flush();
|
||||
// The report's path is the last line on stderr (crash::SAVED_LINE).
|
||||
if !help && !usage {
|
||||
crash::report_error(&home, side, &*error);
|
||||
}
|
||||
// Started from Explorer, the console window closes with the game:
|
||||
// keep the message on screen until it is read.
|
||||
#[cfg(windows)]
|
||||
if std::io::IsTerminal::is_terminal(&std::io::stdin()) {
|
||||
eprintln!("Press Enter to close.");
|
||||
let _ = std::io::stdin().read_line(&mut String::new());
|
||||
}
|
||||
std::process::exit(if usage {
|
||||
game::exit_code::USAGE
|
||||
} else {
|
||||
game::exit_code::ERROR
|
||||
});
|
||||
}
|
||||
logging::flush();
|
||||
}
|
||||
|
||||
/// The JVM raises its open-file limit to the hard limit as it starts
|
||||
/// (`-XX:+MaxFDLimit`, on by default), so a vanilla server may hold as many
|
||||
/// sockets and region files as the host allows, not the soft default (often
|
||||
/// 1024 in a container). The same here; macOS caps it at `OPEN_MAX`, as the
|
||||
/// JVM does there.
|
||||
fn raise_open_file_limit() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use rustix::process::{Resource, Rlimit, getrlimit, setrlimit};
|
||||
let limit = getrlimit(Resource::Nofile);
|
||||
let hard = if cfg!(target_os = "macos") {
|
||||
Some(limit.maximum.map_or(10_240, |h| h.min(10_240)))
|
||||
} else {
|
||||
limit.maximum
|
||||
};
|
||||
let raised = match (limit.current, hard) {
|
||||
(Some(soft), Some(hard)) => hard > soft,
|
||||
(Some(_), None) => true,
|
||||
(None, _) => false,
|
||||
};
|
||||
if raised {
|
||||
let to = Rlimit {
|
||||
current: hard,
|
||||
maximum: limit.maximum,
|
||||
};
|
||||
match setrlimit(Resource::Nofile, to) {
|
||||
Ok(()) => log::info!(
|
||||
"Open file limit raised from {:?} to {hard:?}",
|
||||
limit.current
|
||||
),
|
||||
Err(e) => log::warn!("Open file limit stays at {:?}: {e}", limit.current),
|
||||
}
|
||||
}
|
||||
}
|
||||
horizon_client::entry::main();
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Native component registration, value limits and content identity.
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{
|
||||
content_identity,
|
||||
mod_content::{ContentMod, accept_mod, build, known_components},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_protocol::{
|
||||
buf::{Codec, Reader, Writer},
|
||||
item::Slot,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{item_sync, items::ItemStack, lan::guest::stack_to_network};
|
||||
use horizon_items::item_nbt::{item_from_nbt, item_to_nbt};
|
||||
use horizon_protocol::{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//! column's status (`ChunkLevel.generationStatus`). The chunk itself and the
|
||||
//! two rings around it are level chunks with their light, the third ring has
|
||||
//! its features and the fourth only its terrain.
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{
|
||||
assets::Assets,
|
||||
biome::Biomes,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! after its own decoration and its eight neighbours', and chunks that
|
||||
//! decoration wrote to are saved rather than generated again without those
|
||||
//! writes. Bastions lost whole chunks of pieces this way.
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{
|
||||
assets::Assets,
|
||||
biome::Biomes,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! handshake, status, offline login, configuration (registries and tags),
|
||||
//! then the play state (login, chunks with light, player list, entities,
|
||||
//! block changes). Needs the prepared resources; skipped without them.
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{
|
||||
chunk::{Chunk, MIN_Y},
|
||||
lan::{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//! came closer, so they stayed invisible until a block in them changed.
|
||||
//! Relit columns remesh only where their light changed (#270), and every
|
||||
//! mesh still matches the world.
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{
|
||||
assets::Assets,
|
||||
biome::Biomes,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! A level taller than vanilla's (To The Max's Overworld: -64..2032, 131
|
||||
//! sections) streams, lights and meshes to its top, and edits near the top
|
||||
//! relight and remesh it.
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::{
|
||||
assets::Assets,
|
||||
biome::Biomes,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! content mods can take the registry past 65,536 states. Its own test binary,
|
||||
//! since the palette width is set once for the whole process.
|
||||
|
||||
extern crate horizon_client_lib as horizon_client;
|
||||
use horizon_client::chunk::{Chunk, Section, block_strategy, set_block_states};
|
||||
|
||||
#[test]
|
||||
|
||||
Generated
+34
-1
@@ -306,6 +306,15 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.15"
|
||||
@@ -319,7 +328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.9.1",
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
@@ -506,6 +515,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"naga",
|
||||
"png",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
@@ -783,6 +793,16 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "871285dc19d8d0ebe0eef3d0e99a205f2a71363b122632cbbfa4a6c370a960ce"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.9.1"
|
||||
@@ -911,6 +931,19 @@ version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.6"
|
||||
|
||||
@@ -41,9 +41,10 @@ pub struct Index {
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(from = "String", into = "String")]
|
||||
pub enum Kind {
|
||||
/// A `.hmod`.
|
||||
/// A `.hmod` of content only.
|
||||
Mod,
|
||||
/// A source-only native mixin bundle, compiled into an isolated game build.
|
||||
/// A source-only native mixin bundle, compiled into an isolated game build. Published as an
|
||||
/// `.hnmod` until launchers and the game read native `.hmod`s (#5865, docs/api/mod-files.md).
|
||||
NativeMod,
|
||||
/// A `.hpack`: registry mods played together as a profile.
|
||||
Modpack,
|
||||
@@ -557,8 +558,22 @@ impl Facts {
|
||||
/// this build doesn't know are refused: their checks come with their formats.
|
||||
pub fn inspect(kind: &Kind, path: &Path) -> Result<Facts, String> {
|
||||
match kind {
|
||||
Kind::NativeMod => Ok(native::Bundle::read(path)?.manifest.facts()),
|
||||
Kind::NativeMod => {
|
||||
let facts = native::Bundle::read(path)?.manifest.facts();
|
||||
// A `.hmod` with content too: checked as the game reads it.
|
||||
if Package::has_manifest(path) {
|
||||
let m = Package::read(path)?.manifest;
|
||||
crate::Content::read(path, &m)?;
|
||||
}
|
||||
Ok(facts)
|
||||
}
|
||||
Kind::Mod => {
|
||||
if native::holds(path) {
|
||||
return Err(format!(
|
||||
"it has native code ({}): it is a native-mod",
|
||||
native::MANIFEST
|
||||
));
|
||||
}
|
||||
let Package { manifest: m, wasm } = Package::read(path)?;
|
||||
crate::Content::read(path, &m)?;
|
||||
let api_level = match wasm {
|
||||
|
||||
@@ -5,6 +5,31 @@ use std::{collections::BTreeMap, io::Read, path::Path};
|
||||
|
||||
pub const MANIFEST: &str = "horizon-native.json";
|
||||
|
||||
/// Why a native mod may not carry `mod.wasm`: WebAssembly mods don't run any more.
|
||||
pub const WASM_GONE: &str = "mod.wasm: WebAssembly mods don't run any more, port the code to a native mod (docs/native-mods.md)";
|
||||
|
||||
/// Whether `path` names a native mod's file: an `.hnmod`, or a `.hmod` holding native code
|
||||
/// (one file for content and code, docs/native-mods.md).
|
||||
pub fn is_file(path: &Path) -> bool {
|
||||
path.extension()
|
||||
.is_some_and(|e| e == "hnmod" || e == "hmod")
|
||||
}
|
||||
|
||||
/// Whether the mod file at `path` carries native code ([`MANIFEST`]): every `.hnmod`, and a
|
||||
/// `.hmod` with code beside or instead of its content.
|
||||
pub fn holds(path: &Path) -> bool {
|
||||
std::fs::File::open(path)
|
||||
.ok()
|
||||
.and_then(|f| zip::ZipArchive::new(f).ok())
|
||||
.is_some_and(|mut zip| zip.by_name(MANIFEST).is_ok())
|
||||
}
|
||||
|
||||
/// Whether a `.hmod`'s entry `name` is its content's (`horizon-mod.toml`, `assets/`, `data/`),
|
||||
/// which [`crate::Content`] reads and checks, not the native build.
|
||||
fn content(name: &str) -> bool {
|
||||
name == crate::manifest::MANIFEST || name.starts_with("assets/") || name.starts_with("data/")
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Manifest {
|
||||
@@ -67,7 +92,7 @@ impl Manifest {
|
||||
pub struct Bundle {
|
||||
pub manifest: Manifest,
|
||||
/// Validated relative paths: Rust and TOML, plus files under `mixins/<id>/data/` or a
|
||||
/// `data/` folder deeper in (a crate's).
|
||||
/// `data/` folder deeper in (a crate's). A `.hmod`'s content files are not among them.
|
||||
pub files: BTreeMap<String, Vec<u8>>,
|
||||
}
|
||||
|
||||
@@ -102,10 +127,8 @@ impl Bundle {
|
||||
pub fn read(path: &Path) -> Result<Self, String> {
|
||||
let file = std::fs::File::open(path).map_err(|e| e.to_string())?;
|
||||
let mut zip = zip::ZipArchive::new(file).map_err(|e| e.to_string())?;
|
||||
if zip.len() > 512 {
|
||||
return Err("native bundle has too many entries".into());
|
||||
}
|
||||
let mut files = BTreeMap::new();
|
||||
let mut content_id = None;
|
||||
let mut total = 0u64;
|
||||
for i in 0..zip.len() {
|
||||
let mut entry = zip.by_index(i).map_err(|e| e.to_string())?;
|
||||
@@ -125,6 +148,23 @@ impl Bundle {
|
||||
{
|
||||
return Err(format!("invalid native bundle path: {name}"));
|
||||
}
|
||||
if name == crate::manifest::WASM {
|
||||
return Err(WASM_GONE.into());
|
||||
}
|
||||
if content(&name) {
|
||||
if name == crate::manifest::MANIFEST {
|
||||
let mut text = String::new();
|
||||
(&mut entry)
|
||||
.take(1 << 20)
|
||||
.read_to_string(&mut text)
|
||||
.map_err(|e| format!("{name}: {e}"))?;
|
||||
content_id = Some(crate::Manifest::parse(&text)?.id);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if files.len() >= 512 {
|
||||
return Err("native bundle has too many entries".into());
|
||||
}
|
||||
let mut bytes = Vec::new();
|
||||
(&mut entry)
|
||||
.take(super::MAX_FILE + 1)
|
||||
@@ -142,6 +182,13 @@ impl Bundle {
|
||||
serde_json::from_slice(files.get(MANIFEST).ok_or("missing horizon-native.json")?)
|
||||
.map_err(|e| e.to_string())?;
|
||||
manifest.check()?;
|
||||
if let Some(id) = content_id.filter(|id| *id != manifest.id) {
|
||||
return Err(format!(
|
||||
"its {} is mod {id}, its {MANIFEST} {}: one file is one mod",
|
||||
crate::manifest::MANIFEST,
|
||||
manifest.id
|
||||
));
|
||||
}
|
||||
let prefix = format!("mixins/{}/", manifest.id);
|
||||
for name in files.keys().filter(|n| n.as_str() != MANIFEST) {
|
||||
let Some(rest) = name.strip_prefix(&prefix) else {
|
||||
@@ -282,6 +329,48 @@ mod tests {
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn one_hmod_carries_content_beside_its_native_code() {
|
||||
let toml = r#"id = "veinminer"
|
||||
name = "Veinminer"
|
||||
version = "0.3.0"
|
||||
api = "0.2"
|
||||
capabilities = ["registries"]
|
||||
"#;
|
||||
let bundle = read(
|
||||
"unified",
|
||||
&[
|
||||
("horizon-mod.toml", toml.as_bytes()),
|
||||
("assets/veinminer/lang/en_us.json", b"{}"),
|
||||
("data/veinminer/tags/item/ores.json", b"{}"),
|
||||
("mixins/veinminer/hook.rs", b"fn hook() {}"),
|
||||
],
|
||||
&mixin(),
|
||||
)
|
||||
.unwrap();
|
||||
let names: Vec<&str> = bundle.files.keys().map(String::as_str).collect();
|
||||
assert_eq!(
|
||||
names,
|
||||
[
|
||||
MANIFEST,
|
||||
"mixins/veinminer/hook.rs",
|
||||
"mixins/veinminer/horizon-mixin.toml"
|
||||
]
|
||||
);
|
||||
let other = toml.replace(r#""veinminer""#, r#""other""#);
|
||||
let e = read(
|
||||
"two-ids",
|
||||
&[("horizon-mod.toml", other.as_bytes())],
|
||||
&mixin(),
|
||||
)
|
||||
.err()
|
||||
.unwrap();
|
||||
assert!(e.contains("one file is one mod"), "{e}");
|
||||
let e = read("wasm", &[("mod.wasm", b"\0asm")], &mixin())
|
||||
.err()
|
||||
.unwrap();
|
||||
assert_eq!(e, WASM_GONE);
|
||||
}
|
||||
#[test]
|
||||
fn rejects_missing_or_mismatched_mixin_identity_without_panicking() {
|
||||
assert!(read("missing-mod", &[], "[unrelated]\nvalue = 1").is_err());
|
||||
assert!(
|
||||
|
||||
@@ -354,8 +354,8 @@ pub fn project(entry: &Entry, src: &Sources) -> Result<Project, Vec<String>> {
|
||||
for v in &entry.versions {
|
||||
match version(entry, v, src) {
|
||||
// A mod that turned native keeps its `.hmod` versions published, but the index offers
|
||||
// only its native ones: a launcher never installs a `.hmod` as a native mod.
|
||||
Ok(_) if file_kind(entry, v) != entry.kind => {}
|
||||
// only its native ones: a launcher never installs a content-only `.hmod` as a native mod.
|
||||
Ok(_) if file_kind(entry, v, &src.files.join(&v.sha256)) != entry.kind => {}
|
||||
Ok(v) => versions.push(v),
|
||||
Err(e) => errors.push(format!("{id} {}: {e}", v.version)),
|
||||
}
|
||||
@@ -384,11 +384,11 @@ pub fn project(entry: &Entry, src: &Sources) -> Result<Project, Vec<String>> {
|
||||
})
|
||||
}
|
||||
|
||||
/// The kind of a version's file: the project's, but a `.hmod` of a mod that turned native
|
||||
/// (`review`) is a WebAssembly mod's.
|
||||
fn file_kind(entry: &Entry, v: &EntryVersion) -> Kind {
|
||||
/// The kind of a version's file at `path`: the project's, but a `.hmod` without native code of a
|
||||
/// mod that turned native (`review`) is from before it did.
|
||||
fn file_kind(entry: &Entry, v: &EntryVersion, path: &Path) -> Kind {
|
||||
match entry.kind {
|
||||
Kind::NativeMod if v.file.ends_with(".hmod") => Kind::Mod,
|
||||
Kind::NativeMod if v.file.ends_with(".hmod") && !super::native::holds(path) => Kind::Mod,
|
||||
_ => entry.kind.clone(),
|
||||
}
|
||||
}
|
||||
@@ -425,7 +425,7 @@ fn version_at(
|
||||
(None, Some(_)) => return Err("signed, but the project has no key".into()),
|
||||
(None, None) => {}
|
||||
}
|
||||
let facts = inspect(&file_kind(entry, v), path)?;
|
||||
let facts = inspect(&file_kind(entry, v, path), path)?;
|
||||
if facts.id.as_ref().is_some_and(|id| *id != entry.id) {
|
||||
return Err(format!(
|
||||
"the file's id is {:?}, not {:?}",
|
||||
@@ -598,7 +598,7 @@ pub struct Review {
|
||||
/// Compares the entries of a pull request (`head`) with `main`'s (`base`):
|
||||
/// a published file never changes and is never removed (take it down in
|
||||
/// `revoked.toml`), a project's kind never changes but for a mod turning
|
||||
/// native (its next versions are `.hnmod`s), and changes by someone who
|
||||
/// native (its next versions carry native code), and changes by someone who
|
||||
/// isn't an owner, or to owners and keys, are pointed out.
|
||||
pub fn review(base: &[Entry], head: &[Entry], author: &str) -> Review {
|
||||
let mut r = Review::default();
|
||||
@@ -622,7 +622,7 @@ pub fn review(base: &[Entry], head: &[Entry], author: &str) -> Review {
|
||||
}
|
||||
if (&old.kind, &new.kind) == (&Kind::Mod, &Kind::NativeMod) {
|
||||
r.notes.push(format!(
|
||||
"{}: turns native; its .hmod versions stay published, and launchers keep the installed copies",
|
||||
"{}: turns native; its content-only .hmod versions stay published, and launchers keep the installed copies",
|
||||
old.id
|
||||
));
|
||||
} else if new.kind != old.kind {
|
||||
|
||||
@@ -259,9 +259,27 @@ fn a_mod_may_turn_native_once() {
|
||||
);
|
||||
assert!(r.errors.is_empty(), "{r:?}");
|
||||
assert!(r.notes.iter().any(|n| n.contains("turns native")), "{r:?}");
|
||||
// Its .hmod versions are a WebAssembly mod's files; the new ones native.
|
||||
assert_eq!(file_kind(&native, &native.versions[1]), Kind::Mod);
|
||||
assert_eq!(file_kind(&native, &native.versions[0]), Kind::NativeMod);
|
||||
// Its content-only .hmod versions are from before; the new ones native, and a .hmod
|
||||
// holding native code is native too.
|
||||
let none = Path::new("no-such-file");
|
||||
assert_eq!(file_kind(&native, &native.versions[1], none), Kind::Mod);
|
||||
assert_eq!(
|
||||
file_kind(&native, &native.versions[0], none),
|
||||
Kind::NativeMod
|
||||
);
|
||||
let unified = std::env::temp_dir().join(format!("unified-{}.hmod", std::process::id()));
|
||||
let mut zip = zip::ZipWriter::new(std::fs::File::create(&unified).unwrap());
|
||||
zip.start_file(
|
||||
super::super::native::MANIFEST,
|
||||
zip::write::SimpleFileOptions::default(),
|
||||
)
|
||||
.unwrap();
|
||||
zip.finish().unwrap();
|
||||
assert_eq!(
|
||||
file_kind(&native, &native.versions[1], &unified),
|
||||
Kind::NativeMod
|
||||
);
|
||||
std::fs::remove_file(unified).unwrap();
|
||||
// And never back.
|
||||
let r = review(&[native], &[base], "ludovic111");
|
||||
assert!(
|
||||
|
||||
@@ -362,6 +362,17 @@ impl Package {
|
||||
}
|
||||
Ok(Package { manifest, wasm })
|
||||
}
|
||||
/// Whether the mod at `path` (a `.hmod` or a folder) has a [`MANIFEST`]: content, or a legacy
|
||||
/// WebAssembly mod. A `.hmod` holding only native code has none.
|
||||
pub fn has_manifest(path: &Path) -> bool {
|
||||
if path.is_dir() {
|
||||
return path.join(MANIFEST).is_file();
|
||||
}
|
||||
std::fs::File::open(path)
|
||||
.ok()
|
||||
.and_then(|f| zip::ZipArchive::new(f).ok())
|
||||
.is_some_and(|mut zip| zip.by_name(MANIFEST).is_ok())
|
||||
}
|
||||
/// The `.hmod` bytes for `manifest_text` and a component.
|
||||
pub fn write(manifest_text: &str, wasm: &[u8]) -> Result<Vec<u8>, String> {
|
||||
Self::write_files(manifest_text, Some(wasm), &[])
|
||||
|
||||
@@ -10,8 +10,8 @@ and worlds. Each entry names its version field, its documentation and its level
|
||||
| Benchmark report | the benchmark's output, the local log, uploads | `"format": "horizon-benchmark-report"`, `"version": 1` | stable | [benchmark-report.md](../benchmark-report.md) |
|
||||
| Telemetry batch | `POST /v1/batch` from the launcher | `"schema": 1` | stable | [telemetry.md](../telemetry.md) |
|
||||
| Telemetry consent | `telemetry/consent.json` in the game folder | `"version": 2` | stable | [telemetry.md](../telemetry.md) |
|
||||
| Content mod package | `.hmod`: a zip with `horizon-mod.toml`, `assets/`, `data/` | the manifest's `api` | preview | [mods.md](../mods.md#the-manifest), [mod-index.md](mod-index.md) |
|
||||
| Native mod package | `.hnmod`: a zip with `horizon-native.json` and the bundle's `.rs` and `.toml` files | `schema` | experimental | [native-mods.md](../native-mods.md) |
|
||||
| Content mod package | `.hmod`: a zip with `horizon-mod.toml`, `assets/`, `data/` | the manifest's `api` | preview | [mod-files.md](mod-files.md), [mods.md](../mods.md#the-manifest), [mod-index.md](mod-index.md) |
|
||||
| Native mod package | `.hnmod`, or a `.hmod` with native code: a zip with `horizon-native.json` and the bundle's `.rs` and `.toml` files | `schema` | experimental | [mod-files.md](mod-files.md), [native-mods.md](../native-mods.md) |
|
||||
| Mods turned on | `horizon-mods.txt` | none | preview | [mods.md](../mods.md#playing-with-mods) |
|
||||
| Horizon settings | `horizon-graphics.txt`, `horizon-extras.txt` (`key:value`, as `options.txt`) | none: unknown keys fall back to defaults | preview | [graphics.md](../graphics.md), [extras.md](../extras.md#options-storage) |
|
||||
| Server settings | `server.toml` | its keys | stable | [server-reference.md](server-reference.md) |
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Mod files
|
||||
|
||||
The rules a `.hmod` or `.hnmod` follows, in one place, for tools that make or read them. The readers are
|
||||
`horizon_mods::Package` and `horizon_mods::Content` (content), and `horizon_mods::index::native::Bundle` (native
|
||||
code). Level: content is preview, native code experimental ([data-formats.md](data-formats.md)).
|
||||
|
||||
## The zip
|
||||
|
||||
- A zip archive. Entries are stored or deflate-compressed, the only methods the readers support.
|
||||
- A path is relative and `/`-separated: no `\`, no `:`, no empty, `.` or `..` part, and it doesn't start with `/`.
|
||||
Directory entries are skipped. A native entry must be a regular file (no symlinks or devices in the zip's Unix mode).
|
||||
- Each path appears once.
|
||||
|
||||
## Top-level entries
|
||||
|
||||
What a file holds decides what it is (#5865):
|
||||
|
||||
| Entry | Means | Read by |
|
||||
| --- | --- | --- |
|
||||
| `horizon-mod.toml` | a content mod's manifest ([mods.md](../mods.md#the-manifest)) | `Package`, `Content` |
|
||||
| `assets/…`, `data/…` | the content mod's resources and data | `Content` |
|
||||
| `horizon-native.json` | native code: the manifest below | `Bundle` |
|
||||
| `mixins/<id>/…` | the native bundle's sources ([native-mods.md](../native-mods.md#a-bundle)) | `Bundle` |
|
||||
| `mod.wasm` | a legacy WebAssembly mod: it doesn't run any more | refused in a file with native code |
|
||||
|
||||
- **Content only:** `horizon-mod.toml` with `assets/` and `data/`. Nothing compiles.
|
||||
- **Native code:** `horizon-native.json` and `mixins/<id>/`. The launcher builds it into the game.
|
||||
- **Both, in one `.hmod`:** the two manifests name the same mod id. Content entries are checked as content and left
|
||||
out of the build. Any other top-level entry refuses the file.
|
||||
- **`.hnmod`:** native code only, read exactly as before.
|
||||
|
||||
The registry still publishes native mods as `.hnmod`. It switches to `.hmod` once launchers and the game read native
|
||||
`.hmod`s, so no launcher in use gets a file it can't read.
|
||||
|
||||
## `horizon-native.json`
|
||||
|
||||
A JSON object. Unknown fields refuse the file.
|
||||
|
||||
| Field | Type | Rule |
|
||||
| --- | --- | --- |
|
||||
| `schema` | number | `1` |
|
||||
| `id` | string | the mod id, `[a-z0-9_]{1,64}`, the same as `mixins/<id>/` and its `horizon-mixin.toml` `[mod] id` |
|
||||
| `version` | string | not empty, the same as `horizon-mixin.toml` `[mod] version` |
|
||||
| `game` | string | not empty, the Minecraft version (`"26.3"`) |
|
||||
| `horizon` | string | the exact Horizon commit, 40 lowercase hex characters, the same as `[compatibility] horizon_revision` |
|
||||
| `mixins` | string | the exact horizon-mixins commit, 40 lowercase hex characters |
|
||||
| `hooks` | number, optional | the native hook points' version it needs, at least `1` ([native-mods.md](../native-mods.md#mods-on-the-hook-points)) |
|
||||
|
||||
## Under `mixins/<id>/`
|
||||
|
||||
`mixins/<id>/horizon-mixin.toml` must be there. Besides it, only:
|
||||
|
||||
- `.rs` and `.toml` files;
|
||||
- files under `data/`, or under the `data/` of a top-level folder (`engine/data/`);
|
||||
- `README*`, `LICENSE*`, `LICENCE*`, `COPYING*`, `NOTICE*` and `.gitignore`, in the bundle's folder or a top-level
|
||||
folder.
|
||||
|
||||
## Content paths
|
||||
|
||||
`assets/` or `data/`, then at least two parts of `[a-z0-9_.-]` (vanilla's resource-location characters).
|
||||
|
||||
## Limits
|
||||
|
||||
| What | Limit |
|
||||
| --- | --- |
|
||||
| A registry file, as downloaded | 64 MiB |
|
||||
| Native files (everything but content entries) | 512 entries, 64 MiB unpacked in total |
|
||||
| Content files | 65,536 files, 16 MiB each, 512 MiB in total |
|
||||
| `horizon-mod.toml` inside a native file | 1 MiB |
|
||||
| `mod.wasm` (legacy) | 32 MiB |
|
||||
+13
-3
@@ -4,6 +4,14 @@ The 40 areas changed most often in the 30 days to 2026-10-23 (commits on main to
|
||||
counted only for the game loop because nearly every gameplay change touches it). The crate map, how `Game` is
|
||||
organised and how to build and test are in [AGENTS.md](../AGENTS.md).
|
||||
|
||||
Gameplay lives in the `horizon-client` library; `main.rs` is a thin executable calling
|
||||
`entry::main`. The reusable modules, control tool and probes belong to `horizon-client-lib`,
|
||||
whose library root is `crates/horizon-client/src/client_lib.rs`. Source paths stay in place.
|
||||
The gameplay library re-exports the reusable API, keeping existing mod targets unchanged.
|
||||
Gameplay tests run in its library test target, separately from the reusable library's tests.
|
||||
There is no copy of gameplay in the executable's test target. Tools and probes depend only
|
||||
on the reusable library, so gameplay edits do not invalidate their builds.
|
||||
|
||||
Paths: `L/` = `crates/horizon-client/src/`, `G/` = `crates/horizon-client/src/game/`, `W/` =
|
||||
`crates/horizon-world/src/`, `K/` = `crates/horizon-client-core/src/`, `C/` = `crates/horizon-command/src/`,
|
||||
`I/` = `crates/horizon-items/src/`, `R/` = `crates/horizon-client-render/src/`, `N/` = `crates/horizon-client-input/src/`.
|
||||
@@ -12,8 +20,10 @@ Checks: a bare name is a harness (`python3 tools/parity.py <name>`, script `tool
|
||||
|
||||
For the full set of harnesses a file can affect, ask the runner:
|
||||
`printf '%s\n' <paths> > /tmp/p.txt && python3 tools/parity.py --changed-files /tmp/p.txt --list`.
|
||||
Any file of the game binary (`game.rs`, `game/`) selects the ~25 harnesses that run the binary; a client lib
|
||||
file selects ~103.
|
||||
Client probes are built with `-p horizon-client-lib`; the game with `-p horizon-client`.
|
||||
Because the probe library does not compile gameplay, its dep-info does not include `game.rs`
|
||||
or `game/`. Exact harness counts still depend on refreshed records and reachability; use the
|
||||
command above to inspect the selection rather than relying on historical counts.
|
||||
|
||||
| # | Area (commits) | Files | Checks |
|
||||
|---|---|---|---|
|
||||
@@ -22,7 +32,7 @@ file selects ~103.
|
||||
| 3 | World save and load (163) | `L/save.rs`, `G/saving.rs`, `G/persist.rs` (entities), `L/entity_nbt.rs`, `K/block_entity_nbt.rs`, `W/region.rs`, `W/nbt.rs` | save_roundtrip, world_reopen, world, datafix |
|
||||
| 4 | World generation (153) | `W/worldgen.rs`, `W/worldgen/` (`generator*.rs`, `feature_graph.rs`, `structure/`, `placement/`), `L/vanillagen.rs` | chunk_generation, worldgen, server_world_gen, world_spawn, feature_graph, feature_placement, decoration and the other worldgen harnesses |
|
||||
| 5 | Multiplayer client, packets in (145) | `G/remote.rs` (`fn handle_packet`, `fn tick_remote`), `G/remote_*.rs`, `L/net.rs` | dedicated_server, server_entities, lan_e2e (all) |
|
||||
| 6 | Agent control, horizon-ctl (145) | `L/control.rs`, `L/bin/horizon-ctl/`, `G/control.rs`, `G/agent.rs`, `G/events.rs`, `G/ui_control.rs` | `python3 tools/agent_control_e2e.py`; `cargo test -p horizon-client --bin horizon-ctl` |
|
||||
| 6 | Agent control, horizon-ctl (145) | `L/control.rs`, `L/bin/horizon-ctl/`, `G/control.rs`, `G/agent.rs`, `G/events.rs`, `G/ui_control.rs` | `python3 tools/agent_control_e2e.py`; `cargo test -p horizon-client-lib --bin horizon-ctl` |
|
||||
| 7 | CI and release (134) | `.github/workflows/` (`ci.yml`, `parity.yml`, `release.yml`, `launcher.yml`), `tools/ci/`, `tools/parity.py`, `tools/pr_ready.py` | `python3 -m unittest discover -s tests`; actionlint (`.github/actionlint.yaml`) |
|
||||
| 8 | LAN host: connections, play state, tracking (133) | `L/lan/` (`conn.rs`, `hub.rs` + `hub/`, `chunks.rs`, `entity.rs`, `tracking.rs`, `guest.rs`, `playerdata.rs`), `G/lan.rs` | lan, lan_e2e (all), `cargo test -p horizon-client --test lan_bot` |
|
||||
| 9 | Renderer core (132) | `R/gfx.rs` (`Renderer::new`, `Renderer::render`), `R/gfx/` (cull, occlusion, translucency, upload, timing, lod) | client_graphics, culling_rays, translucency; `horizon-client --benchmark standard` |
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ The report is the [benchmark report format](benchmark-report.md): startup to the
|
||||
|
||||
## Reproduce it
|
||||
|
||||
You need Horizon built (`cargo build --release -p horizon-client --bins`) and its resources prepared from your own copy of 26.3 (which also provides `.local/minecraft/26.3`, the vanilla client the driver starts), Java 25 with `javac`, and Python 3.10 or newer.
|
||||
You need Horizon built (`cargo build --release -p horizon-client -p horizon-client-lib --bins`) and its resources prepared from your own copy of 26.3 (which also provides `.local/minecraft/26.3`, the vanilla client the driver starts), Java 25 with `javac`, and Python 3.10 or newer.
|
||||
|
||||
```sh
|
||||
python3 tools/perf_bench.py --game horizon --label horizon-mine
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ Not implemented: anisotropic filtering (wgpu needs linear magnification for it,
|
||||
`tools/graphics_bench.py` opens a fresh creative world (seed 1234) on a virtual screen and flies the camera along four fixed 192-block paths, over a village, a forest, the ocean and a mountain, at render distances 8, 16 and 32, one game per distance. Each flight takes 12 seconds and reports average FPS, 1% lows, frame-time percentiles, CPU time per frame (frame time minus waits for the GPU, the display and the limiter), the split of that CPU time (world update, ticks, frame preparation, renderer) for all frames and for the slowest 1%, and GPU time per frame from timestamp queries.
|
||||
|
||||
```sh
|
||||
cargo build --release -p horizon-client --bins
|
||||
cargo build --release -p horizon-client -p horizon-client-lib --bins
|
||||
python3 tools/graphics_bench.py --label mine # lavapipe on vscreen: CPU-side numbers
|
||||
python3 tools/graphics_bench.py --label mine-gpu --offscreen # the desktop's RTX 4080
|
||||
python3 tools/graphics_bench.py --compare baseline-4080 mine-gpu # markdown table
|
||||
|
||||
@@ -119,7 +119,7 @@ local socket; under control, losing window focus does not pause the game or
|
||||
release held keys, so a script can play while you use other windows.
|
||||
|
||||
```sh
|
||||
cargo build --release -p horizon-client --bins
|
||||
cargo build --release -p horizon-client -p horizon-client-lib --bins
|
||||
C=target/release/horizon-ctl
|
||||
$C launch --world Survival --seed 5 --wait # start the game, open or create the world, wait for it
|
||||
$C state # player, world, screen, crosshair target
|
||||
|
||||
+11
-1
@@ -431,6 +431,16 @@ text files a crate folder carries (`README*`, `LICENSE*`, `COPYING*`, `NOTICE*`,
|
||||
folder or a crate's), so a crate that compiles its README in builds the same from the registry as from a folder. At
|
||||
most 512 files and 64 MiB unpacked; anything else refuses the bundle. A local folder build has no such limits.
|
||||
|
||||
**One `.hmod` for content and code.** A mod's content and its native code ship as one `.hmod`, and what is inside
|
||||
decides what happens: only `horizon-mod.toml` with `assets/` and `data/` is a content mod, nothing compiles; with
|
||||
`horizon-native.json` and `mixins/<id>/` (a bundle as above) it is a native mod, built into the game. Both may be in
|
||||
one file: `horizon-mod.toml`, `assets/` and `data/` are its content, checked as a content mod's
|
||||
(`horizon_mods::Content`) and left out of the build, and the two manifests name the same mod id. A `.hmod` with
|
||||
native code is never a WebAssembly mod: one carrying `mod.wasm` is refused with
|
||||
`horizon_mods::index::native::WASM_GONE`. An `.hnmod` (code only) keeps loading as before. The registry keeps
|
||||
publishing native mods as `.hnmod` until launchers and the game read native `.hmod`s. Every rule of the file is in
|
||||
[api/mod-files.md](api/mod-files.md).
|
||||
|
||||
### Post-build steps
|
||||
|
||||
A `[[post_build]]` names a bin of one of the mod's crates. After the game compiles, the launcher builds it with the
|
||||
@@ -891,7 +901,7 @@ each was built or last used): switching a mod off and on again plays the kept bu
|
||||
The file is pinned by SHA-256 like every registry file, and the version, game and pins are the bundle's own
|
||||
`horizon-native.json`. `revoked.toml` takes a bad release down.
|
||||
A content mod's entry may turn `native-mod` once (never back): its `.hmod` versions stay published and
|
||||
are checked as `.hmod`s, the index lists only its `.hnmod` versions, and the launcher's update keeps the
|
||||
are checked as `.hmod`s, the index lists only its versions with native code, and the launcher's update keeps the
|
||||
player's `.hmod` (the game runs it until a native build has the mod, then skips it); removing the mod removes both.
|
||||
- **Isolation of data.** Native mods run only in the instance they are installed in, with its worlds, options and
|
||||
logs (the default Horizon instance's, when they are installed there); other instances play their own versions.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Run with the pinned prepared client resources:
|
||||
|
||||
```sh
|
||||
CARGO_BUILD_JOBS=6 cargo run --profile fast -p horizon-client --example portal_scan_bench -- \
|
||||
CARGO_BUILD_JOBS=6 cargo run --profile fast -p horizon-client-lib --example portal_scan_bench -- \
|
||||
.local/horizon-client/26.3
|
||||
```
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Run with the pinned prepared client resources:
|
||||
```sh
|
||||
flock /tmp/horizon-optimizations-20261015-heavy.lock env CARGO_BUILD_JOBS=2 \
|
||||
CARGO_TARGET_DIR=/home/bob/Documents/horizon/target \
|
||||
cargo run --profile fast -p horizon-client --example random_tick_eligibility_bench -- \
|
||||
cargo run --profile fast -p horizon-client-lib --example random_tick_eligibility_bench -- \
|
||||
.local/horizon-client/26.3
|
||||
```
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ From this worktree, with no other benchmark running:
|
||||
```sh
|
||||
export CARGO_INCREMENTAL=0
|
||||
export CARGO_TARGET_DIR=/home/bob/Documents/horizon/target
|
||||
cargo build --profile fast -p horizon-client --example save_batch_bench
|
||||
cargo build --profile fast -p horizon-client-lib --example save_batch_bench
|
||||
taskset -c 2 "$CARGO_TARGET_DIR/fast/examples/save_batch_bench"
|
||||
cargo test --profile fast -p horizon-client --lib save::tests
|
||||
cargo clippy --profile fast -p horizon-client --all-targets -- -D warnings
|
||||
|
||||
@@ -14,7 +14,7 @@ Use real prepared resources; no game files belong in Git. Build the baseline
|
||||
at `89b58991bbc02eb79b6965fc4ca56357fe2297d9` with the benchmark example copied in
|
||||
and `ItemRenderer::load(root)` in its item stage. Save that binary, then build
|
||||
this branch with `ItemRenderer::load_with_library(root, assets.model_library.clone())`.
|
||||
Both builds use `cargo build --locked --profile fast -p horizon-client --example
|
||||
Both builds use `cargo build --locked --profile fast -p horizon-client-lib --example
|
||||
model_library_bench` with `HORIZON_BENCH_COUNT_ALLOCATIONS` unset (`opt-level=3`, LTO disabled, 16 codegen units). Set
|
||||
`CARGO_BUILD_JOBS=2`. On the optimization machine, hold
|
||||
`/tmp/horizon-optimizations-20261015-heavy.lock` for each entire build or paired
|
||||
|
||||
@@ -30,7 +30,7 @@ mod crates;
|
||||
const PREFIX: &str = "native-";
|
||||
/// horizon-mixins, for bundles that don't name the revision they were tested with.
|
||||
const MIXINS_GIT: &str = "https://github.com/horizon-rust/horizon-mixins";
|
||||
pub const MIXINS_REV: &str = "a2af5c0dd4117d51ecd70066ba7a00b4a2c10dab";
|
||||
pub const MIXINS_REV: &str = "ddd9700b21997c95d95ac6ee5ecafe3830cf622a";
|
||||
/// The features of the release's own game (game-build.yml).
|
||||
const FEATURES: &str = "horizon-client/mods";
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from tools import interaction_lab
|
||||
|
||||
|
||||
class CargoExampleTests(unittest.TestCase):
|
||||
def test_client_probe_uses_the_reusable_library_package(self):
|
||||
with mock.patch.object(interaction_lab.subprocess, 'run') as run:
|
||||
interaction_lab.cargo_example('horizon-client', 'menu_fixture', Path('cases.txt'), profile='fast')
|
||||
run.assert_called_once_with(
|
||||
['cargo', 'run', '-q', '-j3', '--profile', 'fast', '-p', 'horizon-client-lib',
|
||||
'--example', 'client_probe', '--', 'menu_fixture', 'cases.txt'],
|
||||
check=True, cwd=interaction_lab.lab.ROOT)
|
||||
|
||||
def test_other_probe_packages_are_unchanged(self):
|
||||
with mock.patch.object(interaction_lab.subprocess, 'run') as run:
|
||||
interaction_lab.cargo_example('horizon-world', 'loot_probe', 42)
|
||||
run.assert_called_once_with(
|
||||
['cargo', 'run', '-q', '-j3', '-p', 'horizon-world', '--example', 'world_probe', '--', 'loot_probe', '42'],
|
||||
check=True, cwd=interaction_lab.lab.ROOT)
|
||||
@@ -131,6 +131,51 @@ class ReachTests(unittest.TestCase):
|
||||
self.assertIn("crates/b/src/used.rs", kept)
|
||||
self.assertNotIn("crates/c/src/lib.rs", kept)
|
||||
|
||||
def test_external_library_and_probe_roots_follow_the_manifest(self):
|
||||
workspace(self.root, {
|
||||
"crates/b/Cargo.toml": '[package]\nname = "b"\nautobins = false\nautoexamples = false\n'
|
||||
'[lib]\npath = "../a/src/shared.rs"\n'
|
||||
'[[example]]\nname = "client_probe"\npath = "../a/examples/probe/main.rs"\n',
|
||||
"crates/a/src/shared.rs": "pub mod used;\n",
|
||||
"crates/a/src/lib.rs": "pub fn game_only() {}\n",
|
||||
"crates/a/Cargo.toml": '[package]\nname = "a"\nautobins = false\nautoexamples = false\n',
|
||||
"crates/a/examples/probe/main.rs": "extern crate b as legacy;\nfn main() { legacy::used::helper(); }\n",
|
||||
"crates/b/src/main.rs": "compile_error!(\"disabled auto-bin\");\n",
|
||||
"crates/b/examples/ignored.rs": "compile_error!(\"disabled auto-example\");\n",
|
||||
})
|
||||
graph = parity_reach.SourceGraph(self.root, parity.rust_code)
|
||||
roots = parity_reach.crate_roots(self.root / "crates/b")
|
||||
self.assertIn((self.root / "crates/a/src/shared.rs").resolve(), roots)
|
||||
self.assertIn((self.root / "crates/a/examples/probe/main.rs").resolve(), roots)
|
||||
self.assertNotIn((self.root / "crates/b/src/main.rs").resolve(), roots)
|
||||
self.assertTrue(graph.is_library("crates/a/src/shared.rs"))
|
||||
self.assertEqual(graph.entries([["example", "client_probe", "dev", ["-p", "a"], True]],
|
||||
{"crates/a/examples/probe/main.rs"}), {"crates/a/examples/probe/main.rs"})
|
||||
reached = graph.reachable({"crates/a/examples/probe/main.rs"},
|
||||
{"crates/a/examples/probe/main.rs", "crates/a/src/shared.rs", "crates/a/src/used.rs"})
|
||||
self.assertIn("crates/a/src/used.rs", reached)
|
||||
self.assertEqual(graph.targets(self.root / "crates/b"),
|
||||
{"client_probe": ("example", "crates/a/examples/probe/main.rs")})
|
||||
|
||||
def test_old_broad_client_records_find_moved_ctl_and_probes(self):
|
||||
workspace(self.root, {
|
||||
"crates/horizon-client/Cargo.toml": '[package]\nname = "horizon-client"\nautobins = false\nautoexamples = false\n'
|
||||
'[[bin]]\nname = "horizon-client"\npath = "src/main.rs"\n',
|
||||
"crates/horizon-client/src/main.rs": "fn main() {}\n",
|
||||
"crates/horizon-client/src/bin/horizon-ctl.rs": "fn main() {}\n",
|
||||
"crates/horizon-client/examples/probe/main.rs": "fn main() {}\n",
|
||||
"crates/horizon-client-lib/Cargo.toml": '[package]\nname = "horizon-client-lib"\nautobins = false\nautoexamples = false\n'
|
||||
'[[bin]]\nname = "horizon-ctl"\npath = "../horizon-client/src/bin/horizon-ctl.rs"\n'
|
||||
'[[example]]\nname = "client_probe"\npath = "../horizon-client/examples/probe/main.rs"\n',
|
||||
})
|
||||
graph = parity_reach.SourceGraph(self.root, parity.rust_code)
|
||||
game, ctl, probe = ("crates/horizon-client/" + path for path in
|
||||
["src/main.rs", "src/bin/horizon-ctl.rs", "examples/probe/main.rs"])
|
||||
self.assertEqual(graph.entries([["bins", "", "dev", ["-p", "horizon-client"], False]], {game, ctl, probe}),
|
||||
{game, ctl})
|
||||
self.assertEqual(graph.entries([["examples", "", "dev", ["-p", "horizon-client"], True]], {game, ctl, probe}),
|
||||
{probe})
|
||||
|
||||
def test_unknown_probes_keep_everything(self):
|
||||
self.assertEqual(set(self.narrowed(artifacts=[])), set(self.dependencies))
|
||||
self.assertEqual(set(self.narrowed(artifacts=[["example", "missing", "release", ["-p", "a"], True]])),
|
||||
|
||||
@@ -30,6 +30,31 @@ class ParityRunnerTests(unittest.TestCase):
|
||||
self.assertEqual(parity.artifacts_of(["cargo", "build", "--release", "-p", "horizon-world", "--bin=horizon-world"]),
|
||||
[["bin", "horizon-world", "release", ["-p", "horizon-world"], False]])
|
||||
|
||||
def test_prebuild_follows_a_moved_target_without_losing_features(self):
|
||||
selection = ["-p", "horizon-client", "--no-default-features", "--features", "horizon-client/rng-trace"]
|
||||
artifacts = [["example", "client_probe", "fast", selection, True],
|
||||
["bin", "horizon-ctl", "fast", selection, False]]
|
||||
packages = {("example", "client_probe"): "horizon-client-lib", ("bin", "horizon-ctl"): "horizon-client-lib"}
|
||||
commands = parity.prebuild_commands(artifacts, packages)
|
||||
self.assertEqual(len(commands), 2)
|
||||
for command in commands:
|
||||
self.assertEqual(command[:5], ["cargo", "build", "--locked", "--profile", "fast"])
|
||||
self.assertEqual(command[5:5 + len(selection)], selection)
|
||||
self.assertEqual(command[5 + len(selection):7 + len(selection)], ["-p", "horizon-client-lib"])
|
||||
self.assertEqual(artifacts[0][3], selection)
|
||||
|
||||
def test_old_broad_client_selections_include_the_moved_targets(self):
|
||||
packages = {("bin", "horizon-client"): "horizon-client", ("bin", "horizon-ctl"): "horizon-client-lib",
|
||||
("example", "client_probe"): "horizon-client-lib"}
|
||||
for flag in ["--bins", "--examples"]:
|
||||
command = ["cargo", "build", "-p", "horizon-client", "--no-default-features", flag]
|
||||
artifacts = parity.artifacts_of(command)
|
||||
before = [list(artifact[3]) for artifact in artifacts]
|
||||
self.assertEqual(parity.prebuild_commands(artifacts, packages),
|
||||
[["cargo", "build", "--locked", "--profile", "dev", "-p", "horizon-client",
|
||||
"--no-default-features", "-p", "horizon-client-lib", flag]])
|
||||
self.assertEqual([artifact[3] for artifact in artifacts], before)
|
||||
|
||||
def test_prebuild_repeats_each_harness_selection(self):
|
||||
artifacts = [
|
||||
*parity.artifacts_of(["cargo", "build", "--release", "-p", "horizon-world", "--example", "a"]),
|
||||
@@ -604,5 +629,16 @@ class WorkspaceManifestTests(unittest.TestCase):
|
||||
optional = {"crates/horizon-client/Cargo.toml": self.CLIENT + '\n[features]\nfast = ["wgpu/x"]\n'}
|
||||
self.assertEqual(self.verdict({"crates/horizon-client/Cargo.toml": self.CLIENT}, optional), set())
|
||||
|
||||
def test_external_feature_sources_are_not_discarded(self):
|
||||
with tempfile.TemporaryDirectory() as temp, mock.patch.object(parity, "ROOT", Path(temp)):
|
||||
root = Path(temp)
|
||||
(root / "crates/core").mkdir(parents=True)
|
||||
(root / "shared").mkdir()
|
||||
(root / "crates/core/Cargo.toml").write_text(
|
||||
'[package]\nname = "core"\n[lib]\npath = "../../shared/lib.rs"\n'
|
||||
'[features]\nextra = []\n')
|
||||
(root / "shared/lib.rs").write_text('#[cfg(feature = "extra")]\nfn extra() {}\n')
|
||||
self.assertFalse(parity.feature_sources_changed("core", "extra", {"crates/core/Cargo.toml"}))
|
||||
|
||||
def test_without_a_base_everything_counts(self):
|
||||
self.assertEqual(parity.workspace_only_manifests({"Cargo.lock"}, None), set())
|
||||
@@ -31,6 +31,17 @@ class PrReadyTests(unittest.TestCase):
|
||||
# The workspace manifest and lock touch every crate.
|
||||
self.assertIsNone(pr_ready.touched_crates({"Cargo.lock", "crates/horizon-core/src/lib.rs"}, PACKAGES))
|
||||
|
||||
def test_shared_client_sources_select_the_library_without_gameplay(self):
|
||||
packages = {**PACKAGES, "horizon-client-lib": "crates/horizon-client-lib"}
|
||||
for path in ["src/client_lib.rs", "src/health.rs", "src/lan/hub.rs", "src/bin/horizon-ctl/main.rs",
|
||||
"examples/probe/main.rs", "tests/lan_bot.rs"]:
|
||||
self.assertEqual(pr_ready.touched_crates({f"crates/horizon-client/{path}"}, packages),
|
||||
["horizon-client-lib"])
|
||||
for path in ["src/lib.rs", "src/main.rs", "src/entry.rs", "src/game.rs", "src/game/combat.rs",
|
||||
"tests/launch_api.rs", "Cargo.toml", "build.rs"]:
|
||||
self.assertEqual(pr_ready.touched_crates({f"crates/horizon-client/{path}"}, packages),
|
||||
["horizon-client"])
|
||||
|
||||
def test_which_workspace_builds_a_path(self):
|
||||
self.assertTrue(pr_ready.workspace_rust("crates/horizon-theme/fonts/x.ttf"))
|
||||
self.assertTrue(pr_ready.workspace_rust("Cargo.toml"))
|
||||
|
||||
@@ -963,7 +963,7 @@ def main():
|
||||
if args.bin_dir:
|
||||
bins = Path(args.bin_dir).resolve()
|
||||
else:
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client', '--bins'],
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client', '-p', 'horizon-client-lib', '--bins'],
|
||||
cwd=ROOT, check=True)
|
||||
meta = json.loads(subprocess.run(['cargo', 'metadata', '--format-version', '1', '--no-deps'], cwd=ROOT,
|
||||
capture_output=True, text=True, check=True).stdout)
|
||||
|
||||
@@ -66,7 +66,7 @@ def run_harness(name, oracle, example, inputs_text, extra_sources=(), java_args=
|
||||
dependencies = os.pathsep.join(map(str, [jars['client'], *libraries]))
|
||||
run(['javac', '--release', lock['minecraft']['java_major'], '-proc:none', '-sourcepath', lab.ROOT / 'mods/horizon/src/test/java',
|
||||
'-cp', dependencies, '-d', classes, oracle_path], 'compile-java')
|
||||
run(['cargo', 'build', '--locked', '-p', 'horizon-client', '--example', 'client_probe'], 'compile-rust')
|
||||
run(['cargo', 'build', '--locked', '-p', 'horizon-client-lib', '--example', 'client_probe'], 'compile-rust')
|
||||
metadata = json.loads(subprocess.check_output(['cargo', 'metadata', '--locked', '--no-deps', '--format-version', '1'], cwd=lab.ROOT))
|
||||
binary = Path(metadata['target_directory']) / 'debug/examples' / ('client_probe.exe' if os.name == 'nt' else 'client_probe')
|
||||
run([binary, example, *rust_args, inputs, build / 'rust.txt'], 'rust')
|
||||
|
||||
@@ -261,7 +261,7 @@ def main():
|
||||
if args.bin_dir:
|
||||
bins = Path(args.bin_dir).resolve()
|
||||
else:
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client', '--bins'],
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client', '-p', 'horizon-client-lib', '--bins'],
|
||||
cwd=ROOT, check=True)
|
||||
meta = json.loads(subprocess.run(['cargo', 'metadata', '--format-version', '1', '--no-deps'], cwd=ROOT,
|
||||
capture_output=True, text=True, check=True).stdout)
|
||||
|
||||
@@ -170,7 +170,7 @@ def main():
|
||||
return
|
||||
ctl = Path(args.build).resolve() / "horizon-ctl"
|
||||
if not ctl.exists():
|
||||
sys.exit(f"{ctl} missing: cargo build --release -p horizon-client --bins")
|
||||
sys.exit(f"{ctl} missing: cargo build --release -p horizon-client -p horizon-client-lib --bins")
|
||||
cwd = ROOT / ".local/graphics-bench"
|
||||
shutil.rmtree(cwd / "saves" / WORLD, ignore_errors=True)
|
||||
cwd.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -51,6 +51,8 @@ def cargo_example(package, example, *args, profile=None):
|
||||
if profile:
|
||||
command += ['--profile', profile]
|
||||
probe = package.removeprefix('horizon-') + '_probe' # each crate's probe example: client_probe, world_probe
|
||||
if package == 'horizon-client':
|
||||
package = 'horizon-client-lib'
|
||||
command += ['-p', package, '--example', probe, '--', example, *map(str, args)]
|
||||
subprocess.run(command, check=True, cwd=lab.ROOT)
|
||||
|
||||
|
||||
+1
-1
@@ -6576,7 +6576,7 @@ def main():
|
||||
if args.bin_dir:
|
||||
bins = Path(args.bin_dir).resolve()
|
||||
else:
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client',
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client', '-p', 'horizon-client-lib',
|
||||
'--bin', 'horizon-client', '--bin', 'horizon-ctl'],
|
||||
cwd=ROOT, check=True)
|
||||
meta = json.loads(subprocess.run(['cargo', 'metadata', '--format-version', '1', '--no-deps'], cwd=ROOT,
|
||||
|
||||
@@ -237,7 +237,7 @@ def main():
|
||||
subprocess.run(command, check=True)
|
||||
if not facts.exists():
|
||||
sys.exit(f'{facts}: no oracle facts (run with --jars, or tools/neoforge_oracle.py export)')
|
||||
subprocess.run(['cargo', 'build', '--release', '-q', '-p', 'horizon-client', '--example', 'mod_port_probe'],
|
||||
subprocess.run(['cargo', 'build', '--release', '-q', '-p', 'horizon-client-lib', '--example', 'mod_port_probe'],
|
||||
cwd=ROOT, check=True)
|
||||
metadata = json.loads(subprocess.check_output(['cargo', 'metadata', '--no-deps', '--format-version', '1'],
|
||||
cwd=ROOT, text=True))
|
||||
|
||||
@@ -42,7 +42,7 @@ def close(g):
|
||||
|
||||
|
||||
def build():
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client', '--bin', 'horizon-client',
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client-lib', '--bin', 'horizon-client',
|
||||
'--bin', 'horizon-ctl', '--example', 'old_world_fixture'], cwd=lan.ROOT, check=True)
|
||||
meta = json.loads(subprocess.run(['cargo', 'metadata', '--format-version', '1', '--no-deps'], cwd=lan.ROOT,
|
||||
capture_output=True, text=True, check=True).stdout)
|
||||
|
||||
@@ -35,7 +35,7 @@ def compile_oracle(build, oracle_name, jars, libraries, java_major):
|
||||
|
||||
def build_probe(build, example):
|
||||
"""Build the horizon-client probe binary; returns the command that runs probe `example`."""
|
||||
run_logged(['cargo', 'build', '--locked', '-p', 'horizon-client', '--example', 'client_probe'],
|
||||
run_logged(['cargo', 'build', '--locked', '-p', 'horizon-client-lib', '--example', 'client_probe'],
|
||||
build, 'compile-rust-' + example, env=dict(os.environ, CARGO_BUILD_JOBS='3'))
|
||||
import json
|
||||
metadata = json.loads(subprocess.check_output(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
tarball for one of your own Linux or macOS machines. The resources are Mojang
|
||||
assets: keep the archive in ignored .local/ and never publish or commit it.
|
||||
|
||||
cargo build --release -p horizon-client --bins
|
||||
cargo build --release -p horizon-client -p horizon-client-lib --bins
|
||||
python3 tools/package_native_client.py
|
||||
"""
|
||||
import argparse
|
||||
|
||||
@@ -644,6 +644,15 @@ def prebuild_commands(artifacts, packages=None):
|
||||
if len(artifact) < 5:
|
||||
continue
|
||||
kind, name, profile, selection, examples = artifact
|
||||
owner = (packages or {}).get((kind, name))
|
||||
selected = [value for flag, value in zip(selection, selection[1:]) if flag == "-p"]
|
||||
if not name and kind in ("bins", "examples") and "horizon-client" in selected \
|
||||
and "horizon-client-lib" in (packages or {}).values():
|
||||
# Broad records predate the client package split too.
|
||||
owner = "horizon-client-lib"
|
||||
if owner and selected and owner not in selected:
|
||||
# A recorded target may have moved packages; retain its feature flags and old selections.
|
||||
selection = [*selection, "-p", owner]
|
||||
targets = groups.setdefault((profile, tuple(selection), examples), [])
|
||||
target = [f"--{kind}", name] if name else [f"--{kind}"]
|
||||
if target not in targets:
|
||||
@@ -1366,6 +1375,9 @@ def feature_sources_changed(crate, feature, changed):
|
||||
break
|
||||
else:
|
||||
return False
|
||||
if any(not root.is_relative_to(manifest.parent.resolve()) for root in parity_reach.crate_roots(manifest.parent)):
|
||||
# A directory-only scan cannot prove that shared external sources changed too.
|
||||
return False
|
||||
table = re.search(r"^\[features\]\n(.*?)(?=^\[|\Z)", text, re.M | re.S)
|
||||
implies = {name: re.findall(r'"([^"]+)"', value) for name, value in
|
||||
re.findall(r'^([\w-]+)\s*=\s*\[([^\]]*)\]', table.group(1) if table else "", re.M)}
|
||||
|
||||
+48
-10
@@ -192,13 +192,33 @@ def parse_items(code, start, end, items):
|
||||
"mentions": sorted(words(text)), "paths": sorted(path_words(text)), "qualified": sorted(qualified_words(text)), "invokes": sorted(set(MACRO_USE.findall(text)))})
|
||||
|
||||
|
||||
def library_root(crate_dir):
|
||||
"""Cargo's library source, including an explicit path outside the package directory."""
|
||||
manifest = crate_dir / "Cargo.toml"
|
||||
text = manifest.read_text(encoding="utf-8") if manifest.is_file() else ""
|
||||
table = re.search(r"^\[lib\]([^\[]*)", text, re.M)
|
||||
path = re.search(r'^path\s*=\s*"([^"\n]+)"', table.group(1), re.M) if table else None
|
||||
return (crate_dir / (path.group(1) if path else "src/lib.rs")).resolve()
|
||||
|
||||
|
||||
def crate_roots(crate_dir):
|
||||
"""A crate's root files: its library, binaries and examples."""
|
||||
roots = [crate_dir / "src" / "lib.rs", crate_dir / "src" / "main.rs"]
|
||||
for folder in (crate_dir / "src" / "bin", crate_dir / "examples"):
|
||||
if folder.is_dir():
|
||||
roots += sorted(folder.glob("*.rs")) + sorted(folder.glob("*/main.rs"))
|
||||
return [root for root in roots if root.is_file()]
|
||||
path = crate_dir / "Cargo.toml"
|
||||
manifest = path.read_text(encoding="utf-8") if path.is_file() else ""
|
||||
roots = [library_root(crate_dir)]
|
||||
if not re.search(r"^autobins\s*=\s*false", manifest, re.M):
|
||||
roots.append(crate_dir / "src" / "main.rs")
|
||||
folder = crate_dir / "src" / "bin"
|
||||
roots += sorted(folder.glob("*.rs")) + sorted(folder.glob("*/main.rs"))
|
||||
if not re.search(r"^autoexamples\s*=\s*false", manifest, re.M):
|
||||
folder = crate_dir / "examples"
|
||||
roots += sorted(folder.glob("*.rs")) + sorted(folder.glob("*/main.rs"))
|
||||
for _, table in re.findall(r"^\[\[(bin|example)\]\]([^\[]*)", manifest, re.M):
|
||||
path = re.search(r'^path\s*=\s*"([^"\n]+)"', table, re.M)
|
||||
if path:
|
||||
roots.append(crate_dir / path.group(1))
|
||||
return [root.resolve() for root in roots if root.is_file()]
|
||||
|
||||
|
||||
|
||||
class SourceGraph:
|
||||
@@ -215,6 +235,8 @@ class SourceGraph:
|
||||
self.parent = {} # module file -> the file declaring it
|
||||
self.module_name = {}
|
||||
self.roots = set()
|
||||
self.library_roots = {manifest.parent.name: self.relative(library_root(manifest.parent))
|
||||
for manifest in (self.root / "crates").glob("*/Cargo.toml")}
|
||||
self.target_cache = {}
|
||||
self.workspace_heads = None
|
||||
self.test_only = set() # modules compiled into tests only
|
||||
@@ -238,6 +260,16 @@ class SourceGraph:
|
||||
packages = [value for flag, value in zip(selection, selection[1:]) if flag == "-p"] or \
|
||||
[manifest.parent.name for manifest in sorted((self.root / "crates").glob("*/Cargo.toml"))]
|
||||
roots = set()
|
||||
if name and not any(name in self.targets(self.root / "crates" / package) for package in packages):
|
||||
owners = [manifest.parent.name for manifest in (self.root / "crates").glob("*/Cargo.toml")
|
||||
if self.targets(manifest.parent).get(name, (None,))[0] == kind.rstrip("s")]
|
||||
if len(owners) == 1:
|
||||
packages = [*packages, owners[0]]
|
||||
if not name and kind in ("bins", "examples") and "horizon-client" in packages \
|
||||
and "horizon-client-lib" not in packages \
|
||||
and (self.root / "crates/horizon-client-lib/Cargo.toml").is_file():
|
||||
# Broad records predate the client package split too.
|
||||
packages = [*packages, "horizon-client-lib"]
|
||||
for package in packages:
|
||||
crate = self.root / "crates" / package
|
||||
targets = self.targets(crate)
|
||||
@@ -270,9 +302,11 @@ class SourceGraph:
|
||||
found = {}
|
||||
manifest = (crate / "Cargo.toml").read_text(encoding="utf-8") if (crate / "Cargo.toml").is_file() else ""
|
||||
package = re.search(r'^\[package\][^\[]*?^name\s*=\s*"([^"]+)"', manifest, re.MULTILINE | re.DOTALL)
|
||||
if (crate / "src" / "main.rs").is_file() and package:
|
||||
if (crate / "src" / "main.rs").is_file() and package and not re.search(r"^autobins\s*=\s*false", manifest, re.M):
|
||||
found[package.group(1)] = ("bin", self.relative(crate / "src" / "main.rs"))
|
||||
for kind, folder in (("bin", crate / "src" / "bin"), ("example", crate / "examples")):
|
||||
if re.search(rf"^auto{kind}s\s*=\s*false", manifest, re.M):
|
||||
continue
|
||||
for path in sorted(folder.glob("*.rs")) + sorted(folder.glob("*/main.rs")):
|
||||
found[path.stem if path.name != "main.rs" else path.parent.name] = (kind, self.relative(path))
|
||||
for kind, table in re.findall(r"^\[\[(bin|example)\]\]([^\[]*)", manifest, re.MULTILINE):
|
||||
@@ -289,7 +323,7 @@ class SourceGraph:
|
||||
return self.root_of.get(path, path)
|
||||
|
||||
def is_library(self, path):
|
||||
return self.target(path).endswith("/src/lib.rs")
|
||||
return self.target(path) in self.library_roots.values()
|
||||
|
||||
def relative(self, path):
|
||||
return Path(path).resolve().relative_to(self.root.resolve()).as_posix()
|
||||
@@ -357,6 +391,10 @@ class SourceGraph:
|
||||
declarations.append([match.group(1), [name for name, _ in stack], attribute.group(1) if attribute else None,
|
||||
bool(TEST_MODULE.search(window))])
|
||||
items = []
|
||||
# An extern-crate alias still links its source crate, even when callers use the alias.
|
||||
for match in re.finditer(r"\bextern\s+crate\s+(\w+)(?:\s+as\s+\w+)?\s*;", code):
|
||||
items.append({"kind": "glue", "names": [], "mentions": [match.group(1)],
|
||||
"paths": [match.group(1)], "qualified": [], "invokes": []})
|
||||
parse_items(code, 0, len(code), items)
|
||||
self.files[path] = parsed = {
|
||||
"declarations": declarations,
|
||||
@@ -608,7 +646,7 @@ class SourceGraph:
|
||||
|
||||
try:
|
||||
for path in universe:
|
||||
if path in entries or path not in self.parent and not path.endswith("/src/lib.rs"):
|
||||
if path in entries or path not in self.parent and not self.is_library(path):
|
||||
open_module(path, "entry, or a file no module places")
|
||||
for node in seeds:
|
||||
reach(node, "entry" if node[0] in entries else "always counted")
|
||||
@@ -627,7 +665,7 @@ class SourceGraph:
|
||||
# A library's root opens once reached code names the crate (`horizon_world::`, `use horizon_command
|
||||
# as command`): its items are out of reach until then.
|
||||
for word in (source["mentions"] | source["paths"]) & idents.keys():
|
||||
root = f"crates/{idents[word]}/src/lib.rs"
|
||||
root = self.library_roots[idents[word]]
|
||||
if root in universe and root not in opened:
|
||||
open_module(root, f"{why} names {word}")
|
||||
# Plain mentions reach items of this file and of the modules above it.
|
||||
@@ -643,7 +681,7 @@ class SourceGraph:
|
||||
for name in item["names"]:
|
||||
for target in sees.get(crate, {crate}):
|
||||
for module in list(by_module.get((target, name), ())) + \
|
||||
([f"crates/{idents[name]}/src/lib.rs"] if name in idents else []):
|
||||
([self.library_roots[idents[name]]] if name in idents else []):
|
||||
if module not in globbed and module in universe:
|
||||
globbed.add(module)
|
||||
wake([(module, index, None) for index in range(len(self.parse(module)["items"]))]
|
||||
|
||||
+1
-1
@@ -244,7 +244,7 @@ def gen_vanilla_order(root, version, store, build):
|
||||
"dev.horizonlab.parity.VanillaCommandParseOracle", "tree", build], build)
|
||||
env = dict(os.environ, CARGO_TARGET_DIR=str(root / ".local" / "port-target"))
|
||||
jobs = os.environ.get("FLEET_LOAD_JOBS")
|
||||
run(["cargo", "build", "-p", "horizon-client", "--example", "client_probe",
|
||||
run(["cargo", "build", "-p", 'horizon-client-lib', "--example", "client_probe",
|
||||
*(["-j", jobs] if jobs else [])], root, env=env)
|
||||
fixture = root / ".local/port-target/debug/examples/client_probe"
|
||||
run([fixture, "command_parse_fixture", "tree", build / "rust_tree.json"], root)
|
||||
|
||||
+8
-1
@@ -97,6 +97,13 @@ def touched_crates(paths, packages):
|
||||
for path in paths:
|
||||
if is_doc(path):
|
||||
continue
|
||||
if "horizon-client-lib" in packages and path.startswith("crates/horizon-client/"):
|
||||
relative = path.removeprefix("crates/horizon-client/")
|
||||
gameplay = relative in {"Cargo.toml", "build.rs", "windows-segment-heap.manifest",
|
||||
"src/lib.rs", "src/main.rs", "src/entry.rs", "src/game.rs", "tests/launch_api.rs"} \
|
||||
or relative.startswith("src/game/")
|
||||
crates.add("horizon-client" if gameplay else "horizon-client-lib")
|
||||
continue
|
||||
owners = [name for name, directory in packages.items() if path.startswith(directory.rstrip("/") + "/")]
|
||||
if owners:
|
||||
crates.add(max(owners, key=lambda name: len(packages[name])))
|
||||
@@ -328,7 +335,7 @@ def mod_features(crates):
|
||||
# they are #[ignore]d and run here only when one of these changes.
|
||||
CONTENT_ROOT = ("crates/horizon-client/src/mod_content", "crates/horizon-client-render/src/assets.rs", "crates/horizon-items/",
|
||||
"crates/horizon-mods/", "crates/horizon-assets/")
|
||||
CONTENT_ROOT_TESTS = "cargo test --locked -p horizon-client --lib mod_content::tests:: -- --ignored"
|
||||
CONTENT_ROOT_TESTS = "cargo test --locked -p horizon-client-lib --lib mod_content::tests:: -- --ignored"
|
||||
|
||||
|
||||
def plan(args, base, changed, crates_of, libraries, dependencies):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Profile the validation and typed-deserialization passes in separate processes.
|
||||
|
||||
Build `cargo build --release -p horizon-client --example gameplay_decode_profile`
|
||||
Build `cargo build --release -p horizon-client-lib --example gameplay_decode_profile`
|
||||
then run this tool with the matching prepared resources. These passes are
|
||||
both currently required for exact JSON error classification/precedence.
|
||||
"""
|
||||
|
||||
@@ -18,7 +18,7 @@ console` attaches through a pipe, runs two commands and shows what they log.
|
||||
|
||||
Everything runs in a fresh temporary folder with fresh HORIZON_HOME and XDG folders and no audio. Logs go
|
||||
to --out. Build first:
|
||||
cargo build --profile fast -p horizon-client --bins -p horizon-bots -p horizon-server
|
||||
cargo build --profile fast -p horizon-client -p horizon-client-lib --bins -p horizon-bots -p horizon-server
|
||||
"""
|
||||
import argparse
|
||||
import http.client
|
||||
|
||||
+1
-1
@@ -214,7 +214,7 @@ def main():
|
||||
if args.bin_dir:
|
||||
bins = Path(args.bin_dir).resolve()
|
||||
else:
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client', '--bins'],
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client', '-p', 'horizon-client-lib', '--bins'],
|
||||
cwd=ROOT, check=True)
|
||||
bins = ROOT / 'target/fast'
|
||||
out = Path(args.out)
|
||||
|
||||
@@ -16,7 +16,7 @@ ops.json); at level 2 the admins' commands must do nothing.
|
||||
|
||||
The game and the server run with fresh HORIZON_HOME, launcher and XDG folders and without audio. Logs go to --out.
|
||||
Build first:
|
||||
cargo build --profile fast -p horizon-client --bins
|
||||
cargo build --profile fast -p horizon-client -p horizon-client-lib --bins
|
||||
cargo build --profile fast -p horizon-bots
|
||||
"""
|
||||
import argparse
|
||||
|
||||
@@ -241,7 +241,7 @@ def main():
|
||||
expected = 3 * operations + len(fixtures)
|
||||
(build / 'inputs.txt').write_text(''.join(json.dumps(f, separators=(',', ':')) + '\n' for f in fixtures))
|
||||
input_hash = lab.digest(build / 'inputs.txt', 'sha256')
|
||||
run(['cargo', 'build', '--locked', '-p', 'horizon-client', '--example', 'client_probe'], 'compile-rust')
|
||||
run(['cargo', 'build', '--locked', '-p', 'horizon-client-lib', '--example', 'client_probe'], 'compile-rust')
|
||||
metadata = json.loads(subprocess.check_output(['cargo', 'metadata', '--locked', '--no-deps', '--format-version', '1'], cwd=lab.ROOT))
|
||||
binary = Path(metadata['target_directory']) / 'debug/examples' / ('client_probe.exe' if os.name=='nt' else 'client_probe')
|
||||
run([binary, 'arrow_probe',resources, build, '--describe'], 'describe')
|
||||
|
||||
@@ -279,7 +279,7 @@ def main():
|
||||
*(PARITY / f'{n}.java' for n in ('ParityData', 'VanillaBlockEntityAnimOracle'))], 'compile-java')
|
||||
run(['java', '-Xmx2G', '-cp', str(classes) + os.pathsep + dependencies, 'dev.horizonlab.parity.VanillaBlockEntityAnimOracle',
|
||||
inputs, build / 'vanilla.txt'], 'vanilla')
|
||||
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe', '--',
|
||||
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client-lib', '--example', 'client_probe', '--',
|
||||
'block_entity_anim_fixture', resources, inputs, build / 'rust.txt'], 'rust')
|
||||
|
||||
def split(path):
|
||||
|
||||
@@ -303,7 +303,7 @@ def main():
|
||||
vanilla = [json.loads(l) for l in (build / 'vanilla.jsonl').read_text().splitlines()]
|
||||
textures = sorted({s['texture'] for v in vanilla for s in v.get('submits', []) if 'texture' in s})
|
||||
(build / 'textures.txt').write_text(''.join(t + '\n' for t in textures))
|
||||
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe', '--',
|
||||
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client-lib', '--example', 'client_probe', '--',
|
||||
'block_entity_render_fixture', resources, inputs, build / 'textures.txt', build / 'rust.jsonl'], 'rust')
|
||||
rust = [json.loads(l) for l in (build / 'rust.jsonl').read_text().splitlines()]
|
||||
assert len(vanilla) == len(rust), (len(vanilla), len(rust))
|
||||
|
||||
@@ -119,7 +119,7 @@ def main():
|
||||
'compile-java')
|
||||
run(['java', '-Xmx1G', '-cp', str(classes) + os.pathsep + dependencies,
|
||||
'dev.horizonlab.parity.VanillaBlockEntityTickerOracle', inputs, build / 'vanilla.txt'], 'vanilla')
|
||||
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client', '--example',
|
||||
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client-lib', '--example',
|
||||
'client_probe', '--', 'block_entity_tickers_fixture', inputs, build / 'rust.txt'], 'rust')
|
||||
matched = base.compare(build / 'vanilla.txt', build / 'rust.txt')
|
||||
ticks = sum(1 for line in lines if line == 'tick')
|
||||
|
||||
@@ -64,7 +64,7 @@ def main():
|
||||
registries = json.loads((build / 'registries.json').read_text())
|
||||
corpus = corpus_module.corpus(tree, registries)
|
||||
(build / 'corpus.txt').write_text(''.join(line + '\n' for line in corpus), encoding='utf-8')
|
||||
run(['cargo', 'build', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe'], 'compile-rust')
|
||||
run(['cargo', 'build', '--profile', 'fast', '-p', 'horizon-client-lib', '--example', 'client_probe'], 'compile-rust')
|
||||
metadata = json.loads(subprocess.check_output(['cargo', 'metadata', '--no-deps', '--format-version', '1'], cwd=lab.ROOT, text=True))
|
||||
binary = Path(metadata['target_directory']) / 'fast/examples' / ('client_probe.exe' if os.name=='nt' else 'client_probe')
|
||||
resources = lab.RESOURCES
|
||||
|
||||
@@ -57,7 +57,7 @@ def main():
|
||||
with (build / (name + '.log')).open('w', encoding='utf-8') as log:
|
||||
base.run(list(map(str, command)), stdout=log, stderr=subprocess.STDOUT)
|
||||
run(['javac', '--release', lock['minecraft']['java_major'], '-proc:none','-sourcepath',str(lab.ROOT/'mods/horizon/src/test/java'), '-cp', str(jars['server']) + os.pathsep + deps, '-d', classes, oracle], 'compile-java')
|
||||
run(['cargo', 'build', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe'], 'compile-rust')
|
||||
run(['cargo', 'build', '--profile', 'fast', '-p', 'horizon-client-lib', '--example', 'client_probe'], 'compile-rust')
|
||||
metadata = json.loads(subprocess.check_output(['cargo', 'metadata', '--no-deps', '--format-version', '1'], cwd=lab.ROOT, text=True))
|
||||
binary = Path(metadata['target_directory']) / 'fast/examples' / ('client_probe.exe' if os.name=='nt' else 'client_probe')
|
||||
run([binary, 'enchanting_fixture',resources, inputs, build / 'rust.txt'], 'rust')
|
||||
|
||||
@@ -149,7 +149,7 @@ def main():
|
||||
'-cp', str(jars['server']) + os.pathsep + deps, '-d', classes, oracle], 'compile-java')
|
||||
run(['java', '-Xmx2G', '-cp', os.pathsep.join([str(classes), str(jars['server']), deps]),
|
||||
'dev.horizonlab.parity.VanillaEntityDeathLootOracle', inputs, build / 'server.txt'], 'server')
|
||||
run(['cargo', 'build', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe'], 'compile-rust')
|
||||
run(['cargo', 'build', '--profile', 'fast', '-p', 'horizon-client-lib', '--example', 'client_probe'], 'compile-rust')
|
||||
metadata = json.loads(subprocess.check_output(['cargo', 'metadata', '--no-deps', '--format-version', '1'], cwd=lab.ROOT, text=True))
|
||||
binary = Path(metadata['target_directory']) / 'fast/examples/client_probe'
|
||||
run([binary, 'entity_death_loot_fixture', resources, inputs, build / 'rust.txt'], 'rust')
|
||||
|
||||
@@ -381,7 +381,7 @@ def main():
|
||||
run(['javac', '--release', lock['minecraft']['java_major'], '-proc:none', '-cp', dependencies, '-d', classes, oracle], 'compile-java')
|
||||
run(['java', '-Xmx1G', '-cp', str(classes) + os.pathsep + dependencies, 'dev.horizonlab.parity.VanillaEntityAnimOracle',
|
||||
inputs, build / 'vanilla.jsonl', jars['client']], 'vanilla')
|
||||
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe', '--',
|
||||
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client-lib', '--example', 'client_probe', '--',
|
||||
'entity_anim_fixture', resources, build / 'vanilla.jsonl', build / 'rust.jsonl'], 'rust')
|
||||
vanilla = [json.loads(l) for l in (build / 'vanilla.jsonl').read_text().splitlines()]
|
||||
rust = [json.loads(l) for l in (build / 'rust.jsonl').read_text().splitlines()]
|
||||
|
||||
@@ -66,7 +66,7 @@ def main():
|
||||
'-cp', str(jars['server']) + os.pathsep + deps, '-d', classes, oracle], 'compile-java')
|
||||
run(['java', '-Xmx1G', '-cp', os.pathsep.join([str(classes), str(jars['server']), deps]),
|
||||
'dev.horizonlab.parity.VanillaEnvironmentOracle', inputs, build / 'vanilla.jsonl'], 'vanilla')
|
||||
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe', '--',
|
||||
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client-lib', '--example', 'client_probe', '--',
|
||||
'environment_fixture', resources, inputs, build / 'rust.jsonl'], 'rust')
|
||||
vanilla = [json.loads(l) for l in (build / 'vanilla.jsonl').read_text().splitlines()]
|
||||
rust = [json.loads(l) for l in (build / 'rust.jsonl').read_text().splitlines()]
|
||||
|
||||
@@ -148,7 +148,7 @@ def main():
|
||||
print(f'{len(vanilla)} oracle lines')
|
||||
return
|
||||
halloween = vanilla[0].split()[1]
|
||||
run(['cargo', 'build', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe'], 'compile-rust')
|
||||
run(['cargo', 'build', '--profile', 'fast', '-p', 'horizon-client-lib', '--example', 'client_probe'], 'compile-rust')
|
||||
metadata = json.loads(subprocess.check_output(['cargo', 'metadata', '--no-deps', '--format-version', '1'], cwd=lab.ROOT, text=True))
|
||||
binary = Path(metadata['target_directory']) / 'fast/examples/client_probe'
|
||||
run([binary, 'finalize_spawn_fixture', resources, inputs, build / 'rust.txt', halloween], 'rust')
|
||||
|
||||
@@ -15,7 +15,7 @@ from integration_env import suite_lock
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client',
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client', '-p', 'horizon-client-lib',
|
||||
'-p', 'horizon-relay', '--bin', 'horizon-client', '--bin', 'horizon-ctl',
|
||||
'--bin', 'horizon-relay'], cwd=lab.ROOT, check=True)
|
||||
metadata = json.loads(subprocess.check_output(
|
||||
|
||||
@@ -34,7 +34,7 @@ def main():
|
||||
subprocess.run(['java', '-Xmx512M', '-cp', str(classes) + os.pathsep + classpath,
|
||||
'dev.horizonlab.parity.VanillaHeldDropOracle', str(build / 'vanilla.txt')], check=True)
|
||||
with (build / 'rust.txt').open('w') as out:
|
||||
subprocess.run(['cargo', 'run', '--quiet', '-p', 'horizon-client', '--example',
|
||||
subprocess.run(['cargo', 'run', '--quiet', '-p', 'horizon-client-lib', '--example',
|
||||
'client_probe', '--', 'held_drop_fixture'], cwd=lab.ROOT, stdout=out, check=True)
|
||||
matched = base.compare(build / 'vanilla.txt', build / 'rust.txt')
|
||||
print(f'Held drop parity passed: {matched} inventory/drop/swing/packet snapshots match vanilla')
|
||||
|
||||
@@ -114,7 +114,7 @@ def main():
|
||||
*(PARITY / f'{n}.java' for n in ('ParityData', 'ParityFont', 'VanillaHudOracle'))], 'compile-java')
|
||||
run(['java', '-Xmx2G', '-cp', str(classes) + os.pathsep + dependencies, 'dev.horizonlab.parity.VanillaHudOracle',
|
||||
inputs, build / 'vanilla.jsonl'], 'vanilla')
|
||||
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe', '--',
|
||||
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client-lib', '--example', 'client_probe', '--',
|
||||
'hud_fixture', resources, inputs, build / 'rust.jsonl'], 'rust')
|
||||
vanilla = [json.loads(l) for l in (build / 'vanilla.jsonl').read_text().splitlines()]
|
||||
rust = [json.loads(l) for l in (build / 'rust.jsonl').read_text().splitlines()]
|
||||
|
||||
@@ -91,7 +91,7 @@ def main():
|
||||
if not (dimension / 'region').is_dir():
|
||||
dimension = run / 'world'
|
||||
for extra in (['--chunks', '0', '0', '2', '3'], ['--incremental', '100']):
|
||||
status = subprocess.run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client',
|
||||
status = subprocess.run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client-lib',
|
||||
'--example', 'client_probe', '--', 'light_parity', str(RESOURCES), str(dimension), *extra],
|
||||
cwd=ROOT).returncode
|
||||
if status:
|
||||
|
||||
@@ -44,7 +44,7 @@ def main():
|
||||
classes=build/'classes';classes.mkdir(exist_ok=True)
|
||||
dependencies=os.pathsep.join(map(str,[jars['client'],*libraries]))
|
||||
run(['javac','--release',lock['minecraft']['java_major'],'-proc:none','-sourcepath',str(lab.ROOT/'mods/horizon/src/test/java'),'-cp',dependencies,'-d',classes,oracle],'compile-java')
|
||||
run(['cargo','build','--locked','-p','horizon-client','--example','client_probe'],'compile-rust')
|
||||
run(['cargo','build','--locked','-p','horizon-client-lib','--example','client_probe'],'compile-rust')
|
||||
metadata=json.loads(subprocess.check_output(['cargo','metadata','--locked','--no-deps','--format-version','1'],cwd=lab.ROOT))
|
||||
binary=Path(metadata['target_directory'])/'debug/examples'/('client_probe.exe' if os.name=='nt' else 'client_probe')
|
||||
run([binary,'input_probe',inputs,build/'rust.txt'],'rust')
|
||||
|
||||
@@ -663,7 +663,7 @@ def main():
|
||||
'dev.horizonlab.parity.VanillaMobAiOracle', inputs, build / 'vanilla.txt', world],
|
||||
build, 'vanilla', timeout=3600)
|
||||
if not args.java_only:
|
||||
command = ['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe']
|
||||
command = ['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client-lib', '--example', 'client_probe']
|
||||
target, env = lab.cargo_target(), None
|
||||
if args.trace:
|
||||
target = lab.LOCAL / 'mob-ai-trace-target'
|
||||
|
||||
@@ -62,7 +62,7 @@ def main():
|
||||
subprocess.run(list(map(str, cmd)), cwd=lab.ROOT, check=True, timeout=3600, stdout=log, stderr=subprocess.STDOUT)
|
||||
run(['javac', '--release', lock['minecraft']['java_major'], '-proc:none', '-cp', str(jars['server']) + os.pathsep + deps, '-d', classes, oracle], 'compile-java')
|
||||
run(['java', '-Xmx1G', '-cp', os.pathsep.join([str(classes), str(jars['server']), deps]), 'dev.horizonlab.parity.VanillaMobAttributesOracle', build / 'vanilla.txt'], 'java')
|
||||
run(['cargo', 'build', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe'], 'compile-rust')
|
||||
run(['cargo', 'build', '--profile', 'fast', '-p', 'horizon-client-lib', '--example', 'client_probe'], 'compile-rust')
|
||||
binary = lab.cargo_target() / 'fast/examples/client_probe'
|
||||
resources = lab.RESOURCES
|
||||
run([binary, 'mob_attributes_fixture', resources, build / 'rust.txt'], 'rust')
|
||||
|
||||
@@ -82,7 +82,7 @@ def main():
|
||||
dependencies = os.pathsep.join(map(str, libraries))
|
||||
run(['javac', '--release', lock['minecraft']['java_major'], '-proc:none', '-cp',
|
||||
str(jars['server'])+os.pathsep+dependencies, '-d', classes, oracle, *helpers], 'compile-java')
|
||||
run(['cargo', 'build', '--locked', '-p', 'horizon-client', '--example', 'client_probe'], 'compile-rust')
|
||||
run(['cargo', 'build', '--locked', '-p', 'horizon-client-lib', '--example', 'client_probe'], 'compile-rust')
|
||||
metadata = json.loads(subprocess.check_output(['cargo', 'metadata', '--locked', '--no-deps', '--format-version', '1'], cwd=lab.ROOT))
|
||||
executable = Path(metadata['target_directory'])/'debug/examples'/('client_probe.exe' if os.name=='nt' else 'client_probe')
|
||||
cases = []
|
||||
|
||||
@@ -807,7 +807,7 @@ def main():
|
||||
damage_dir=build/'damage';damage_dir.mkdir(exist_ok=True)
|
||||
for target, source in [('types.json','damage-types.json'),('tags.json','damage-tags.json')]:
|
||||
(damage_dir/target).write_bytes((resources/source).read_bytes())
|
||||
run(['cargo','build','--locked','-p','horizon-client','--example','client_probe'],'compile-rust')
|
||||
run(['cargo','build','--locked','-p','horizon-client-lib','--example','client_probe'],'compile-rust')
|
||||
metadata=json.loads(subprocess.check_output(['cargo','metadata','--locked','--no-deps','--format-version','1'],cwd=lab.ROOT))
|
||||
binary=Path(metadata['target_directory'])/'debug/examples'/('client_probe.exe' if os.name=='nt' else 'client_probe')
|
||||
run([binary,'scene_motion_probe',resources,build,'--describe'],'describe')
|
||||
|
||||
@@ -569,7 +569,7 @@ def main():
|
||||
'-cp', str(jars['server']) + os.pathsep + deps, '-d', classes, oracle], 'compile-java')
|
||||
run(['java', '-Xmx3G', '-cp', os.pathsep.join([str(classes), str(jars['server']), deps]),
|
||||
'dev.horizonlab.parity.VanillaNaturalSpawnOracle', inputs, build / 'server.txt'], 'server')
|
||||
run(['cargo', 'build', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe'], 'compile-rust')
|
||||
run(['cargo', 'build', '--profile', 'fast', '-p', 'horizon-client-lib', '--example', 'client_probe'], 'compile-rust')
|
||||
metadata = json.loads(subprocess.check_output(['cargo', 'metadata', '--no-deps', '--format-version', '1'], cwd=lab.ROOT, text=True))
|
||||
binary = Path(metadata['target_directory']) / 'fast/examples/client_probe'
|
||||
run([binary, 'natural_spawn_fixture', resources, inputs, build / 'rust.txt'], 'rust')
|
||||
|
||||
@@ -71,7 +71,7 @@ def main():
|
||||
*(PARITY / f'{n}.java' for n in ('ParityData', 'VanillaPackSelectionOracle'))], 'compile-java')
|
||||
run(['java', '-Xmx1G', '-cp', str(classes) + os.pathsep + dependencies, 'dev.horizonlab.parity.VanillaPackSelectionOracle',
|
||||
inputs, build / 'vanilla.jsonl'], 'vanilla')
|
||||
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe', '--',
|
||||
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client-lib', '--example', 'client_probe', '--',
|
||||
'pack_selection_fixture', inputs, build / 'rust.jsonl'], 'rust')
|
||||
vanilla = [json.loads(l) for l in (build / 'vanilla.jsonl').read_text().splitlines()]
|
||||
rust = [json.loads(l) for l in (build / 'rust.jsonl').read_text().splitlines()]
|
||||
|
||||
@@ -335,7 +335,7 @@ def main():
|
||||
'-cp', str(jars['server']) + os.pathsep + deps, '-d', classes, oracle], 'compile-java')
|
||||
run(['java', '-Xmx3G', '-cp', os.pathsep.join([str(classes), str(jars['server']), deps]),
|
||||
'dev.horizonlab.parity.VanillaRaidOracle', inputs, build / 'server.txt'], 'server')
|
||||
run(['cargo', 'build', '--profile', 'fast', '-p', 'horizon-client', '--example', 'client_probe'], 'compile-rust')
|
||||
run(['cargo', 'build', '--profile', 'fast', '-p', 'horizon-client-lib', '--example', 'client_probe'], 'compile-rust')
|
||||
metadata = json.loads(subprocess.check_output(['cargo', 'metadata', '--no-deps', '--format-version', '1'], cwd=lab.ROOT, text=True))
|
||||
binary = Path(metadata['target_directory']) / 'fast/examples/client_probe'
|
||||
run([binary, 'raid_fixture', resources, inputs, build / 'rust.txt'], 'rust')
|
||||
|
||||
@@ -80,7 +80,7 @@ def check_thrown(host, guest, out):
|
||||
|
||||
|
||||
def main():
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client',
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client', '-p', 'horizon-client-lib',
|
||||
'--bin', 'horizon-client', '--bin', 'horizon-ctl'], cwd=lab.ROOT, check=True)
|
||||
metadata = json.loads(subprocess.check_output(
|
||||
['cargo', 'metadata', '--format-version', '1', '--no-deps'], cwd=lab.ROOT, text=True))
|
||||
|
||||
@@ -11,7 +11,7 @@ import lan_e2e as lan
|
||||
|
||||
|
||||
def main():
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client',
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client', '-p', 'horizon-client-lib',
|
||||
'--bin', 'horizon-client', '--bin', 'horizon-ctl'], cwd=lab.ROOT, check=True)
|
||||
metadata = json.loads(subprocess.check_output(
|
||||
['cargo', 'metadata', '--format-version', '1', '--no-deps'], cwd=lab.ROOT, text=True))
|
||||
|
||||
@@ -44,7 +44,7 @@ def main():
|
||||
args = parser.parse_args()
|
||||
bin_dir = Path(args.bin_dir).resolve() if args.bin_dir else None
|
||||
if bin_dir is None:
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client', '--bins'], cwd=ROOT, check=True)
|
||||
subprocess.run(['cargo', 'build', '--locked', '--profile', 'fast', '-p', 'horizon-client', '-p', 'horizon-client-lib', '--bins'], cwd=ROOT, check=True)
|
||||
bin_dir = ROOT / os.environ.get('CARGO_TARGET_DIR', 'target') / 'fast'
|
||||
shutil.rmtree(RUN, ignore_errors=True)
|
||||
RUN.mkdir(parents=True)
|
||||
|
||||
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user