feat(runtime): trace visual dependency requests
Docs Deploy / Build and Deploy MkDocs (push) Successful in 36s
Test / Lint (push) Failing after 3m0s
Test / Test (push) Skipped
Test / Render parity (push) Skipped

This commit is contained in:
2026-07-18 12:16:45 +04:00
parent 6b4fefc21f
commit bcf1ce9971
3 changed files with 142 additions and 11 deletions
+117 -4
View File
@@ -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 {
@@ -1137,6 +1147,39 @@ pub fn extend_graph_report_with_visual_dependencies_and_progress<R: ResourceRepo
graph: &mut PrototypeGraph,
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,
@@ -2200,6 +2283,36 @@ mod tests {
use fparkan_vfs::{DirectoryVfs, MemoryVfs, Vfs};
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();
+13 -4
View File
@@ -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() {
+12 -3
View File
@@ -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