From 2e14b1d5dd99ecd577c96ea4aaacdd584872a338 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Sat, 18 Jul 2026 13:17:06 +0400 Subject: [PATCH] perf(assets): cache visual wear validation --- crates/fparkan-assets/src/lib.rs | 43 ++++++++++++++++++++++++++++++-- docs/tomes/05-render.md | 11 ++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/crates/fparkan-assets/src/lib.rs b/crates/fparkan-assets/src/lib.rs index d133574..11e324f 100644 --- a/crates/fparkan-assets/src/lib.rs +++ b/crates/fparkan-assets/src/lib.rs @@ -50,6 +50,9 @@ const TEXTURES_ARCHIVE: &str = "textures.lib"; const LIGHTMAP_ARCHIVE: &str = "lightmap.lib"; const VISUAL_DEPENDENCY_PROGRESS_INTERVAL: usize = 64; +type WearValidationCache = + HashMap<(NormalizedPath, Vec), Result>; + /// Canonical terrain archive paths derived from a mission land reference. #[derive(Clone, Debug, Eq, PartialEq)] pub struct MissionTerrainPaths { @@ -1202,6 +1205,7 @@ pub fn extend_graph_report_with_visual_dependencies_with_progress { report.wear_resolved_count += 1; let wear_key = match wear_resource_key(mesh) { @@ -1457,7 +1461,7 @@ pub fn extend_graph_report_with_visual_dependencies_with_progress( decode_wear(&bytes).map_err(AssetError::Material) } +fn resolve_wear_table_cached( + repository: &R, + mesh: &ResourceKey, + cache: &mut WearValidationCache, +) -> Result { + let wear_key = wear_resource_key(mesh).map_err(|error| error.to_string())?; + let cache_key = (wear_key.archive.clone(), wear_key.name.0.clone()); + if let Some(result) = cache.get(&cache_key) { + return result.clone(); + } + let result = resolve_wear_table(repository, mesh).map_err(|error| error.to_string()); + cache.insert(cache_key, result.clone()); + result +} + fn push_visual_failure( report: &mut PrototypeGraphReport, graph: &PrototypeGraph, @@ -2323,6 +2342,26 @@ mod tests { ); } + #[test] + fn wear_validation_cache_replays_archive_qualified_failure() { + let repository = repository_with_archives(&[]); + let mesh = ResourceKey { + archive: parse_path("static.rlb").expect("archive"), + name: resource_name(b"tree.msh"), + type_id: Some(0x4853_454D), + }; + let mut cache = WearValidationCache::new(); + cache.insert( + (mesh.archive.clone(), b"tree.wea".to_vec()), + Err("cached WEAR failure".to_string()), + ); + + assert_eq!( + resolve_wear_table_cached(&repository, &mesh, &mut cache), + Err("cached WEAR failure".to_string()) + ); + } + #[test] fn count_only_plan_uses_graph_requests() { let graph = PrototypeGraph::default(); diff --git a/docs/tomes/05-render.md b/docs/tomes/05-render.md index d6c9953..b647f19 100644 --- a/docs/tomes/05-render.md +++ b/docs/tomes/05-render.md @@ -1151,6 +1151,17 @@ the measured unfinished interval for this run. It still does not allocate that time among MAT0 decode, graph allocation, archive I/O, OS cache state, or the interleaved TEXM requests. +Visual expansion now also caches WEAR validation by the complete derived WEAR +archive/name key and replays both successes and failures. It still creates each +per-prototype WEAR edge, increments the same request counters, and reports the +same failure provenance. A unit test verifies an archive-qualified cached +failure is replayed without consulting the repository. In a matched bounded +Part 2 probe the first TEXM-64 marker occurred at 135,520 ms, versus 134,793 +ms in the preceding sample, and neither run reached `Assets`; therefore this +corpus does not evidence a startup advance from WEAR caching. The path is kept +as a correctness-preserving duplicate-resolve optimization, not a causal +performance claim. + A rebuilt executable then ran a controlled 180-second Part 2 `Autodemo.00` probe with the append-only trace. Before exact-child termination it recorded `GraphVisualTextureRequests(64)`, `GraphVisualMaterialRequests(100)`, and every