aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-07-18 11:16:45 +0300
committerValentin Popov <valentin@popov.link>2026-07-18 11:16:45 +0300
commitbcf1ce99719bb455d47e716951a7d3943dd41b33 (patch)
tree216092605602a87fb0a38b7dcad08f1ba4e6649a
parent6b4fefc21f30300b07e6e6e99eedf3d81e855388 (diff)
downloadfparkan-bcf1ce99719bb455d47e716951a7d3943dd41b33.tar.xz
fparkan-bcf1ce99719bb455d47e716951a7d3943dd41b33.zip
feat(runtime): trace visual dependency requests
-rw-r--r--crates/fparkan-assets/src/lib.rs121
-rw-r--r--crates/fparkan-runtime/src/lib.rs17
-rw-r--r--docs/tomes/05-render.md15
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