Skip to content

Commit 17edf4f

Browse files
cartkillercup
andauthored
Copy on Write AssetPaths (#9729)
# Objective The `AssetServer` and `AssetProcessor` do a lot of `AssetPath` cloning (across many threads). To store the path on the handle, to store paths in dependency lists, to pass an owned path to the offloaded thread, to pass a path to the LoadContext, etc , etc. Cloning multiple string allocations multiple times like this will add up. It is worth optimizing this. Referenced in #9714 ## Solution Added a new `CowArc<T>` type to `bevy_util`, which behaves a lot like `Cow<T>`, but the Owned variant is an `Arc<T>`. Use this in place of `Cow<str>` and `Cow<Path>` on `AssetPath`. --- ## Changelog - `AssetPath` now internally uses `CowArc`, making clone operations much cheaper - `AssetPath` now serializes as `AssetPath("some_path.extension#Label")` instead of as `AssetPath { path: "some_path.extension", label: Some("Label) }` ## Migration Guide ```rust // Old AssetPath::new("logo.png", None); // New AssetPath::new("logo.png"); // Old AssetPath::new("scene.gltf", Some("Mesh0"); // New AssetPath::new("scene.gltf").with_label("Mesh0"); ``` `AssetPath` now serializes as `AssetPath("some_path.extension#Label")` instead of as `AssetPath { path: "some_path.extension", label: Some("Label) }` --------- Co-authored-by: Pascal Hertleif <[email protected]>
1 parent 1980ac8 commit 17edf4f

File tree

11 files changed

+400
-163
lines changed

11 files changed

+400
-163
lines changed

crates/bevy_asset/src/io/processor_gated.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ impl ProcessorGatedReader {
3636
) -> Result<RwLockReadGuardArc<()>, AssetReaderError> {
3737
let infos = self.processor_data.asset_infos.read().await;
3838
let info = infos
39-
.get(&AssetPath::new(path.to_owned(), None))
39+
.get(&AssetPath::from_path(path.to_path_buf()))
4040
.ok_or_else(|| AssetReaderError::NotFound(path.to_owned()))?;
4141
Ok(info.file_transaction_lock.read_arc().await)
4242
}

crates/bevy_asset/src/loader.rs

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
Asset, AssetLoadError, AssetServer, Assets, Handle, UntypedAssetId, UntypedHandle,
99
};
1010
use bevy_ecs::world::World;
11-
use bevy_utils::{BoxedFuture, HashMap, HashSet};
11+
use bevy_utils::{BoxedFuture, CowArc, HashMap, HashSet};
1212
use downcast_rs::{impl_downcast, Downcast};
1313
use futures_lite::AsyncReadExt;
1414
use ron::error::SpannedError;
@@ -143,7 +143,7 @@ pub struct LoadedAsset<A: Asset> {
143143
pub(crate) value: A,
144144
pub(crate) dependencies: HashSet<UntypedAssetId>,
145145
pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
146-
pub(crate) labeled_assets: HashMap<String, LabeledAsset>,
146+
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
147147
pub(crate) meta: Option<Box<dyn AssetMetaDyn>>,
148148
}
149149

@@ -175,7 +175,7 @@ pub struct ErasedLoadedAsset {
175175
pub(crate) value: Box<dyn AssetContainer>,
176176
pub(crate) dependencies: HashSet<UntypedAssetId>,
177177
pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
178-
pub(crate) labeled_assets: HashMap<String, LabeledAsset>,
178+
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
179179
pub(crate) meta: Option<Box<dyn AssetMetaDyn>>,
180180
}
181181

@@ -214,13 +214,16 @@ impl ErasedLoadedAsset {
214214
}
215215

216216
/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
217-
pub fn get_labeled(&self, label: &str) -> Option<&ErasedLoadedAsset> {
218-
self.labeled_assets.get(label).map(|a| &a.asset)
217+
pub fn get_labeled(
218+
&self,
219+
label: impl Into<CowArc<'static, str>>,
220+
) -> Option<&ErasedLoadedAsset> {
221+
self.labeled_assets.get(&label.into()).map(|a| &a.asset)
219222
}
220223

221224
/// Iterate over all labels for "labeled assets" in the loaded asset
222225
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
223-
self.labeled_assets.keys().map(|s| s.as_str())
226+
self.labeled_assets.keys().map(|s| &**s)
224227
}
225228
}
226229

@@ -269,7 +272,7 @@ pub struct LoadContext<'a> {
269272
dependencies: HashSet<UntypedAssetId>,
270273
/// Direct dependencies used by this loader.
271274
loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
272-
labeled_assets: HashMap<String, LabeledAsset>,
275+
labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
273276
}
274277

