perf(assets): reuse materials during mission preparation
This commit is contained in:
@@ -21,7 +21,8 @@
|
|||||||
//! Asset manager ports and transactional preparation models.
|
//! Asset manager ports and transactional preparation models.
|
||||||
|
|
||||||
use fparkan_material::{
|
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};
|
use fparkan_mission_format::{decode_tma, decode_tma_land_path};
|
||||||
pub use fparkan_mission_format::{LpString, MissionDocument, MissionError, TmaProfile};
|
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 textures = Vec::new();
|
||||||
let mut visuals = Vec::new();
|
let mut visuals = Vec::new();
|
||||||
let mut prototype_visual_ids = Vec::with_capacity(prototypes.len());
|
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 {
|
for proto in prototypes {
|
||||||
let visual_id = AssetId::new((identity_policy.visual)(proto));
|
let visual_id = AssetId::new((identity_policy.visual)(proto));
|
||||||
@@ -711,7 +712,7 @@ fn prepare_mission_assets_with_repository_internal<R: ResourceRepository>(
|
|||||||
repository,
|
repository,
|
||||||
proto,
|
proto,
|
||||||
identity_policy,
|
identity_policy,
|
||||||
&mut texture_cache,
|
&mut preparation_cache,
|
||||||
)?;
|
)?;
|
||||||
if bundle.visual.id != visual_id {
|
if bundle.visual.id != visual_id {
|
||||||
// Defensive check. stable IDs are deterministic for the same inputs.
|
// Defensive check. stable IDs are deterministic for the same inputs.
|
||||||
@@ -1385,12 +1386,12 @@ pub fn prepare_visual_with_repository<R: ResourceRepository>(
|
|||||||
repository: &R,
|
repository: &R,
|
||||||
proto: &EffectivePrototype,
|
proto: &EffectivePrototype,
|
||||||
) -> Result<PreparedVisual, AssetError> {
|
) -> Result<PreparedVisual, AssetError> {
|
||||||
let mut texture_cache = TexturePreparationCache::default();
|
let mut preparation_cache = PreparationCache::default();
|
||||||
Ok(prepare_visual_with_repository_internal(
|
Ok(prepare_visual_with_repository_internal(
|
||||||
repository,
|
repository,
|
||||||
proto,
|
proto,
|
||||||
AssetIdentityPolicy::default(),
|
AssetIdentityPolicy::default(),
|
||||||
&mut texture_cache,
|
&mut preparation_cache,
|
||||||
)?
|
)?
|
||||||
.visual)
|
.visual)
|
||||||
}
|
}
|
||||||
@@ -1404,9 +1405,10 @@ struct PreparedVisualBundle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct TexturePreparationCache {
|
struct PreparationCache {
|
||||||
diffuse: HashMap<Vec<u8>, PreparedTexture>,
|
diffuse: HashMap<Vec<u8>, PreparedTexture>,
|
||||||
lightmaps: 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.
|
// 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,
|
repository: &R,
|
||||||
proto: &EffectivePrototype,
|
proto: &EffectivePrototype,
|
||||||
identity_policy: AssetIdentityPolicy,
|
identity_policy: AssetIdentityPolicy,
|
||||||
texture_cache: &mut TexturePreparationCache,
|
preparation_cache: &mut PreparationCache,
|
||||||
) -> Result<PreparedVisualBundle, AssetError> {
|
) -> Result<PreparedVisualBundle, AssetError> {
|
||||||
let PrototypeGeometry::Mesh(mesh_key) = &proto.geometry else {
|
let PrototypeGeometry::Mesh(mesh_key) = &proto.geometry else {
|
||||||
return Ok(PreparedVisualBundle {
|
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())
|
AssetError::InvalidPrototype("material index does not fit archive format".to_string())
|
||||||
})?;
|
})?;
|
||||||
let material =
|
let material =
|
||||||
resolve_material(repository, &wear, material_index).map_err(AssetError::Material)?;
|
resolve_material_cached(repository, &wear, material_index, preparation_cache)?;
|
||||||
material_count += 1;
|
material_count += 1;
|
||||||
let material_id = AssetId::new((identity_policy.material)(
|
let material_id = AssetId::new((identity_policy.material)(
|
||||||
proto,
|
proto,
|
||||||
@@ -1504,7 +1506,7 @@ fn prepare_visual_with_repository_internal<R: ResourceRepository>(
|
|||||||
&texture,
|
&texture,
|
||||||
PreparedTextureUsage::Diffuse,
|
PreparedTextureUsage::Diffuse,
|
||||||
identity_policy,
|
identity_policy,
|
||||||
texture_cache,
|
preparation_cache,
|
||||||
)?;
|
)?;
|
||||||
texture_ids.push(prepared_texture.id);
|
texture_ids.push(prepared_texture.id);
|
||||||
prepared_textures.push(prepared_texture);
|
prepared_textures.push(prepared_texture);
|
||||||
@@ -1518,7 +1520,7 @@ fn prepare_visual_with_repository_internal<R: ResourceRepository>(
|
|||||||
&lightmap.lightmap,
|
&lightmap.lightmap,
|
||||||
PreparedTextureUsage::Lightmap,
|
PreparedTextureUsage::Lightmap,
|
||||||
identity_policy,
|
identity_policy,
|
||||||
texture_cache,
|
preparation_cache,
|
||||||
)?;
|
)?;
|
||||||
lightmap_ids.push(prepared_lightmap.id);
|
lightmap_ids.push(prepared_lightmap.id);
|
||||||
prepared_textures.push(prepared_lightmap);
|
prepared_textures.push(prepared_lightmap);
|
||||||
@@ -1808,7 +1810,7 @@ fn prepare_texture_cached<R: ResourceRepository>(
|
|||||||
name: &ResourceName,
|
name: &ResourceName,
|
||||||
usage: PreparedTextureUsage,
|
usage: PreparedTextureUsage,
|
||||||
identity_policy: AssetIdentityPolicy,
|
identity_policy: AssetIdentityPolicy,
|
||||||
cache: &mut TexturePreparationCache,
|
cache: &mut PreparationCache,
|
||||||
) -> Result<PreparedTexture, AssetError> {
|
) -> Result<PreparedTexture, AssetError> {
|
||||||
let entries = match usage {
|
let entries = match usage {
|
||||||
PreparedTextureUsage::Diffuse => &mut cache.diffuse,
|
PreparedTextureUsage::Diffuse => &mut cache.diffuse,
|
||||||
@@ -1822,6 +1824,31 @@ fn prepare_texture_cached<R: ResourceRepository>(
|
|||||||
Ok(texture)
|
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>(
|
fn resolve_texm<R: ResourceRepository>(
|
||||||
repository: &R,
|
repository: &R,
|
||||||
name: &ResourceName,
|
name: &ResourceName,
|
||||||
@@ -2475,7 +2502,7 @@ mod tests {
|
|||||||
}],
|
}],
|
||||||
)]);
|
)]);
|
||||||
let name = resource_name(b"TEX_A");
|
let name = resource_name(b"TEX_A");
|
||||||
let mut cache = TexturePreparationCache::default();
|
let mut cache = PreparationCache::default();
|
||||||
|
|
||||||
let first = prepare_texture_cached(
|
let first = prepare_texture_cached(
|
||||||
&repo,
|
&repo,
|
||||||
@@ -2499,6 +2526,28 @@ mod tests {
|
|||||||
assert!(cache.lightmaps.is_empty());
|
assert!(cache.lightmaps.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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]
|
#[test]
|
||||||
fn forced_model_id_collision_is_rejected() {
|
fn forced_model_id_collision_is_rejected() {
|
||||||
assert_forced_collision(
|
assert_forced_collision(
|
||||||
|
|||||||
Reference in New Issue
Block a user