mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Startup: keep the behaviour and registry snapshots across rebuilds
The snapshots in .horizon-cache keyed on the executable's modification time, so every rebuild of the game threw them away and the first start parsed facts.json and block-behaviours.json again. They now key on a hash of the code that resolves them (horizon-world and horizon-core sources, their manifests, Cargo.lock, profile, target and rustflags), computed by horizon-world's build.rs. Fixes #9522 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
63eccfc355
commit
56e06994d3
2 files changed
+80
-18
No files matched your search
@@ -0,0 +1,68 @@
|
||||
//! `HORIZON_WORLD_SOURCES`: a hash of the code that resolves the behaviour and
|
||||
//! block-registry snapshots (`src/level/data/cache.rs`). This crate's sources,
|
||||
//! `horizon-core`'s, both manifests, the workspace's `Cargo.lock` and the build
|
||||
//! profile and target. A rebuild that changes none of them (an edit in the
|
||||
//! renderer or in `game.rs`) keeps the snapshots; the executable's own
|
||||
//! modification time, the key before, threw them away on every build.
|
||||
|
||||
use std::{
|
||||
collections::hash_map::DefaultHasher,
|
||||
fs,
|
||||
hash::{Hash, Hasher},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let manifest = PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap());
|
||||
let core = manifest.join("../horizon-core");
|
||||
let lock = manifest.join("../../Cargo.lock");
|
||||
|
||||
let mut files = Vec::new();
|
||||
for dir in [manifest.join("src"), core.join("src")] {
|
||||
walk(&dir, &mut files);
|
||||
println!("cargo:rerun-if-changed={}", dir.display());
|
||||
}
|
||||
for file in [manifest.join("Cargo.toml"), core.join("Cargo.toml"), lock] {
|
||||
if file.is_file() {
|
||||
println!("cargo:rerun-if-changed={}", file.display());
|
||||
files.push(file);
|
||||
}
|
||||
}
|
||||
files.sort();
|
||||
|
||||
// Two differently seeded hashes: 128 bits, so two builds never share a key by chance.
|
||||
let mut hashers = [DefaultHasher::new(), DefaultHasher::new()];
|
||||
1u8.hash(&mut hashers[1]);
|
||||
for hasher in &mut hashers {
|
||||
for name in ["PROFILE", "TARGET", "OPT_LEVEL", "CARGO_ENCODED_RUSTFLAGS"] {
|
||||
std::env::var(name).unwrap_or_default().hash(hasher);
|
||||
}
|
||||
}
|
||||
for file in &files {
|
||||
let relative = file.strip_prefix(&manifest).unwrap_or(file);
|
||||
let bytes = fs::read(file).unwrap_or_default();
|
||||
for hasher in &mut hashers {
|
||||
relative.to_string_lossy().replace('\\', "/").hash(hasher);
|
||||
bytes.hash(hasher);
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"cargo:rustc-env=HORIZON_WORLD_SOURCES={:016x}{:016x}",
|
||||
hashers[0].finish(),
|
||||
hashers[1].finish()
|
||||
);
|
||||
}
|
||||
|
||||
fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
|
||||
let Ok(entries) = fs::read_dir(dir) else {
|
||||
return;
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
walk(&path, out);
|
||||
} else {
|
||||
out.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,10 @@
|
||||
//! versions, corrupt payloads and write failures always use the normal loader.
|
||||
//! Snapshots live in the game's own cache folder ([`set_dir`]), never in the
|
||||
//! resource folder: that one is shared by worktrees and copied between installed
|
||||
//! versions by the launcher. Its key includes the running executable, so a build
|
||||
//! that resolves the tables differently never reads another build's snapshot.
|
||||
//! versions by the launcher. Its key includes a hash of the code that resolves
|
||||
//! the tables (this crate, `horizon-core`, their dependencies; see `build.rs`),
|
||||
//! so a build that resolves them differently never reads another build's
|
||||
//! snapshot, while a rebuild that only touched other crates keeps them.
|
||||
use super::*;
|
||||
use crate::registry::snapshot as registry_snapshot;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -18,7 +20,7 @@ use std::{
|
||||
Mutex, OnceLock,
|
||||
atomic::{AtomicU32, Ordering},
|
||||
},
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
const MAGIC: &[u8; 8] = b"HZBEHAV\0";
|
||||
@@ -141,26 +143,18 @@ struct Item {
|
||||
game_master: bool,
|
||||
}
|
||||
|
||||
/// The running executable's stamp: the version constant alone misses loader
|
||||
/// changes that keep a snapshot's layout.
|
||||
fn hash_executable(hasher: &mut Sha256) -> io::Result<()> {
|
||||
let exe = std::env::current_exe()?;
|
||||
let built = fs::metadata(&exe)?;
|
||||
hash_str(hasher, &exe.to_string_lossy());
|
||||
hasher.update(built.len().to_le_bytes());
|
||||
let modified = built
|
||||
.modified()?
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(|e| invalid(e.to_string()))?;
|
||||
hasher.update(modified.as_nanos().to_le_bytes());
|
||||
Ok(())
|
||||
/// The build that resolves the tables: a hash of this crate's and
|
||||
/// `horizon-core`'s sources, `Cargo.lock` and the profile (`build.rs`). The
|
||||
/// version constant alone misses loader changes that keep a snapshot's layout.
|
||||
fn hash_build(hasher: &mut Sha256) {
|
||||
hash_str(hasher, env!("HORIZON_WORLD_SOURCES"));
|
||||
}
|
||||
|
||||
fn fingerprint(registry: &BlockRegistry, root: &Path) -> io::Result<[u8; 32]> {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"horizon resolved behaviours 2");
|
||||
// The build that resolved the tables.
|
||||
hash_executable(&mut hasher)?;
|
||||
hash_build(&mut hasher);
|
||||
hasher.update(sources_digest(root)?);
|
||||
// The registry argument may be modified independently of facts.json.
|
||||
hasher.update((registry.len() as u64).to_le_bytes());
|
||||
@@ -865,7 +859,7 @@ fn write(
|
||||
fn registry_fingerprint(root: &Path) -> io::Result<[u8; 32]> {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"horizon block registry 1");
|
||||
hash_executable(&mut hasher)?;
|
||||
hash_build(&mut hasher);
|
||||
hasher.update(paths_digest(root, &[root.join("facts.json")])?);
|
||||
Ok(hasher.finalize().into())
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user