1use crate::{
2 io::{AssetReaderError, MissingAssetSourceError, MissingProcessedAssetReaderError, Reader},
3loader_builders::NestedLoadBuilder,
4 meta::{AssetHash, AssetMeta, AssetMetaDyn, ProcessedInfo, ProcessedInfoMinimal, Settings},
5path::AssetPath,
6Asset, AssetIndex, AssetLoadError, AssetServer, AssetServerMode, Assets, ErasedAssetIndex,
7Handle, UntypedAssetId, UntypedHandle,
8};
9use alloc::{boxed::Box, string::ToString, vec::Vec};
10use atomicow::CowArc;
11use bevy_ecs::{error::BevyError, world::World};
12use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet};
13use bevy_reflect::TypePath;
14use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
15use core::{
16 any::{Any, TypeId},
17convert::Infallible,
18};
19use downcast_rs::{impl_downcast, Downcast};
20use ron::error::SpannedError;
21use serde::{Deserialize, Serialize};
22use std::path::{Path, PathBuf};
23use thiserror::Error;
24use tracing::error;
2526/// Loads an [`Asset`] from a given byte [`Reader`]. This can accept [`AssetLoader::Settings`], which configure how the [`Asset`]
27/// should be loaded.
28///
29/// This trait is generally used in concert with [`AssetReader`](crate::io::AssetReader) to load assets from a byte source.
30///
31/// For a complementary version of this trait that can save assets, see [`AssetSaver`](crate::saver::AssetSaver).
32pub trait AssetLoader: TypePath + Send + Sync + 'static {
33/// The top level [`Asset`] loaded by this [`AssetLoader`].
34type Asset: Asset;
35/// The settings type used by this [`AssetLoader`].
36type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
37/// The type of [error](`std::error::Error`) which could be encountered by this loader.
38type Error: Into<BevyError>;
39/// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`].
40fn load(
41&self,
42 reader: &mut dyn Reader,
43 settings: &Self::Settings,
44 load_context: &mut LoadContext,
45 ) -> impl ConditionalSendFuture<Output = Result<Self::Asset, Self::Error>>;
4647/// Returns a list of extensions supported by this [`AssetLoader`], without the preceding dot.
48 /// Note that users of this [`AssetLoader`] may choose to load files with a non-matching extension.
49fn extensions(&self) -> &[&str] {
50&[]
51 }
52}
5354/// Provides type-erased access to an [`AssetLoader`].
55pub trait ErasedAssetLoader: Send + Sync + 'static {
56/// Asynchronously loads the asset(s) from the bytes provided by [`Reader`].
57fn load<'a>(
58&'a self,
59 reader: &'a mut dyn Reader,
60 settings: &'a dyn Settings,
61 load_context: LoadContext<'a>,
62 ) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>>;
6364/// Returns a list of extensions supported by this asset loader, without the preceding dot.
65fn extensions(&self) -> &[&str];
66/// Deserializes metadata from the input `meta` bytes into the appropriate type (erased as [`Box<dyn AssetMetaDyn>`]).
67fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
68/// Returns the default meta value for the [`AssetLoader`] (erased as [`Box<dyn AssetMetaDyn>`]).
69fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
70/// Returns the type path of the [`AssetLoader`].
71fn type_path(&self) -> &'static str;
72/// Returns the [`TypeId`] of the [`AssetLoader`].
73fn type_id(&self) -> TypeId;
74/// Returns the type name of the top-level [`Asset`] loaded by the [`AssetLoader`].
75fn asset_type_name(&self) -> &'static str;
76/// Returns the [`TypeId`] of the top-level [`Asset`] loaded by the [`AssetLoader`].
77fn asset_type_id(&self) -> TypeId;
78}
7980impl<L> ErasedAssetLoaderfor L
81where
82L: AssetLoader + Send + Sync,
83{
84/// Processes the asset in an asynchronous closure.
85fn load<'a>(
86&'a self,
87 reader: &'a mut dyn Reader,
88 settings: &'a dyn Settings,
89mut load_context: LoadContext<'a>,
90 ) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>> {
91Box::pin(async move {
92let settings = settings93 .downcast_ref::<L::Settings>()
94 .expect("AssetLoader settings should match the loader type");
95let asset = <L as AssetLoader>::load(self, reader, settings, &mut load_context)
96 .await
97.map_err(Into::into)?;
98Ok(load_context.finish(asset).into())
99 })
100 }
101102fn extensions(&self) -> &[&str] {
103 <L as AssetLoader>::extensions(self)
104 }
105106fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
107let meta = AssetMeta::<L, ()>::deserialize(meta)?;
108Ok(Box::new(meta))
109 }
110111fn default_meta(&self) -> Box<dyn AssetMetaDyn> {
112Box::new(AssetMeta::<L, ()>::new(crate::meta::AssetAction::Load {
113 loader: self.type_path().to_string(),
114 settings: L::Settings::default(),
115 }))
116 }
117118fn type_path(&self) -> &'static str {
119 L::type_path()
120 }
121122fn type_id(&self) -> TypeId {
123TypeId::of::<L>()
124 }
125126fn asset_type_name(&self) -> &'static str {
127 core::any::type_name::<L::Asset>()
128 }
129130fn asset_type_id(&self) -> TypeId {
131TypeId::of::<L::Asset>()
132 }
133}
134135pub(crate) struct LabeledAsset {
136pub(crate) asset: ErasedLoadedAsset,
137pub(crate) handle: UntypedHandle,
138}
139140/// The successful result of an [`AssetLoader::load`] call. This contains the loaded "root" asset and any other "labeled" assets produced
141/// by the loader. It also holds the input [`AssetMeta`] (if it exists) and tracks dependencies:
142/// * normal dependencies: dependencies that must be loaded as part of this asset load (ex: assets a given asset has handles to).
143/// * Loader dependencies: dependencies whose actual asset values are used during the load process
144pub struct LoadedAsset<A: Asset> {
145pub(crate) value: A,
146pub(crate) dependencies: HashSet<ErasedAssetIndex>,
147pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
148/// The subassets of this asset.
149pub(crate) labeled_assets: Vec<LabeledAsset>,
150/// The mapping from subasset labels to their index in [`Self::labeled_assets`].
151pub(crate) label_to_asset_index: HashMap<CowArc<'static, str>, usize>,
152/// The mapping from a subasset asset IDs to their index in [`Self::labeled_assets`].
153 ///
154 /// This is entirely redundant with [`Self::labeled_assets`], but it allows looking up the
155 /// labeled asset by its asset ID.
156pub(crate) asset_id_to_asset_index: HashMap<UntypedAssetId, usize>,
157}
158159impl<A: Asset> LoadedAsset<A> {
160/// Create a new loaded asset. This will use [`VisitAssetDependencies`](crate::VisitAssetDependencies) to populate `dependencies`.
161pub fn new_with_dependencies(value: A) -> Self {
162let mut dependencies = <HashSet<_>>::default();
163value.visit_dependencies(&mut |id| {
164let Ok(asset_index) = id.try_into() else {
165return;
166 };
167dependencies.insert(asset_index);
168 });
169LoadedAsset {
170value,
171dependencies,
172 loader_dependencies: HashMap::default(),
173 labeled_assets: Default::default(),
174 label_to_asset_index: Default::default(),
175 asset_id_to_asset_index: Default::default(),
176 }
177 }
178179/// Cast (and take ownership) of the [`Asset`] value of the given type.
180pub fn take(self) -> A {
181self.value
182 }
183184/// Retrieves a reference to the internal [`Asset`] type.
185pub fn get(&self) -> &A {
186&self.value
187 }
188189/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
190pub fn get_labeled(&self, label: impl AsRef<str>) -> Option<&ErasedLoadedAsset> {
191self.label_to_asset_index
192 .get(label.as_ref())
193 .map(|index| self.labeled_assets.get(*index).unwrap())
194 .map(|a| &a.asset)
195 }
196197/// Returns the labeled asset given its asset ID if it exists.
198 ///
199 /// This can be used to get the asset from its handle since `&Handle` implements
200 /// [`Into<UntypedAssetId>`].
201pub fn get_labeled_by_id(&self, id: impl Into<UntypedAssetId>) -> Option<&ErasedLoadedAsset> {
202let index = self.asset_id_to_asset_index.get(&id.into())?;
203let labeled = &self.labeled_assets[*index];
204Some(&labeled.asset)
205 }
206207/// Iterate over all labels for "labeled assets" in the loaded asset
208pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
209self.label_to_asset_index.keys().map(|s| &**s)
210 }
211}
212213impl<A: Asset> From<A> for LoadedAsset<A> {
214fn from(asset: A) -> Self {
215LoadedAsset::new_with_dependencies(asset)
216 }
217}
218219/// A "type erased / boxed" counterpart to [`LoadedAsset`]. This is used in places where the loaded type is not statically known.
220pub struct ErasedLoadedAsset {
221pub(crate) value: Box<dyn AssetContainer>,
222pub(crate) dependencies: HashSet<ErasedAssetIndex>,
223pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
224/// The subassets of this asset.
225pub(crate) labeled_assets: Vec<LabeledAsset>,
226/// The mapping from subasset labels to their index in [`Self::labeled_assets`].
227pub(crate) label_to_asset_index: HashMap<CowArc<'static, str>, usize>,
228/// The mapping from subasset asset IDs to their index in [`Self::labeled_assets`].
229 ///
230 /// This is entirely redundant with [`Self::labeled_assets`], but it allows looking up the
231 /// labeled asset by its asset ID.
232pub(crate) asset_id_to_asset_index: HashMap<UntypedAssetId, usize>,
233}
234235impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset {
236fn from(asset: LoadedAsset<A>) -> Self {
237ErasedLoadedAsset {
238 value: Box::new(asset.value),
239 dependencies: asset.dependencies,
240 loader_dependencies: asset.loader_dependencies,
241 labeled_assets: asset.labeled_assets,
242 label_to_asset_index: asset.label_to_asset_index,
243 asset_id_to_asset_index: asset.asset_id_to_asset_index,
244 }
245 }
246}
247248impl ErasedLoadedAsset {
249/// Cast (and take ownership) of the [`Asset`] value of the given type. This will return [`Some`] if
250 /// the stored type matches `A` and [`None`] if it does not.
251pub fn take<A: Asset>(self) -> Option<A> {
252self.value.downcast::<A>().map(|a| *a).ok()
253 }
254255/// Retrieves a reference to the internal [`Asset`] type, if it matches the type `A`. Otherwise returns [`None`].
256pub fn get<A: Asset>(&self) -> Option<&A> {
257self.value.downcast_ref::<A>()
258 }
259260/// Retrieves the [`TypeId`] of the stored [`Asset`] type.
261pub fn asset_type_id(&self) -> TypeId {
262 (*self.value).type_id()
263 }
264265/// Retrieves the `type_name` of the stored [`Asset`] type.
266pub fn asset_type_name(&self) -> &'static str {
267self.value.asset_type_name()
268 }
269270/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
271pub fn get_labeled(&self, label: impl AsRef<str>) -> Option<&ErasedLoadedAsset> {
272self.label_to_asset_index
273 .get(label.as_ref())
274 .map(|index| self.labeled_assets.get(*index).unwrap())
275 .map(|a| &a.asset)
276 }
277278/// Returns the labeled asset given its asset ID if it exists.
279 ///
280 /// This can be used to get the asset from its handle since `&Handle` implements
281 /// [`Into<UntypedAssetId>`].
282pub fn get_labeled_by_id(&self, id: impl Into<UntypedAssetId>) -> Option<&ErasedLoadedAsset> {
283let index = self.asset_id_to_asset_index.get(&id.into())?;
284let labeled = &self.labeled_assets[*index];
285Some(&labeled.asset)
286 }
287288/// Iterate over all labels for "labeled assets" in the loaded asset
289pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
290self.label_to_asset_index.keys().map(|s| &**s)
291 }
292293/// Cast this loaded asset as the given type. If the type does not match,
294 /// the original type-erased asset is returned.
295#[cfg_attr(
296 not(target_arch = "wasm32"),
297 expect(
298 clippy::result_large_err,
299 reason = "Returning the passed in ErasedLoadedAsset"
300)
301 )]
302pub fn downcast<A: Asset>(mut self) -> Result<LoadedAsset<A>, ErasedLoadedAsset> {
303match self.value.downcast::<A>() {
304Ok(value) => Ok(LoadedAsset {
305 value: *value,
306 dependencies: self.dependencies,
307 loader_dependencies: self.loader_dependencies,
308 labeled_assets: self.labeled_assets,
309 label_to_asset_index: self.label_to_asset_index,
310 asset_id_to_asset_index: self.asset_id_to_asset_index,
311 }),
312Err(value) => {
313self.value = value;
314Err(self)
315 }
316 }
317 }
318}
319320/// A type erased container for an [`Asset`] value that is capable of inserting the [`Asset`] into a [`World`]'s [`Assets`] collection.
321pub(crate) trait AssetContainer: Downcast + Any + Send + Sync + 'static {
322fn insert(self: Box<Self>, id: AssetIndex, world: &mut World);
323fn asset_type_name(&self) -> &'static str;
324}
325326impl dyn AssetContainer<> {
/// Returns true if the trait object wraps an object of type `__T`.
#[inline]
pub fn is<__T: AssetContainer<>>(&self) -> bool {
::downcast_rs::Downcast::as_any(self).is::<__T>()
}
/// Returns a boxed object from a boxed trait object if the underlying object is of type
/// `__T`. Returns the original boxed trait if it isn't.
#[inline]
pub fn downcast<__T: AssetContainer<>>(self:
::downcast_rs::__alloc::boxed::Box<Self>)
->
::downcast_rs::__std::result::Result<::downcast_rs::__alloc::boxed::Box<__T>,
::downcast_rs::__alloc::boxed::Box<Self>> {
if self.is::<__T>() {
Ok(::downcast_rs::Downcast::into_any(self).downcast::<__T>().unwrap())
} else { Err(self) }
}
/// Returns an `Rc`-ed object from an `Rc`-ed trait object if the underlying object is of
/// type `__T`. Returns the original `Rc`-ed trait if it isn't.
#[inline]
pub fn downcast_rc<__T: AssetContainer<>>(self:
::downcast_rs::__alloc::rc::Rc<Self>)
->
::downcast_rs::__std::result::Result<::downcast_rs::__alloc::rc::Rc<__T>,
::downcast_rs::__alloc::rc::Rc<Self>> {
if self.is::<__T>() {
Ok(::downcast_rs::Downcast::into_any_rc(self).downcast::<__T>().unwrap())
} else { Err(self) }
}
/// Returns a reference to the object within the trait object if it is of type `__T`, or
/// `None` if it isn't.
#[inline]
pub fn downcast_ref<__T: AssetContainer<>>(&self)
-> ::downcast_rs::__std::option::Option<&__T> {
::downcast_rs::Downcast::as_any(self).downcast_ref::<__T>()
}
/// Returns a mutable reference to the object within the trait object if it is of type
/// `__T`, or `None` if it isn't.
#[inline]
pub fn downcast_mut<__T: AssetContainer<>>(&mut self)
-> ::downcast_rs::__std::option::Option<&mut __T> {
::downcast_rs::Downcast::as_any_mut(self).downcast_mut::<__T>()
}
}impl_downcast!(AssetContainer);
327328impl<A: Asset> AssetContainerfor A {
329fn insert(self: Box<Self>, index: AssetIndex, world: &mut World) {
330// We only ever call this if we know the asset is still alive, so it is fine to unwrap here.
331world332 .resource_mut::<Assets<A>>()
333 .insert(index, *self)
334 .expect("the AssetIndex is still valid");
335 }
336337fn asset_type_name(&self) -> &'static str {
338 core::any::type_name::<A>()
339 }
340}
341342/// An error that occurs when attempting an async load using [`NestedLoadBuilder`].
343#[derive(#[allow(unused_qualifications)]
#[automatically_derived]
impl ::core::fmt::Display for LoadDirectError {
fn fmt(&self, __formatter: &mut ::core::fmt::Formatter)
-> ::core::fmt::Result {
use ::thiserror::__private18::AsDisplay as _;
#[allow(unused_variables, deprecated, clippy ::
used_underscore_binding)]
match self {
LoadDirectError::EmptyPath(_0) =>
match (_0.as_display(),) {
(__display0,) =>
__formatter.write_fmt(format_args!("Attempted to load an asset with an empty path \"{0}\"",
__display0)),
},
LoadDirectError::RequestedSubasset(_0) =>
match (_0,) {
(__field0,) =>
__formatter.write_fmt(format_args!("Requested to load an asset path ({0:?}) with a subasset, but this is unsupported. See issue #18291",
__field0)),
},
LoadDirectError::LoadError { dependency, error } =>
match (dependency, error.as_display()) {
(__field_dependency, __display_error) =>
__formatter.write_fmt(format_args!("Failed to load dependency {0:?} {1}",
__field_dependency, __display_error)),
},
}
}
}Error, #[automatically_derived]
impl ::core::fmt::Debug for LoadDirectError {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
LoadDirectError::EmptyPath(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"EmptyPath", &__self_0),
LoadDirectError::RequestedSubasset(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"RequestedSubasset", &__self_0),
LoadDirectError::LoadError { dependency: __self_0, error: __self_1
} =>
::core::fmt::Formatter::debug_struct_field2_finish(f,
"LoadError", "dependency", __self_0, "error", &__self_1),
}
}
}Debug)]
344pub enum LoadDirectError {
345#[error("Attempted to load an asset with an empty path \"{0}\"")]
346EmptyPath(AssetPath<'static>),
347#[error("Requested to load an asset path ({0:?}) with a subasset, but this is unsupported. See issue #18291")]
348RequestedSubasset(AssetPath<'static>),
349#[error("Failed to load dependency {dependency:?} {error}")]
350LoadError {
351 dependency: AssetPath<'static>,
352 error: AssetLoadError,
353 },
354}
355356/// An error that occurs while deserializing [`AssetMeta`].
357#[derive(fn from(source: SpannedError) -> Self {
DeserializeMetaError::DeserializeSettings { 0: source }
}Error, #[automatically_derived]
impl ::core::fmt::Debug for DeserializeMetaError {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
DeserializeMetaError::DeserializeSettings(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"DeserializeSettings", &__self_0),
DeserializeMetaError::DeserializeMinimal(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"DeserializeMinimal", &__self_0),
}
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for DeserializeMetaError {
#[inline]
fn clone(&self) -> DeserializeMetaError {
match self {
DeserializeMetaError::DeserializeSettings(__self_0) =>
DeserializeMetaError::DeserializeSettings(::core::clone::Clone::clone(__self_0)),
DeserializeMetaError::DeserializeMinimal(__self_0) =>
DeserializeMetaError::DeserializeMinimal(::core::clone::Clone::clone(__self_0)),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for DeserializeMetaError {
#[inline]
fn eq(&self, other: &DeserializeMetaError) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr &&
match (self, other) {
(DeserializeMetaError::DeserializeSettings(__self_0),
DeserializeMetaError::DeserializeSettings(__arg1_0)) =>
__self_0 == __arg1_0,
(DeserializeMetaError::DeserializeMinimal(__self_0),
DeserializeMetaError::DeserializeMinimal(__arg1_0)) =>
__self_0 == __arg1_0,
_ => unsafe { ::core::intrinsics::unreachable() }
}
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for DeserializeMetaError {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<SpannedError>;
}
}Eq)]
358pub enum DeserializeMetaError {
359#[error("Failed to deserialize asset meta: {0:?}")]
360DeserializeSettings(#[from] SpannedError),
361#[error("Failed to deserialize minimal asset meta: {0:?}")]
362DeserializeMinimal(SpannedError),
363}
364365/// A context that provides access to assets in [`AssetLoader`]s, tracks dependencies, and collects asset load state.
366///
367/// Any asset state accessed by [`LoadContext`] will be tracked and stored for use in dependency events and asset preprocessing.
368pub struct LoadContext<'a> {
369pub(crate) asset_server: &'a AssetServer,
370/// Specifies whether dependencies that are loaded deferred should be loaded.
371 ///
372 /// This allows us to skip loads for cases where we're never going to use the asset and we just
373 /// need the dependency information, for example during asset processing.
374pub(crate) should_load_dependencies: bool,
375 populate_hashes: bool,
376 asset_path: AssetPath<'static>,
377pub(crate) dependencies: HashSet<ErasedAssetIndex>,
378/// Direct dependencies used by this loader.
379pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
380/// Stores the subassets added to this context.
381pub(crate) labeled_assets: Vec<LabeledAsset>,
382/// Maps the label of a subasset to the index into [`Self::labeled_assets`].
383pub(crate) label_to_asset_index: HashMap<CowArc<'static, str>, usize>,
384/// Maps the subasset asset ID to the index into [`Self::labeled_assets`].
385 ///
386 /// This is entirely redundant with [`Self::labeled_assets`], but it allows looking up the
387 /// labeled asset by its asset ID.
388pub(crate) asset_id_to_asset_index: HashMap<UntypedAssetId, usize>,
389}
390391impl<'a> LoadContext<'a> {
392/// Creates a new [`LoadContext`] instance.
393pub(crate) fn new(
394 asset_server: &'a AssetServer,
395 asset_path: AssetPath<'static>,
396 should_load_dependencies: bool,
397 populate_hashes: bool,
398 ) -> Self {
399Self {
400asset_server,
401asset_path,
402populate_hashes,
403should_load_dependencies,
404 dependencies: HashSet::default(),
405 loader_dependencies: HashMap::default(),
406 labeled_assets: Default::default(),
407 label_to_asset_index: Default::default(),
408 asset_id_to_asset_index: Default::default(),
409 }
410 }
411412/// Begins a new labeled asset load. Use the returned [`LoadContext`] to load
413 /// dependencies for the new asset and call [`LoadContext::finish`] to finalize the asset load.
414 /// When finished, make sure you call [`LoadContext::add_loaded_labeled_asset`] to add the results back to the parent
415 /// context.
416 /// Prefer [`LoadContext::labeled_asset_scope`] when possible, which will automatically add
417 /// the labeled [`LoadContext`] back to the parent context.
418 /// [`LoadContext::begin_labeled_asset`] exists largely to enable parallel asset loading.
419 ///
420 /// See [`AssetPath`] for more on labeled assets.
421 ///
422 /// ```no_run
423 /// # use bevy_asset::{Asset, LoadContext};
424 /// # use bevy_reflect::TypePath;
425 /// # #[derive(Asset, TypePath, Default)]
426 /// # struct Image;
427 /// # let load_context: LoadContext = panic!();
428 /// let mut handles = Vec::new();
429 /// for i in 0..2 {
430 /// let labeled = load_context.begin_labeled_asset();
431 /// handles.push(std::thread::spawn(move || {
432 /// (i.to_string(), labeled.finish(Image::default()))
433 /// }));
434 /// }
435 ///
436 /// for handle in handles {
437 /// let (label, loaded_asset) = handle.join().unwrap();
438 /// load_context.add_loaded_labeled_asset(label, loaded_asset);
439 /// }
440 /// ```
441pub fn begin_labeled_asset(&self) -> LoadContext<'_> {
442LoadContext::new(
443self.asset_server,
444self.asset_path.clone(),
445self.should_load_dependencies,
446self.populate_hashes,
447 )
448 }
449450/// Creates a new [`LoadContext`] for the given `label`. The `load` function is responsible for loading an [`Asset`] of
451 /// type `A`. `load` will be called immediately and the result will be used to finalize the [`LoadContext`], resulting in a new
452 /// [`LoadedAsset`], which is registered under the `label` label.
453 ///
454 /// This exists to remove the need to manually call [`LoadContext::begin_labeled_asset`] and then manually register the
455 /// result with [`LoadContext::add_loaded_labeled_asset`].
456 ///
457 /// See [`AssetPath`] for more on labeled assets.
458pub fn labeled_asset_scope<A: Asset, E>(
459&mut self,
460 label: impl Into<CowArc<'static, str>>,
461 load: impl FnOnce(&mut LoadContext) -> Result<A, E>,
462 ) -> Result<Handle<A>, E> {
463let mut context = self.begin_labeled_asset();
464let asset = load(&mut context)?;
465let loaded_asset = context.finish(asset);
466Ok(self.add_loaded_labeled_asset(label, loaded_asset))
467 }
468469/// This will add the given `asset` as a "labeled [`Asset`]" with the `label` label.
470 ///
471 /// # Warning
472 ///
473 /// This will not assign dependencies to the given `asset`. If adding an asset
474 /// with dependencies generated from calls such as [`LoadContext::load`], use
475 /// [`LoadContext::labeled_asset_scope`] or [`LoadContext::begin_labeled_asset`] to generate a
476 /// new [`LoadContext`] to track the dependencies for the labeled asset.
477 ///
478 /// See [`AssetPath`] for more on labeled assets.
479pub fn add_labeled_asset<A: Asset>(
480&mut self,
481 label: impl Into<CowArc<'static, str>>,
482 asset: A,
483 ) -> Handle<A> {
484let Ok(handle) = self.labeled_asset_scope(label, |_| Ok::<_, Infallible>(asset));
485handle486 }
487488/// Add a [`LoadedAsset`] that is a "labeled sub asset" of the root path of this load context.
489 /// This can be used in combination with [`LoadContext::begin_labeled_asset`] to parallelize
490 /// sub asset loading.
491 ///
492 /// See [`AssetPath`] for more on labeled assets.
493pub fn add_loaded_labeled_asset<A: Asset>(
494&mut self,
495 label: impl Into<CowArc<'static, str>>,
496 loaded_asset: LoadedAsset<A>,
497 ) -> Handle<A> {
498let label = label.into();
499let loaded_asset: ErasedLoadedAsset = loaded_asset.into();
500let labeled_path = self.asset_path.clone().with_label(label.clone());
501let handle = self502 .asset_server
503 .get_or_create_path_handle(labeled_path, None);
504let asset = LabeledAsset {
505 asset: loaded_asset,
506 handle: handle.clone().untyped(),
507 };
508match self.label_to_asset_index.entry(label) {
509Entry::Occupied(entry) => {
510// TODO: We should probably treat this as an error. It seems unlikely someone wants
511 // to replace a subasset - this is probably accidental.
512let index = *entry.get();
513// Note: we don't need to mess with the `asset_id_to_asset_index` here, since we
514 // know the same path to `get_or_create_path_handle` will return the same handle as
515 // long as the handle remains alive, and we hold the handle in `LabeledAsset`.
516self.labeled_assets[index] = asset;
517 }
518Entry::Vacant(entry) => {
519entry.insert(self.labeled_assets.len());
520self.asset_id_to_asset_index
521 .insert(handle.id().untyped(), self.labeled_assets.len());
522self.labeled_assets.push(asset);
523 }
524 }
525handle526 }
527528/// Returns `true` if an asset with the label `label` exists in this context.
529 ///
530 /// See [`AssetPath`] for more on labeled assets.
531pub fn has_labeled_asset<'b>(&self, label: impl Into<CowArc<'b, str>>) -> bool {
532let path = self.asset_path.clone().with_label(label.into());
533 !self.asset_server.get_handles_untyped(&path).is_empty()
534 }
535536/// "Finishes" this context by populating the final [`Asset`] value.
537pub fn finish<A: Asset>(mut self, value: A) -> LoadedAsset<A> {
538// At this point, we assume the asset/subasset is "locked in" and won't be changed, so we
539 // can ensure all the dependencies are included (in case a handle was used without loading
540 // it through this `LoadContext`). If in the future we provide an API for mutating assets in
541 // `LoadedAsset`, `ErasedLoadedAsset`, or `LoadContext` (for mutating existing subassets),
542 // we should move this to some point after those mutations are not possible. This spot is
543 // convenient because we still have access to the static type of `A`.
544value.visit_dependencies(&mut |asset_id| {
545let (type_id, index) = match asset_id {
546 UntypedAssetId::Index { type_id, index } => (type_id, index),
547// UUID assets can't be loaded anyway, so just ignore this ID.
548UntypedAssetId::Uuid { .. } => return,
549 };
550self.dependencies
551 .insert(ErasedAssetIndex { index, type_id });
552 });
553LoadedAsset {
554value,
555 dependencies: self.dependencies,
556 loader_dependencies: self.loader_dependencies,
557 labeled_assets: self.labeled_assets,
558 label_to_asset_index: self.label_to_asset_index,
559 asset_id_to_asset_index: self.asset_id_to_asset_index,
560 }
561 }
562563/// Gets the source asset path for this load context.
564pub fn path(&self) -> &AssetPath<'static> {
565&self.asset_path
566 }
567568/// Reads the asset at the given path and returns its bytes
569pub async fn read_asset_bytes<'b, 'c>(
570&'b mut self,
571 path: impl Into<AssetPath<'c>>,
572 ) -> Result<Vec<u8>, ReadAssetBytesError> {
573let path = path.into();
574if path.path() == Path::new("") {
575{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/loader.rs:575",
"bevy_asset::loader", ::tracing::Level::ERROR,
::tracing_core::__macro_support::Option::Some("src/loader.rs"),
::tracing_core::__macro_support::Option::Some(575u32),
::tracing_core::__macro_support::Option::Some("bevy_asset::loader"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::ERROR <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::ERROR <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("Attempted to load an asset with an empty path \"{0}\"!",
path) as &dyn ::tracing::field::Value))])
});
} else { ; }
};error!("Attempted to load an asset with an empty path \"{path}\"!");
576return Err(ReadAssetBytesError::EmptyPath(path.into_owned()));
577 }
578579let source = self.asset_server.get_source(path.source())?;
580let asset_reader = match self.asset_server.mode() {
581 AssetServerMode::Unprocessed => source.reader(),
582 AssetServerMode::Processed => source.processed_reader()?,
583 };
584let mut reader = asset_reader.read(path.path()).await?;
585let hash = if self.populate_hashes {
586// NOTE: ensure meta is read while the asset bytes reader is still active to ensure transactionality
587 // See `ProcessorGatedReader` for more info
588let meta_bytes = asset_reader.read_meta_bytes(path.path()).await?;
589let minimal: ProcessedInfoMinimal = ron::de::from_bytes(&meta_bytes)
590 .map_err(DeserializeMetaError::DeserializeMinimal)?;
591let processed_info = minimal
592 .processed_info
593 .ok_or(ReadAssetBytesError::MissingAssetHash)?;
594 processed_info.full_hash
595 } else {
596 Default::default()
597 };
598let mut bytes = Vec::new();
599 reader
600 .read_to_end(&mut bytes)
601 .await
602.map_err(|source| ReadAssetBytesError::Io {
603 path: path.path().to_path_buf(),
604 source,
605 })?;
606self.loader_dependencies.insert(path.clone_owned(), hash);
607Ok(bytes)
608 }
609610/// Returns a handle to an asset of type `A` with the label `label`. This [`LoadContext`] must produce an asset of the
611 /// given type and the given label or the dependencies of this asset will never be considered "fully loaded". However you
612 /// can call this method before _or_ after adding the labeled asset.
613pub fn get_label_handle<'b, A: Asset>(
614&mut self,
615 label: impl Into<CowArc<'b, str>>,
616 ) -> Handle<A> {
617let path = self.asset_path.clone().with_label(label);
618let handle = self.asset_server.get_or_create_path_handle(path, None);
619// `get_or_create_path_handle` always returns a Strong variant, so we are safe to unwrap.
620let index = (&handle).try_into().unwrap();
621self.dependencies.insert(index);
622handle623 }
624625/// Returns the labeled asset if it exists.
626pub fn get_labeled(&self, label: impl AsRef<str>) -> Option<&ErasedLoadedAsset> {
627let index = self.label_to_asset_index.get(label.as_ref())?;
628let labeled = &self.labeled_assets[*index];
629Some(&labeled.asset)
630 }
631632/// Returns the labeled asset given its asset ID if it exists.
633 ///
634 /// This can be used to get the asset from its handle since `&Handle` implements
635 /// [`Into<UntypedAssetId>`].
636pub fn get_labeled_by_id(&self, id: impl Into<UntypedAssetId>) -> Option<&ErasedLoadedAsset> {
637let index = self.asset_id_to_asset_index.get(&id.into())?;
638let labeled = &self.labeled_assets[*index];
639Some(&labeled.asset)
640 }
641642pub(crate) async fn load_direct_internal(
643&mut self,
644 path: AssetPath<'static>,
645 settings: &dyn Settings,
646 loader: &dyn ErasedAssetLoader,
647 reader: &mut dyn Reader,
648 processed_info: Option<&ProcessedInfo>,
649 ) -> Result<ErasedLoadedAsset, LoadDirectError> {
650let loaded_asset = self
651.asset_server
652 .load_with_settings_loader_and_reader(
653&path,
654 settings,
655 loader,
656 reader,
657self.should_load_dependencies,
658self.populate_hashes,
659 )
660 .await
661.map_err(|error| LoadDirectError::LoadError {
662 dependency: path.clone(),
663 error,
664 })?;
665let hash = processed_info.map(|i| i.full_hash).unwrap_or_default();
666self.loader_dependencies.insert(path, hash);
667Ok(loaded_asset)
668 }
669670/// Create a builder for loading nested assets in this context.
671#[must_use]
672pub fn load_builder(&mut self) -> NestedLoadBuilder<'a, '_> {
673NestedLoadBuilder::new(self)
674 }
675676/// Retrieves a handle for the asset at the given path and adds that path as a dependency of the asset.
677 /// If the current context is a normal [`AssetServer::load`], an actual asset load will be kicked off immediately, which ensures the load happens
678 /// as soon as possible.
679 /// "Normal loads" kicked from within a normal Bevy App will generally configure the context to kick off loads immediately.
680 /// If the current context is configured to not load dependencies automatically (ex: [`AssetProcessor`](crate::processor::AssetProcessor)),
681 /// a load will not be kicked off automatically. It is then the calling context's responsibility to begin a load if necessary.
682 ///
683 /// If you need to override asset settings, asset type, or load directly, please see [`LoadContext::load_builder`].
684pub fn load<'b, A: Asset>(&mut self, path: impl Into<AssetPath<'b>>) -> Handle<A> {
685self.load_builder().load(path)
686 }
687}
688689/// An error produced when calling [`LoadContext::read_asset_bytes`]
690#[derive(fn from(source: MissingProcessedAssetReaderError) -> Self {
ReadAssetBytesError::MissingProcessedAssetReaderError { 0: source }
}Error, #[automatically_derived]
impl ::core::fmt::Debug for ReadAssetBytesError {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
ReadAssetBytesError::EmptyPath(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"EmptyPath", &__self_0),
ReadAssetBytesError::DeserializeMetaError(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"DeserializeMetaError", &__self_0),
ReadAssetBytesError::AssetReaderError(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"AssetReaderError", &__self_0),
ReadAssetBytesError::MissingAssetSourceError(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"MissingAssetSourceError", &__self_0),
ReadAssetBytesError::MissingProcessedAssetReaderError(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"MissingProcessedAssetReaderError", &__self_0),
ReadAssetBytesError::Io { path: __self_0, source: __self_1 } =>
::core::fmt::Formatter::debug_struct_field2_finish(f, "Io",
"path", __self_0, "source", &__self_1),
ReadAssetBytesError::MissingAssetHash =>
::core::fmt::Formatter::write_str(f, "MissingAssetHash"),
}
}
}Debug)]
691pub enum ReadAssetBytesError {
692#[error("Attempted to load an asset with an empty path \"{0}\"")]
693EmptyPath(AssetPath<'static>),
694#[error(transparent)]
695DeserializeMetaError(#[from] DeserializeMetaError),
696#[error(transparent)]
697AssetReaderError(#[from] AssetReaderError),
698#[error(transparent)]
699MissingAssetSourceError(#[from] MissingAssetSourceError),
700#[error(transparent)]
701MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
702/// Encountered an I/O error while loading an asset.
703#[error("Encountered an io error while loading asset at `{}`: {source}", path.display())]
704Io {
705 path: PathBuf,
706 source: std::io::Error,
707 },
708#[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")]
709MissingAssetHash,
710}