From c55d835d351c276e5f53ee190d5608dc73627d7a Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Sat, 18 Jul 2026 11:15:43 +0400 Subject: feat(runtime): trace asset preparation phases --- crates/fparkan-assets/src/lib.rs | 63 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) (limited to 'crates/fparkan-assets/src') diff --git a/crates/fparkan-assets/src/lib.rs b/crates/fparkan-assets/src/lib.rs index 0bafcfa..423e6ec 100644 --- a/crates/fparkan-assets/src/lib.rs +++ b/crates/fparkan-assets/src/lib.rs @@ -455,6 +455,17 @@ pub struct AssetPreparationReport { pub texture_pixels: u64, } +/// Asset preparation checkpoint emitted while resolving visual dependencies. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AssetPreparationPhase { + /// Decode a model and its corresponding WEAR table. + ModelAndWear, + /// Resolve MAT0 material documents referenced by a WEAR table. + Materials, + /// Decode diffuse textures and baked lightmaps. + Textures, +} + /// Errors raised while preparing CPU-side assets. #[derive(Debug)] pub enum AssetError { @@ -571,6 +582,25 @@ impl AssetManager { prepare_mission_assets_with_repository(&self.repository, root_prototype_spans, prototypes) } + /// Builds mission assets and reports the active dependency class. + /// + /// # Errors + /// + /// Returns [`AssetError`] if any visual dependency is missing or malformed. + pub fn prepare_mission_assets_with_progress( + &self, + root_prototype_spans: &[std::ops::Range], + prototypes: &[EffectivePrototype], + on_phase: impl FnMut(AssetPreparationPhase), + ) -> Result { + prepare_mission_assets_with_repository_with_progress( + &self.repository, + root_prototype_spans, + prototypes, + on_phase, + ) + } + /// Builds mission assets together with a bounded preparation report. /// /// # Errors @@ -656,6 +686,28 @@ pub fn prepare_mission_assets_with_repository( .0) } +/// Builds mission assets and reports the active dependency class. +/// +/// # Errors +/// +/// Returns [`AssetError`] if any visual dependency is missing or malformed. +pub fn prepare_mission_assets_with_repository_with_progress( + repository: &R, + root_prototype_spans: &[std::ops::Range], + prototypes: &[EffectivePrototype], + mut on_phase: impl FnMut(AssetPreparationPhase), +) -> Result { + Ok(prepare_mission_assets_with_repository_internal( + repository, + root_prototype_spans, + prototypes, + AssetIdentityPolicy::default(), + AssetPreparationLimits::default(), + &mut on_phase, + )? + .0) +} + /// Builds mission assets while enforcing explicit preparation limits. /// /// # Errors @@ -668,12 +720,14 @@ pub fn prepare_mission_assets_profiled_with_repository( prototypes: &[EffectivePrototype], limits: AssetPreparationLimits, ) -> Result<(MissionAssets, AssetPreparationReport), AssetError> { + let mut ignore_phase = |_| {}; prepare_mission_assets_with_repository_internal( repository, root_prototype_spans, prototypes, AssetIdentityPolicy::default(), limits, + &mut ignore_phase, ) } @@ -686,6 +740,7 @@ fn prepare_mission_assets_with_repository_internal( prototypes: &[EffectivePrototype], identity_policy: AssetIdentityPolicy, limits: AssetPreparationLimits, + on_phase: &mut dyn FnMut(AssetPreparationPhase), ) -> Result<(MissionAssets, AssetPreparationReport), AssetError> { if prototypes.is_empty() { return Ok((MissionAssets::default(), AssetPreparationReport::default())); @@ -725,6 +780,7 @@ fn prepare_mission_assets_with_repository_internal( proto, identity_policy, &mut preparation_cache, + on_phase, )?; if bundle.visual.id != visual_id { // Defensive check. stable IDs are deterministic for the same inputs. @@ -1416,6 +1472,7 @@ pub fn prepare_visual_with_repository( proto, AssetIdentityPolicy::default(), &mut preparation_cache, + &mut |_| {}, )? .visual) } @@ -1444,6 +1501,7 @@ fn prepare_visual_with_repository_internal( proto: &EffectivePrototype, identity_policy: AssetIdentityPolicy, preparation_cache: &mut PreparationCache, + on_phase: &mut dyn FnMut(AssetPreparationPhase), ) -> Result { let PrototypeGeometry::Mesh(mesh_key) = &proto.geometry else { return Ok(PreparedVisualBundle { @@ -1455,6 +1513,7 @@ fn prepare_visual_with_repository_internal( }); }; + on_phase(AssetPreparationPhase::ModelAndWear); let model = prepare_model_cached(repository, mesh_key, preparation_cache)?; let model_id = AssetId::new((identity_policy.model)(proto)); let prepared_model = PreparedModel { @@ -1491,6 +1550,7 @@ fn prepare_visual_with_repository_internal( .iter() .map(|lightmap| lightmap.lightmap.clone()) .collect(); + on_phase(AssetPreparationPhase::Materials); for material_index in 0..wear.entries.len() { let material_index = u16::try_from(material_index).map_err(|_| { AssetError::InvalidPrototype("material index does not fit archive format".to_string()) @@ -1520,6 +1580,7 @@ fn prepare_visual_with_repository_internal( }); for texture in texture_requests { + on_phase(AssetPreparationPhase::Textures); let prepared_texture = prepare_texture_cached( repository, &texture, @@ -1534,6 +1595,7 @@ fn prepare_visual_with_repository_internal( } for lightmap in &wear.lightmaps { + on_phase(AssetPreparationPhase::Textures); let prepared_lightmap = prepare_texture_cached( repository, &lightmap.lightmap, @@ -2830,6 +2892,7 @@ mod tests { &prototypes, policy, AssetPreparationLimits::default(), + &mut |_| {}, ) .expect_err("collision should fail"); -- cgit v1.2.3