aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-07-18 05:35:20 +0300
committerValentin Popov <valentin@popov.link>2026-07-18 05:35:20 +0300
commit57d8b654e988c47a0fc738a763e494784679b314 (patch)
tree0aeef2f63eb30b3ef3a8d838722388fdf8b2ea2e /crates
parent3f14e1ad7d8c01bd99eb75580b54ac558e6c6465 (diff)
downloadfparkan-57d8b654e988c47a0fc738a763e494784679b314.tar.xz
fparkan-57d8b654e988c47a0fc738a763e494784679b314.zip
perf(assets): reuse materials during mission preparation
Diffstat (limited to 'crates')
-rw-r--r--crates/fparkan-assets/src/lib.rs73
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 {