Compare commits

...
Author SHA1 Message Date
Ludovic Marie 1e93f0a460 Merge remote-tracking branch 'origin/perf/recipes-read-once-2' into perf/sync-data-once 2026-11-02 05:27:56 +01:00
Ludovic MarieandClaude Opus 5.5 4c877c4c49 Launcher lock: horizon-world's new log dependency
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 05:27:27 +01:00
Ludovic MarieandClaude Opus 5.5 ed5e93d659 Server data: one read when a world data preload is running
A launch with --world starts the world data preload, which ends with
sync_data; the server thread then started its own "Server data preload"
of the same data, and adopt threw one of the two away. preload_sync_data
now leaves it to a preload still running.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 05:08:40 +01:00
Ludovic MarieandClaude Opus 5.5 916cca3b77 Recipes: the --world launch preload holds the recipe folder too
Its loot data and its server data now share one read, as the title's
world-screen preload does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 05:06:53 +01:00
Ludovic MarieandClaude Opus 5.5 4a4cfcd68f Recipes: the loot data and the server's sync share the recipe folder's read
json_folder moves to horizon-world so loot's load_dir reads through it; the
LAN recipe sync reads through it too. The server-data preload and
SyncData::load hold the recipe folder, so the loot item tables, the recipe
sync and the recipe book's index read each recipe file once, not three times.

Part of #1961.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 04:41:04 +01:00
11 changed files with 51 additions and 27 deletions

No files matched your search

