diff options
| author | Valentin Popov <valentin@popov.link> | 2026-07-18 11:16:45 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-07-18 11:16:45 +0300 |
| commit | bcf1ce99719bb455d47e716951a7d3943dd41b33 (patch) | |
| tree | 216092605602a87fb0a38b7dcad08f1ba4e6649a | |
| parent | 6b4fefc21f30300b07e6e6e99eedf3d81e855388 (diff) | |
| download | fparkan-bcf1ce99719bb455d47e716951a7d3943dd41b33.tar.xz fparkan-bcf1ce99719bb455d47e716951a7d3943dd41b33.zip | |
feat(runtime): trace visual dependency requests
| -rw-r--r-- | crates/fparkan-assets/src/lib.rs | 121 | ||||
| -rw-r--r-- | crates/fparkan-runtime/src/lib.rs | 17 | ||||
| -rw-r--r-- | docs/tomes/05-render.md | 15 |
3 files changed, 142 insertions, 11 deletions
diff --git a/crates/fparkan-assets/src/lib.rs b/crates/fparkan-assets/src/lib.rs index aeb91d9..ac64648 100644 --- a/crates/fparkan-assets/src/lib.rs +++ b/crates/fparkan-assets/src/lib.rs @@ -48,6 +48,7 @@ use std::sync::Arc; const TEXTURES_ARCHIVE: &str = "textures.lib"; const LIGHTMAP_ARCHIVE: &str = "lightmap.lib"; +const VISUAL_DEPENDENCY_PROGRESS_INTERVAL: usize = 64; /// Canonical terrain archive paths derived from a mission land reference. #[derive(Clone, Debug, Eq, PartialEq)] @@ -479,6 +480,15 @@ pub enum VisualDependencyPhase { Texture, } +/// Throttled visual-dependency expansion progress. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct VisualDependencyProgress { + /// Dependency class currently being expanded. + pub phase: VisualDependencyPhase, + /// Number of requests reached in this class during the current expansion. + pub request_count: usize, +} + /// Errors raised while preparing CPU-side assets. #[derive(Debug)] pub enum AssetError { @@ -1138,6 +1148,39 @@ pub fn extend_graph_report_with_visual_dependencies_and_progress<R: ResourceRepo prototypes: &[EffectivePrototype], mut on_phase: impl FnMut(VisualDependencyPhase), ) { + let mut last_phase = None; + extend_graph_report_with_visual_dependencies_with_progress( + repository, + report, + graph, + prototypes, + |progress| { + if last_phase != Some(progress.phase) { + on_phase(progress.phase); + last_phase = Some(progress.phase); + } + }, + ); +} + +/// Extends a prototype dependency report with throttled request progress. +/// +/// The callback receives each dependency-class entrance, its first request, +/// and every subsequent multiple of 64 requests. Graph traversal, edge order +/// and validation remain unchanged. +/// +/// # Panics +/// +/// Panics only if the hard-coded, host-compatible `material.lib` path stops +/// satisfying the path policy, which indicates a programming error in this module. +#[allow(clippy::too_many_lines)] +pub fn extend_graph_report_with_visual_dependencies_with_progress<R: ResourceRepository>( + repository: &R, + report: &mut PrototypeGraphReport, + graph: &mut PrototypeGraph, + prototypes: &[EffectivePrototype], + mut on_progress: impl FnMut(VisualDependencyProgress), +) { if graph.visual_dependencies_expanded { return; } @@ -1159,6 +1202,9 @@ pub fn extend_graph_report_with_visual_dependencies_and_progress<R: ResourceRepo let mut diffuse_texture_validation = HashMap::new(); let mut lightmap_texture_validation = HashMap::new(); let mut material_validation = HashMap::new(); + let mut wear_requests = 0usize; + let mut material_requests = 0usize; + let mut texture_requests = 0usize; for (prototype_index, prototype) in prototypes.iter().enumerate() { let PrototypeGeometry::Mesh(mesh) = &prototype.geometry else { @@ -1174,7 +1220,12 @@ pub fn extend_graph_report_with_visual_dependencies_and_progress<R: ResourceRepo let mesh_parent_edge = mesh_edge_id(graph, prototype_node_id); let root_index = root_index_for_prototype(graph, prototype_index); - on_phase(VisualDependencyPhase::Wear); + wear_requests = wear_requests.saturating_add(1); + report_visual_dependency_progress( + &mut on_progress, + VisualDependencyPhase::Wear, + wear_requests, + ); match resolve_wear_table(repository, mesh) { Ok(table) => { report.wear_resolved_count += 1; @@ -1213,8 +1264,17 @@ pub fn extend_graph_report_with_visual_dependencies_and_progress<R: ResourceRepo &mut next_edge, ); report.material_slot_count += table.entries.len(); - on_phase(VisualDependencyPhase::Material); + on_progress(VisualDependencyProgress { + phase: VisualDependencyPhase::Material, + request_count: material_requests, + }); for (material_index, _entry) in table.entries.iter().enumerate() { + material_requests = material_requests.saturating_add(1); + report_visual_dependency_progress( + &mut on_progress, + VisualDependencyPhase::Material, + material_requests, + ); let Ok(material_index) = u16::try_from(material_index) else { push_visual_failure( report, @@ -1260,7 +1320,12 @@ pub fn extend_graph_report_with_visual_dependencies_and_progress<R: ResourceRepo &mut next_edge, ); for texture in material.document.texture_requests() { - on_phase(VisualDependencyPhase::Texture); + texture_requests = texture_requests.saturating_add(1); + report_visual_dependency_progress( + &mut on_progress, + VisualDependencyPhase::Texture, + texture_requests, + ); report.texture_request_count += 1; match resolve_texm_validation_cached( repository, @@ -1321,7 +1386,12 @@ pub fn extend_graph_report_with_visual_dependencies_and_progress<R: ResourceRepo } } for lightmap in &table.lightmaps { - on_phase(VisualDependencyPhase::Texture); + texture_requests = texture_requests.saturating_add(1); + report_visual_dependency_progress( + &mut on_progress, + VisualDependencyPhase::Texture, + texture_requests, + ); report.lightmap_request_count += 1; match resolve_texm_validation_cached( repository, @@ -1384,6 +1454,19 @@ pub fn extend_graph_report_with_visual_dependencies_and_progress<R: ResourceRepo graph.visual_dependencies_expanded = true; } +fn report_visual_dependency_progress( + on_progress: &mut impl FnMut(VisualDependencyProgress), + phase: VisualDependencyPhase, + request_count: usize, +) { + if request_count == 1 || request_count.is_multiple_of(VISUAL_DEPENDENCY_PROGRESS_INTERVAL) { + on_progress(VisualDependencyProgress { + phase, + request_count, + }); + } +} + fn push_graph_resource_node( graph: &mut PrototypeGraph, kind: PrototypeGraphNodeKind, @@ -2201,6 +2284,36 @@ mod tests { use std::path::PathBuf; #[test] + fn visual_dependency_progress_is_throttled_at_fixed_request_intervals() { + let mut progress = Vec::new(); + for request_count in 1..=129 { + report_visual_dependency_progress( + &mut |event| progress.push(event), + VisualDependencyPhase::Texture, + request_count, + ); + } + + assert_eq!( + progress, + vec![ + VisualDependencyProgress { + phase: VisualDependencyPhase::Texture, + request_count: 1, + }, + VisualDependencyProgress { + phase: VisualDependencyPhase::Texture, + request_count: 64, + }, + VisualDependencyProgress { + phase: VisualDependencyPhase::Texture, + request_count: 128, + }, + ] + ); + } + + #[test] fn count_only_plan_uses_graph_requests() { let graph = PrototypeGraph::default(); diff --git a/crates/fparkan-runtime/src/lib.rs b/crates/fparkan-runtime/src/lib.rs index 154d588..8ecce74 100644 --- a/crates/fparkan-runtime/src/lib.rs +++ b/crates/fparkan-runtime/src/lib.rs @@ -22,7 +22,7 @@ use fparkan_assets::{ decode_mission_land_path, decode_mission_payload, decode_nres_payload, - derive_mission_land_paths, extend_graph_report_with_visual_dependencies_and_progress, + derive_mission_land_paths, extend_graph_report_with_visual_dependencies_with_progress, prepare_terrain_world, AssetError as AssetPreparationError, AssetId, AssetManager, AssetPreparationPhase, BuildCategory, MissionAssetPlan, MissionDocument, MissionError, MissionTerrainPaths, NresError, PreparedVisual, TerrainFormatError, TerrainPreparationError, @@ -127,6 +127,8 @@ pub enum MissionLoadPhase { GraphVisualMaterials, /// Validate TEXM documents while expanding visual dependencies. GraphVisualTextures, + /// Progress marker emitted after each 64 TEXM validation requests. + GraphVisualTextureRequests(usize), /// Prepare all reachable visual/resource dependencies. Assets, /// Decode and validate MSH model meshes. @@ -706,13 +708,13 @@ fn load_mission_with_options_and_progress( build_prototype_graph_report(&repository, vfs.as_ref(), scoped_graph_roots); record_load_phase(&mut trace, &mut on_phase, MissionLoadPhase::GraphVisuals); let mut last_graph_visual_phase = None; - extend_graph_report_with_visual_dependencies_and_progress( + extend_graph_report_with_visual_dependencies_with_progress( &repository, &mut prototype_report, &mut prototype_graph, &resolved_prototypes, - |phase| { - let runtime_phase = match phase { + |progress| { + let runtime_phase = match progress.phase { VisualDependencyPhase::Wear => MissionLoadPhase::GraphVisualWears, VisualDependencyPhase::Material => MissionLoadPhase::GraphVisualMaterials, VisualDependencyPhase::Texture => MissionLoadPhase::GraphVisualTextures, @@ -721,6 +723,13 @@ fn load_mission_with_options_and_progress( record_load_phase(&mut trace, &mut on_phase, runtime_phase); last_graph_visual_phase = Some(runtime_phase); } + if progress.phase == VisualDependencyPhase::Texture && progress.request_count > 1 { + record_load_phase( + &mut trace, + &mut on_phase, + MissionLoadPhase::GraphVisualTextureRequests(progress.request_count), + ); + } }, ); if !prototype_report.is_success() { diff --git a/docs/tomes/05-render.md b/docs/tomes/05-render.md index 9565f22..1acb056 100644 --- a/docs/tomes/05-render.md +++ b/docs/tomes/05-render.md @@ -1097,9 +1097,18 @@ separate profiling and compatibility target. `GraphVisuals` is now split observationally into `GraphVisualWears`, `GraphVisualMaterials` and `GraphVisualTextures`; it preserves the existing graph traversal, edge order and validation semantics. Repeating the controlled -180-second Part 2 probe ended at `GraphVisualTextures`. Thus Part 2 had passed -WEAR and MAT0 expansion before timeout; its remaining bottleneck is TEXM -validation/archive work, not Vulkan submission or the base prototype graph. +180-second Part 2 probe once ended at `GraphVisualTextures`. That particular +execution had passed WEAR and MAT0 expansion before timeout, but a checkpoint +is only the last phase reached in one run: it is not proof of a single global +bottleneck or of Vulkan involvement. + +Visual expansion now reports each dependency-class entrance, its first request +and every 64th later request; this is diagnostic-only and does not alter +traversal, edges or validation. A +later controlled 180-second Part 2 probe ended at `GraphVisualMaterials`, +before the first TEXM progress marker. The differing checkpoints rule out the +previous overly narrow claim that TEXM is the sole remaining target. Profiling +must compare WEAR, MAT0, TEXM, graph allocation and archive I/O quantitatively. Mission loading now raises the decoded-payload cache entry budget from 64 to 256 while retaining its 64 MiB byte budget. This avoids premature entry-count |