275278
impl<'a> LoadContext<'a> {
@@ -362,9 +365,10 @@ impl<'a> LoadContext<'a> {
362365
/// See [`AssetPath`] for more on labeled assets.
363366
pub fn add_loaded_labeled_asset<A: Asset>(
364367
&mut self,
365-
label: String,
368+
label: impl Into<CowArc<'static, str>>,
366369
loaded_asset: LoadedAsset<A>,
367370
) -> Handle<A> {
371+
let label = label.into();
368372
let loaded_asset: ErasedLoadedAsset = loaded_asset.into();
369373
let labeled_path = self.asset_path.with_label(label.clone());
370374
let handle = self
@@ -383,9 +387,9 @@ impl<'a> LoadContext<'a> {
383387
/// Returns `true` if an asset with the label `label` exists in this context.
384388
///
385389
/// See [`AssetPath`] for more on labeled assets.
386-
pub fn has_labeled_asset(&self, label: &str) -> bool {
387-
let path = self.asset_path.with_label(label);
388-
self.asset_server.get_handle_untyped(path).is_some()
390+
pub fn has_labeled_asset<'b>(&self, label: impl Into<CowArc<'b, str>>) -> bool {
391+
let path = self.asset_path.with_label(label.into());
392+
self.asset_server.get_handle_untyped(&path).is_some()
389393
}
390394

391395
/// "Finishes" this context by populating the final [`Asset`] value (and the erased [`AssetMeta`] value, if it exists).
@@ -406,7 +410,7 @@ impl<'a> LoadContext<'a> {
406410
}
407411

408412
/// Gets the source asset path for this load context.
409-
pub fn asset_path(&self) -> &AssetPath {
413+
pub fn asset_path(&self) -> &AssetPath<'static> {
410414
&self.asset_path
411415
}
412416

@@ -432,7 +436,7 @@ impl<'a> LoadContext<'a> {
432436
let mut bytes = Vec::new();
433437
reader.read_to_end(&mut bytes).await?;
434438
self.loader_dependencies
435-
.insert(AssetPath::new(path.to_owned(), None), hash);
439+
.insert(AssetPath::from_path(path.to_owned()), hash);
436440
Ok(bytes)
437441
}
438442

@@ -461,14 +465,12 @@ impl<'a> LoadContext<'a> {
461465
path: impl Into<AssetPath<'b>>,
462466
settings: impl Fn(&mut S) + Send + Sync + 'static,
463467
) -> Handle<A> {
464-
let path = path.into().to_owned();
468+
let path = path.into();
465469
let handle = if self.should_load_dependencies {
466470
self.asset_server.load_with_settings(path.clone(), settings)
467471
} else {
468-
self.asset_server.get_or_create_path_handle(
469-
path.clone(),
470-
Some(loader_settings_meta_transform(settings)),
471-
)
472+
self.asset_server
473+
.get_or_create_path_handle(path, Some(loader_settings_meta_transform(settings)))
472474
};
473475
self.dependencies.insert(handle.id().untyped());
474476
handle
@@ -477,8 +479,11 @@ impl<'a> LoadContext<'a> {
477479
/// Returns a handle to an asset of type `A` with the label `label`. This [`LoadContext`] must produce an asset of the
478480
/// given type and the given label or the dependencies of this asset will never be considered "fully loaded". However you
479481
/// can call this method before _or_ after adding the labeled asset.
480-
pub fn get_label_handle<A: Asset>(&mut self, label: &str) -> Handle<A> {
481-
let path = self.asset_path.with_label(label).to_owned();
482+
pub fn get_label_handle<'b, A: Asset>(
483+
&mut self,
484+
label: impl Into<CowArc<'b, str>>,
485+
) -> Handle<A> {
486+
let path = self.asset_path.with_label(label);
482487
let handle = self.asset_server.get_or_create_path_handle::<A>(path, None);
483488
self.dependencies.insert(handle.id().untyped());
484489
handle
@@ -498,36 +503,37 @@ impl<'a> LoadContext<'a> {
498503
&mut self,
499504
path: impl Into<AssetPath<'b>>,
500505
) -> Result<ErasedLoadedAsset, LoadDirectError> {
501-
let path = path.into();
506+
let path = path.into().into_owned();
502507
let to_error = |e: AssetLoadError| -> LoadDirectError {
503508
LoadDirectError {
504-
dependency: path.to_owned(),
509+
dependency: path.clone(),
505510
error: e,
506511
}
507512
};
508-
let (meta, loader, mut reader) = self
509-
.asset_server
510-
.get_meta_loader_and_reader(&path)
511-
.await
512-
.map_err(to_error)?;
513-
let loaded_asset = self
514-
.asset_server
515-
.load_with_meta_loader_and_reader(
516-
&path,
517-
meta,
518-
&*loader,
519-
&mut *reader,
520-
false,
521-
self.populate_hashes,
522-
)
523-
.await
524-
.map_err(to_error)?;
513+
let loaded_asset = {
514+
let (meta, loader, mut reader) = self
515+
.asset_server
516+
.get_meta_loader_and_reader(&path)
517+
.await
518+
.map_err(to_error)?;
519+
self.asset_server
520+
.load_with_meta_loader_and_reader(
521+
&path,
522+
meta,
523+
&*loader,
524+
&mut *reader,
525+
false,
526+
self.populate_hashes,
527+
)
528+
.await
529+
.map_err(to_error)?
530+
};
525531
let info = loaded_asset
526532
.meta
527533
.as_ref()
528534
.and_then(|m| m.processed_info().as_ref());
529535
let hash = info.map(|i| i.full_hash).unwrap_or(Default::default());
530-
self.loader_dependencies.insert(path.to_owned(), hash);
536+
self.loader_dependencies.insert(path, hash);
531537
Ok(loaded_asset)
532538
}
533539
}

0 commit comments

Comments
 (0)