diff options
| author | Valentin Popov <valentin@popov.link> | 2026-07-18 05:35:20 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-07-18 05:35:20 +0300 |
| commit | 57d8b654e988c47a0fc738a763e494784679b314 (patch) | |
| tree | 0aeef2f63eb30b3ef3a8d838722388fdf8b2ea2e | |
| parent | 3f14e1ad7d8c01bd99eb75580b54ac558e6c6465 (diff) | |
| download | fparkan-57d8b654e988c47a0fc738a763e494784679b314.tar.xz fparkan-57d8b654e988c47a0fc738a763e494784679b314.zip | |
perf(assets): reuse materials during mission preparation
| -rw-r--r-- | crates/fparkan-assets/src/lib.rs | 73 |
1 files changed, 61 insertions, 12 deletions
diff --git a/crates/fparkan-assets/src/lib.rs b/crates/fparkan-assets/src/lib.rs index a5dbfd1..f2b820c 100644 --- a/crates/fparkan-assets/src/lib.rs +++ b/crates/fparkan-assets/src/lib.rs @@ -21,7 +21,8 @@ //! Asset manager ports and transactional preparation models. use fparkan_material::{ - decode_wear, resolve_material, Mat0Document, MaterialError, WearTable, MAT0_KIND, WEAR_KIND, + decode_wear, resolve_material, Mat0Document, MaterialError, ResolvedMaterial, WearTable, + MAT0_KIND, WEAR_KIND, }; use fparkan_mission_format::{decode_tma, decode_tma_land_path}; pub use fparkan_mission_format::{LpString, MissionDocument, MissionError, TmaProfile}; @@ -693,7 +694,7 @@ fn prepare_mission_assets_with_repository_internal<R: ResourceRepository>( let mut textures = Vec::new(); let mut visuals = Vec::new(); let mut prototype_visual_ids = Vec::with_capacity(prototypes.len()); - let mut texture_cache = TexturePreparationCache::default(); + let mut preparation_cache = PreparationCache::default(); for proto in prototypes { let visual_id = AssetId::new((identity_policy.visual)(proto)); @@ -711,7 +712,7 @@ fn prepare_mission_assets_with_repository_internal<R: ResourceRepository>( repository, proto, identity_policy, - &mut texture_cache, + &mut preparation_cache, )?; if bundle.visual.id != visual_id { // Defensive check. stable IDs are deterministic for the same inputs. @@ -1385,12 +1386,12 @@ pub fn prepare_visual_with_repository<R: ResourceRepository>( repository: &R, proto: &EffectivePrototype, ) -> Result<PreparedVisual, AssetError> { - let mut texture_cache = TexturePreparationCache::default(); + let mut preparation_cache = PreparationCache::default(); Ok(prepare_visual_with_repository_internal( repository, proto, AssetIdentityPolicy::default(), - &mut texture_cache, + &mut preparation_cache, )? .visual) } @@ -1404,9 +1405,10 @@ struct PreparedVisualBundle { } #[derive(Default)] -struct TexturePreparationCache { +struct PreparationCache { diffuse: HashMap<Vec<u8>, PreparedTexture>, lightmaps: HashMap<Vec<u8>, PreparedTexture>, + materials: HashMap<Vec<u8>, ResolvedMaterial>, } // Keeps the complete model -> WEAR -> material -> texture transaction in one fallible boundary. @@ -1415,7 +1417,7 @@ fn prepare_visual_with_repository_internal<R: ResourceRepository>( repository: &R, proto: &EffectivePrototype, identity_policy: AssetIdentityPolicy, - texture_cache: &mut TexturePreparationCache, + preparation_cache: &mut PreparationCache, ) -> Result<PreparedVisualBundle, AssetError> { let PrototypeGeometry::Mesh(mesh_key) = &proto.geometry else { return Ok(PreparedVisualBundle { @@ -1475,7 +1477,7 @@ fn prepare_visual_with_repository_internal<R: ResourceRepository>( AssetError::InvalidPrototype("material index does not fit archive format".to_string()) })?; let material = - resolve_material(repository, &wear, material_index).map_err(AssetError::Material)?; + resolve_material_cached(repository, &wear, material_index, preparation_cache)?; material_count += 1; let material_id = AssetId::new((identity_policy.material)( proto, @@ -1504,7 +1506,7 @@ fn prepare_visual_with_repository_internal<R: ResourceRepository>( &texture, PreparedTextureUsage::Diffuse, identity_policy, - texture_cache, + preparation_cache, )?; texture_ids.push(prepared_texture.id); prepared_textures.push(prepared_texture); @@ -1518,7 +1520,7 @@ fn prepare_visual_with_repository_internal<R: ResourceRepository>( &lightmap.lightmap, PreparedTextureUsage::Lightmap, identity_policy, - texture_cache, + preparation_cache, )?; lightmap_ids.push(prepared_lightmap.id); prepared_textures.push(prepared_lightmap); @@ -1808,7 +1810,7 @@ fn prepare_texture_cached<R: ResourceRepository>( name: &ResourceName, usage: PreparedTextureUsage, identity_policy: AssetIdentityPolicy, - cache: &mut TexturePreparationCache, + cache: &mut PreparationCache, ) -> Result<PreparedTexture, AssetError> { let entries = match usage { PreparedTextureUsage::Diffuse => &mut cache.diffuse, @@ -1822,6 +1824,31 @@ fn prepare_texture_cached<R: ResourceRepository>( Ok(texture) } +fn resolve_material_cached<R: ResourceRepository>( + repository: &R, + table: &WearTable, + index: u16, + cache: &mut PreparationCache, +) -> Result<ResolvedMaterial, AssetError> { + let request = table + .entries + .get(usize::from(index)) + .ok_or(MaterialError::WearIndexOutOfBounds { + index, + count: table.entries.len(), + }) + .map_err(AssetError::Material)? + .material + .0 + .clone(); + if let Some(material) = cache.materials.get(&request) { + return Ok(material.clone()); + } + let material = resolve_material(repository, table, index).map_err(AssetError::Material)?; + cache.materials.insert(request, material.clone()); + Ok(material) +} + fn resolve_texm<R: ResourceRepository>( repository: &R, name: &ResourceName, @@ -2475,7 +2502,7 @@ mod tests { }], )]); let name = resource_name(b"TEX_A"); - let mut cache = TexturePreparationCache::default(); + let mut cache = PreparationCache::default(); let first = prepare_texture_cached( &repo, @@ -2500,6 +2527,28 @@ mod tests { } #[test] + fn material_preparation_cache_preserves_exact_wear_request() { + let mat0 = mat0_with_texture(b"TEX_A"); + let repo = repository_with_archives_meta(&[( + "material.lib", + &[TestNresEntry { + name: b"MAT_A", + payload: &mat0, + type_id: MAT0_KIND, + attr2: 0, + }], + )]); + let wear = decode_wear(b"1\n0 MAT_A\n").expect("wear"); + let mut cache = PreparationCache::default(); + + let first = resolve_material_cached(&repo, &wear, 0, &mut cache).expect("first material"); + let second = resolve_material_cached(&repo, &wear, 0, &mut cache).expect("cached material"); + + assert_eq!(first, second); + assert_eq!(cache.materials.len(), 1); + } + + #[test] fn forced_model_id_collision_is_rejected() { assert_forced_collision( AssetIdentityPolicy { |