Generated
+1
View File
@@ -1733,6 +1733,7 @@ dependencies = [
"flate2",
"horizon-core",
"libm",
"log",
"lz4_flex",
"postcard",
"serde",
+1 -1
View File
@@ -49,7 +49,7 @@ pub mod hud_overlays;
pub mod inventory_effects;
pub mod java_text;
pub mod jigsaw_block;
pub mod json_folder;
pub use horizon_world::json_folder;
pub mod level_loading;
pub mod light;
pub mod lod;
+7 -2
View File
@@ -111,9 +111,14 @@ impl Game<'_> {
pub(super) struct SyncPreload(std::thread::JoinHandle<std::result::Result<SyncData, String>>);
/// Start reading the server's data beside the world's preparation; `None`
/// when it is read already, or being read (`preload_world_data`).
/// when it is read already, or being read: by `sync_data` (the lock is
/// held), or soon by a world data preload still running, which ends with
/// it. A second read would be thrown away by `adopt`.
pub(super) fn preload_sync_data(root: &std::path::Path) -> Option<SyncPreload> {
if !SYNC.try_lock().is_ok_and(|sync| sync.is_none()) {
// PRELOAD before SYNC, never both: `preload_world_data` takes SYNC
// under PRELOAD.
if super::world_loading::preload_running() || !SYNC.try_lock().is_ok_and(|sync| sync.is_none())
{
return None;
}
let root = root.to_owned();
@@ -388,6 +388,10 @@ pub(super) fn preload_world_data(
// seed shares: about 140 ms off opening the world.
let _ = data.prepare_decoration("minecraft:normal", "minecraft:overworld");
}
// The loot data's item tables and the server's data both
// read the recipe folder: held here, it is read once.
let _recipe_files =
horizon_client::json_folder::read(&resources.join("data/minecraft/recipe"));
// The preload is the game's own data; the world's packs'
// tables are built when it opens (finish_open_world).
let _ = horizon_client::structure_blocks::loot_data(
@@ -459,6 +463,10 @@ pub(super) fn preload_launch_world(
let preset = horizon_client::save::WorldGen::preset(&world_root);
let _ = data.prepare_decoration(preset, "minecraft:overworld");
}
// As in `preload_world_data`: the loot data and the server's
// data share one read of the recipe folder.
let _recipe_files =
horizon_client::json_folder::read(&resources.join("data/minecraft/recipe"));
let _ = horizon_client::structure_blocks::loot_data(
&resources,
&horizon_world::loot::PackOverlay::NONE,
@@ -475,6 +483,15 @@ pub(super) fn preload_launch_world(
}
}
/// Whether the "World data preload" thread is still running: it reads the
/// server's data (`sync_data`) last, so nothing else needs to.
pub(super) fn preload_running() -> bool {
matches!(
&*PRELOAD.lock().unwrap_or_else(|e| e.into_inner()),
Preload::Started { thread, .. } if !thread.is_finished()
)
}
/// A world opens: what `preload_world_data` read is the world's from now on.
pub(super) fn world_data_used() {
*PRELOAD.lock().unwrap_or_else(|e| e.into_inner()) = Preload::Used;
+4
View File
@@ -208,6 +208,10 @@ impl SyncData {
resources: &Path,
overlay: &horizon_world::loot::PackOverlay,
) -> Result<Self, String> {
// The recipe book, the recipe sync and the advancements read the
// recipe folder: held here, they share one read of it.
let _recipe_files =
horizon_world::json_folder::read(&resources.join("data/minecraft/recipe"));
let builtin = Builtin::find(resources)
.and_then(|p| Builtin::load(&p).ok())
.ok_or("registries.json not found next to the resources")?;
+11 -12
View File
@@ -52,17 +52,16 @@ fn ingredient(v: &Value) -> Option<Ingredient> {
}
}
/// Every recipe file, by recipe id (the registry's order).
pub(super) fn recipes(dir: &Path, prefix: &str, out: &mut BTreeMap<String, Value>) {
for entry in std::fs::read_dir(dir).into_iter().flatten().flatten() {
let path = entry.path();
let name = entry.file_name().to_string_lossy().into_owned();
if path.is_dir() {
recipes(&path, &format!("{prefix}{name}/"), out);
} else if let Some(stem) = name.strip_suffix(".json")
&& let Ok(v) = serde_json::from_slice(&std::fs::read(&path).unwrap_or_default())
{
out.insert(format!("minecraft:{prefix}{stem}"), v);
/// Every recipe file, by recipe id (the registry's order): read through
/// `json_folder`, so a load holding the folder shares one read of it. A file
/// that can't be read or doesn't parse is left out.
pub(super) fn recipes(dir: &Path, out: &mut BTreeMap<String, Value>) {
let Ok(folder) = horizon_world::json_folder::read(dir) else {
return;
};
for file in &folder.files {
if let Ok(v) = serde_json::from_slice(file.bytes().unwrap_or_default()) {
out.insert(format!("minecraft:{}", file.name), v);
}
}
}
@@ -94,7 +93,7 @@ pub(super) fn network_recipes(
overlay: &horizon_world::loot::PackOverlay,
) -> (BTreeMap<String, Box<str>>, HashMap<String, i32>) {
let mut all = BTreeMap::new();
recipes(&resources.join("data/minecraft/recipe"), "", &mut all);
recipes(&resources.join("data/minecraft/recipe"), &mut all);
overlay_recipes(overlay, &mut all);
// The data's recipe filters, as the server's own tables apply them
// (#5910), once, before the ingredient normalization, so a swapped
+1
View File
@@ -8,6 +8,7 @@ description = "Horizon vanilla world data, NBT, regions and chunk storage"
[dependencies]
libm = "=0.2.16"
log = "0.4"
serde_json = { version = "1.0.151", features = ["raw_value"] }
serde = { version = "1", features = ["derive"] }
sha2 = "0.10"
+1
View File
@@ -77,6 +77,7 @@ pub mod item_use_catalog;
pub mod item_use_lifecycle;
pub mod item_use_start;
pub mod java_hash_map;
pub mod json_folder;
pub mod lazy_json;
pub mod level;
pub mod liquid_collision;
+7 -12
View File
@@ -635,20 +635,15 @@ fn load_dir(
prefix: &str,
out: &mut HashMap<String, Value>,
) -> io::Result<()> {
let Ok(entries) = fs::read_dir(dir) else {
// Through `json_folder`: a load holding the folder (the recipes, read by
// the item tables, the recipe book and the server's sync too) shares one
// read of it.
let Ok(folder) = crate::json_folder::read(dir) else {
return Ok(());
};
for entry in entries.flatten() {
let path = entry.path();
let Some(stem) = path.file_stem().and_then(|s| s.to_str()) else {
continue;
};
if path.is_dir() {
load_dir(&path, namespace, &format!("{prefix}{stem}/"), out)?;
} else if path.extension().is_some_and(|e| e == "json") {
let value = serde_json::from_slice(&fs::read(&path)?)?;
out.insert(format!("{namespace}:{prefix}{stem}"), value);
}
for file in &folder.files {
let value = serde_json::from_slice(file.bytes()?)?;
out.insert(format!("{namespace}:{prefix}{}", file.name), value);
}
Ok(())
}
+1
View File
@@ -2817,6 +2817,7 @@ dependencies = [
"flate2",
"horizon-core",
"libm",
"log",
"lz4_flex",
"postcard",
"serde",