perf(assets): reuse archives during mission loads

This commit is contained in:
2026-07-18 15:15:29 +04:00
parent 4007f6fcec
commit 733bfcf6fa
3 changed files with 65 additions and 8 deletions
+3 -3
View File
@@ -1863,7 +1863,7 @@ fn read_key<R: ResourceRepository>(
) -> Result<Arc<[u8]>, AssetError> {
let label = label.unwrap_or("asset");
let archive = repository
.open_archive(&key.archive)
.open_archive_unchanged(&key.archive)
.map_err(|err| map_resource_error(label, key, err))?;
let handle = repository
.find(archive, &key.name)
@@ -1928,7 +1928,7 @@ fn resolve_wear_table<R: ResourceRepository>(
mesh: &ResourceKey,
) -> Result<fparkan_material::WearTable, AssetError> {
let archive = repository
.open_archive(&mesh.archive)
.open_archive_unchanged(&mesh.archive)
.map_err(|err| map_resource_error("wear", mesh, err))?;
let wear_name = sibling_name(mesh, "wea")?;
let handle = repository
@@ -2271,7 +2271,7 @@ fn read_optional_key<R: ResourceRepository>(
key: &ResourceKey,
label: Option<&str>,
) -> Result<Option<Arc<[u8]>>, AssetError> {
let archive = match repository.open_archive(&key.archive) {
let archive = match repository.open_archive_unchanged(&key.archive) {
Ok(archive) => archive,
Err(ResourceError::MissingArchive { .. } | ResourceError::MissingEntry) => return Ok(None),
Err(err) => {
+45
View File
@@ -258,6 +258,21 @@ pub trait ResourceRepository {
/// Returns [`ResourceError`] when the archive is missing, unsupported, or
/// malformed.
fn open_archive(&self, path: &NormalizedPath) -> Result<ArchiveId, ResourceError>;
/// Opens an archive known to remain unchanged for the caller's bounded
/// loading transaction.
///
/// Implementations may reuse an already decoded archive without a second
/// content-fingerprint pass. Callers must use this only while their asset
/// source is immutable; the default preserves [`Self::open_archive`]'s
/// strict invalidation behavior.
///
/// # Errors
///
/// Returns [`ResourceError`] when the archive is missing, unsupported, or
/// malformed.
fn open_archive_unchanged(&self, path: &NormalizedPath) -> Result<ArchiveId, ResourceError> {
self.open_archive(path)
}
/// Finds entry.
///
/// # Errors
@@ -524,6 +539,19 @@ impl ResourceRepository for CachedResourceRepository {
}
}
fn open_archive_unchanged(&self, path: &NormalizedPath) -> Result<ArchiveId, ResourceError> {
let key = path.identity_bytes().to_vec();
let mut state = self.state.lock().map_err(|_| ResourceError::Poisoned)?;
if let Some(id) = state.paths.get(&key).copied() {
if state.archive(id)?.document.is_some() {
state.touch_archive(id)?;
return Ok(id);
}
}
drop(state);
self.open_archive(path)
}
fn find(
&self,
archive: ArchiveId,
@@ -1101,6 +1129,23 @@ mod tests {
assert_eq!(vfs.metadata_reads.load(Ordering::Relaxed), 1);
}
#[test]
fn unchanged_transaction_reuses_decoded_archive_without_rereading() {
let path = archive_path(b"archives/test.lib").expect("path");
let bytes = Arc::from(build_nres(&[("one", b"payload")]).into_boxed_slice());
let vfs = Arc::new(CountingVfs::new(bytes));
let repo = CachedResourceRepository::new(Arc::clone(&vfs) as Arc<dyn Vfs>);
let first = repo.open_archive_unchanged(&path).expect("first open");
let second = repo
.open_archive_unchanged(&path)
.expect("transactional cached open");
assert_eq!(first, second);
assert_eq!(vfs.reads.load(Ordering::Relaxed), 1);
assert_eq!(vfs.metadata_reads.load(Ordering::Relaxed), 1);
}
#[test]
fn concurrent_same_archive_open_reuses_archive_id() {
let path = archive_path(b"archives/test.lib").expect("path");