Skip to main content

bevy_asset/
loader.rs

1use crate::{
2    io::{AssetReaderError, MissingAssetSourceError, MissingProcessedAssetReaderError, Reader},
3    loader_builders::NestedLoadBuilder,
4    meta::{AssetHash, AssetMeta, AssetMetaDyn, ProcessedInfo, ProcessedInfoMinimal, Settings},
5    path::AssetPath,
6    Asset, AssetIndex, AssetLoadError, AssetServer, AssetServerMode, Assets, ErasedAssetIndex,
7    Handle, 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},
17    convert::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;
25
26/// 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`].
34    type Asset: Asset;
35    /// The settings type used by this [`AssetLoader`].
36    type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
37    /// The type of [error](`std::error::Error`) which could be encountered by this loader.
38    type Error: Into<BevyError>;
39    /// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`].
40    fn 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>>;
46
47    /// 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.
49    fn extensions(&self) -> &[&str] {
50        &[]
51    }
52}
53
54/// 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`].
57    fn 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>>;
63
64    /// Returns a list of extensions supported by this asset loader, without the preceding dot.
65    fn extensions(&self) -> &[&str];
66    /// Deserializes metadata from the input `meta` bytes into the appropriate type (erased as [`Box<dyn AssetMetaDyn>`]).
67    fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
68    /// Returns the default meta value for the [`AssetLoader`] (erased as [`Box<dyn AssetMetaDyn>`]).
69    fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
70    /// Returns the type path of the [`AssetLoader`].
71    fn type_path(&self) -> &'static str;
72    /// Returns the [`TypeId`] of the [`AssetLoader`].
73    fn type_id(&self) -> TypeId;
74    /// Returns the type name of the top-level [`Asset`] loaded by the [`AssetLoader`].
75    fn asset_type_name(&self) -> &'static str;
76    /// Returns the [`TypeId`] of the top-level [`Asset`] loaded by the [`AssetLoader`].
77    fn asset_type_id(&self) -> TypeId;
78}
79
80impl<L> ErasedAssetLoader for L
81where
82    L: AssetLoader + Send + Sync,
83{
84    /// Processes the asset in an asynchronous closure.
85    fn load<'a>(
86        &'a self,
87        reader: &'a mut dyn Reader,
88        settings: &'a dyn Settings,
89        mut load_context: LoadContext<'a>,
90    ) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>> {
91        Box::pin(async move {
92            let settings = settings
93                .downcast_ref::<L::Settings>()
94                .expect("AssetLoader settings should match the loader type");
95            let asset = <L as AssetLoader>::load(self, reader, settings, &mut load_context)
96                .await
97                .map_err(Into::into)?;
98            Ok(load_context.finish(asset).into())
99        })
100    }
101
102    fn extensions(&self) -> &[&str] {
103        <L as AssetLoader>::extensions(self)
104    }
105
106    fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
107        let meta = AssetMeta::<L, ()>::deserialize(meta)?;
108        Ok(Box::new(meta))
109    }
110
111    fn default_meta(&self) -> Box<dyn AssetMetaDyn> {
112        Box::new(AssetMeta::<L, ()>::new(crate::meta::AssetAction::Load {
113            loader: self.type_path().to_string(),
114            settings: L::Settings::default(),
115        }))
116    }
117
118    fn type_path(&self) -> &'static str {
119        L::type_path()
120    }
121
122    fn type_id(&self) -> TypeId {
123        TypeId::of::<L>()
124    }
125
126    fn asset_type_name(&self) -> &'static str {
127        core::any::type_name::<L::Asset>()
128    }
129
130    fn asset_type_id(&self) -> TypeId {
131        TypeId::of::<L::Asset>()
132    }
133}
134
135pub(crate) struct LabeledAsset {
136    pub(crate) asset: ErasedLoadedAsset,
137    pub(crate) handle: UntypedHandle,
138}
139
140/// 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> {
145    pub(crate) value: A,
146    pub(crate) dependencies: HashSet<ErasedAssetIndex>,
147    pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
148    /// The subassets of this asset.
149    pub(crate) labeled_assets: Vec<LabeledAsset>,
150    /// The mapping from subasset labels to their index in [`Self::labeled_assets`].
151    pub(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.
156    pub(crate) asset_id_to_asset_index: HashMap<UntypedAssetId, usize>,
157}
158
159impl<A: Asset> LoadedAsset<A> {
160    /// Create a new loaded asset. This will use [`VisitAssetDependencies`](crate::VisitAssetDependencies) to populate `dependencies`.
161    pub fn new_with_dependencies(value: A) -> Self {
162        let mut dependencies = <HashSet<_>>::default();
163        value.visit_dependencies(&mut |id| {
164            let Ok(asset_index) = id.try_into() else {
165                return;
166            };
167            dependencies.insert(asset_index);
168        });
169        LoadedAsset {
170            value,
171            dependencies,
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    }
178
179    /// Cast (and take ownership) of the [`Asset`] value of the given type.
180    pub fn take(self) -> A {
181        self.value
182    }
183
184    /// Retrieves a reference to the internal [`Asset`] type.
185    pub fn get(&self) -> &A {
186        &self.value
187    }
188
189    /// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
190    pub fn get_labeled(&self, label: impl AsRef<str>) -> Option<&ErasedLoadedAsset> {
191        self.label_to_asset_index
192            .get(label.as_ref())
193            .map(|index| self.labeled_assets.get(*index).unwrap())
194            .map(|a| &a.asset)
195    }
196
197    /// 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>`].
201    pub fn get_labeled_by_id(&self, id: impl Into<UntypedAssetId>) -> Option<&ErasedLoadedAsset> {
202        let index = self.asset_id_to_asset_index.get(&id.into())?;
203        let labeled = &self.labeled_assets[*index];
204        Some(&labeled.asset)
205    }
206
207    /// Iterate over all labels for "labeled assets" in the loaded asset
208    pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
209        self.label_to_asset_index.keys().map(|s| &**s)
210    }
211}
212
213impl<A: Asset> From<A> for LoadedAsset<A> {
214    fn from(asset: A) -> Self {
215        LoadedAsset::new_with_dependencies(asset)
216    }
217}
218
219/// A "type erased / boxed" counterpart to [`LoadedAsset`]. This is used in places where the loaded type is not statically known.
220pub struct ErasedLoadedAsset {
221    pub(crate) value: Box<dyn AssetContainer>,
222    pub(crate) dependencies: HashSet<ErasedAssetIndex>,
223    pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
224    /// The subassets of this asset.
225    pub(crate) labeled_assets: Vec<LabeledAsset>,
226    /// The mapping from subasset labels to their index in [`Self::labeled_assets`].
227    pub(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.
232    pub(crate) asset_id_to_asset_index: HashMap<UntypedAssetId, usize>,
233}
234
235impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset {
236    fn from(asset: LoadedAsset<A>) -> Self {
237        ErasedLoadedAsset {
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}
247
248impl 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.
251    pub fn take<A: Asset>(self) -> Option<A> {
252        self.value.downcast::<A>().map(|a| *a).ok()
253    }
254
255    /// Retrieves a reference to the internal [`Asset`] type, if it matches the type `A`. Otherwise returns [`None`].
256    pub fn get<A: Asset>(&self) -> Option<&A> {
257        self.value.downcast_ref::<A>()
258    }
259
260    /// Retrieves the [`TypeId`] of the stored [`Asset`] type.
261    pub fn asset_type_id(&self) -> TypeId {
262        (*self.value).type_id()
263    }
264
265    /// Retrieves the `type_name` of the stored [`Asset`] type.
266    pub fn asset_type_name(&self) -> &'static str {
267        self.value.asset_type_name()
268    }
269
270    /// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
271    pub fn get_labeled(&self, label: impl AsRef<str>) -> Option<&ErasedLoadedAsset> {
272        self.label_to_asset_index
273            .get(label.as_ref())
274            .map(|index| self.labeled_assets.get(*index).unwrap())
275            .map(|a| &a.asset)
276    }
277
278    /// 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>`].
282    pub fn get_labeled_by_id(&self, id: impl Into<UntypedAssetId>) -> Option<&ErasedLoadedAsset> {
283        let index = self.asset_id_to_asset_index.get(&id.into())?;
284        let labeled = &self.labeled_assets[*index];
285        Some(&labeled.asset)
286    }
287
288    /// Iterate over all labels for "labeled assets" in the loaded asset
289    pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
290        self.label_to_asset_index.keys().map(|s| &**s)
291    }
292
293    /// 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    )]
302    pub fn downcast<A: Asset>(mut self) -> Result<LoadedAsset<A>, ErasedLoadedAsset> {
303        match self.value.downcast::<A>() {
304            Ok(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            }),
312            Err(value) => {
313                self.value = value;
314                Err(self)
315            }
316        }
317    }
318}
319
320/// 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 {
322    fn insert(self: Box<Self>, id: AssetIndex, world: &mut World);
323    fn asset_type_name(&self) -> &'static str;
324}
325
326impl 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);
327
328impl<A: Asset> AssetContainer for A {
329    fn 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.
331        world
332            .resource_mut::<Assets<A>>()
333            .insert(index, *self)
334            .expect("the AssetIndex is still valid");
335    }
336
337    fn asset_type_name(&self) -> &'static str {
338        core::any::type_name::<A>()
339    }
340}
341
342/// 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}\"")]
346    EmptyPath(AssetPath<'static>),
347    #[error("Requested to load an asset path ({0:?}) with a subasset, but this is unsupported. See issue #18291")]
348    RequestedSubasset(AssetPath<'static>),
349    #[error("Failed to load dependency {dependency:?} {error}")]
350    LoadError {
351        dependency: AssetPath<'static>,
352        error: AssetLoadError,
353    },
354}
355
356/// 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:?}")]
360    DeserializeSettings(#[from] SpannedError),
361    #[error("Failed to deserialize minimal asset meta: {0:?}")]
362    DeserializeMinimal(SpannedError),
363}
364
365/// 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> {
369    pub(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.
374    pub(crate) should_load_dependencies: bool,
375    populate_hashes: bool,
376    asset_path: AssetPath<'static>,
377    pub(crate) dependencies: HashSet<ErasedAssetIndex>,
378    /// Direct dependencies used by this loader.
379    pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
380    /// Stores the subassets added to this context.
381    pub(crate) labeled_assets: Vec<LabeledAsset>,
382    /// Maps the label of a subasset to the index into [`Self::labeled_assets`].
383    pub(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.
388    pub(crate) asset_id_to_asset_index: HashMap<UntypedAssetId, usize>,
389}
390
391impl<'a> LoadContext<'a> {
392    /// Creates a new [`LoadContext`] instance.
393    pub(crate) fn new(
394        asset_server: &'a AssetServer,
395        asset_path: AssetPath<'static>,
396        should_load_dependencies: bool,
397        populate_hashes: bool,
398    ) -> Self {
399        Self {
400            asset_server,
401            asset_path,
402            populate_hashes,
403            should_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    }
411
412    /// 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    /// ```
441    pub fn begin_labeled_asset(&self) -> LoadContext<'_> {
442        LoadContext::new(
443            self.asset_server,
444            self.asset_path.clone(),
445            self.should_load_dependencies,
446            self.populate_hashes,
447        )
448    }
449
450    /// 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.
458    pub 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> {
463        let mut context = self.begin_labeled_asset();
464        let asset = load(&mut context)?;
465        let loaded_asset = context.finish(asset);
466        Ok(self.add_loaded_labeled_asset(label, loaded_asset))
467    }
468
469    /// 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.
479    pub fn add_labeled_asset<A: Asset>(
480        &mut self,
481        label: impl Into<CowArc<'static, str>>,
482        asset: A,
483    ) -> Handle<A> {
484        let Ok(handle) = self.labeled_asset_scope(label, |_| Ok::<_, Infallible>(asset));
485        handle
486    }
487
488    /// 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.
493    pub 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> {
498        let label = label.into();
499        let loaded_asset: ErasedLoadedAsset = loaded_asset.into();
500        let labeled_path = self.asset_path.clone().with_label(label.clone());
501        let handle = self
502            .asset_server
503            .get_or_create_path_handle(labeled_path, None);
504        let asset = LabeledAsset {
505            asset: loaded_asset,
506            handle: handle.clone().untyped(),
507        };
508        match self.label_to_asset_index.entry(label) {
509            Entry::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.
512                let 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`.
516                self.labeled_assets[index] = asset;
517            }
518            Entry::Vacant(entry) => {
519                entry.insert(self.labeled_assets.len());
520                self.asset_id_to_asset_index
521                    .insert(handle.id().untyped(), self.labeled_assets.len());
522                self.labeled_assets.push(asset);
523            }
524        }
525        handle
526    }
527
528    /// Returns `true` if an asset with the label `label` exists in this context.
529    ///
530    /// See [`AssetPath`] for more on labeled assets.
531    pub fn has_labeled_asset<'b>(&self, label: impl Into<CowArc<'b, str>>) -> bool {
532        let path = self.asset_path.clone().with_label(label.into());
533        !self.asset_server.get_handles_untyped(&path).is_empty()
534    }
535
536    /// "Finishes" this context by populating the final [`Asset`] value.
537    pub 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`.
544        value.visit_dependencies(&mut |asset_id| {
545            let (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.
548                UntypedAssetId::Uuid { .. } => return,
549            };
550            self.dependencies
551                .insert(ErasedAssetIndex { index, type_id });
552        });
553        LoadedAsset {
554            value,
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    }
562
563    /// Gets the source asset path for this load context.
564    pub fn path(&self) -> &AssetPath<'static> {
565        &self.asset_path
566    }
567
568    /// Reads the asset at the given path and returns its bytes
569    pub async fn read_asset_bytes<'b, 'c>(
570        &'b mut self,
571        path: impl Into<AssetPath<'c>>,
572    ) -> Result<Vec<u8>, ReadAssetBytesError> {
573        let path = path.into();
574        if 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}\"!");
576            return Err(ReadAssetBytesError::EmptyPath(path.into_owned()));
577        }
578
579        let source = self.asset_server.get_source(path.source())?;
580        let asset_reader = match self.asset_server.mode() {
581            AssetServerMode::Unprocessed => source.reader(),
582            AssetServerMode::Processed => source.processed_reader()?,
583        };
584        let mut reader = asset_reader.read(path.path()).await?;
585        let 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
588            let meta_bytes = asset_reader.read_meta_bytes(path.path()).await?;
589            let minimal: ProcessedInfoMinimal = ron::de::from_bytes(&meta_bytes)
590                .map_err(DeserializeMetaError::DeserializeMinimal)?;
591            let processed_info = minimal
592                .processed_info
593                .ok_or(ReadAssetBytesError::MissingAssetHash)?;
594            processed_info.full_hash
595        } else {
596            Default::default()
597        };
598        let 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            })?;
606        self.loader_dependencies.insert(path.clone_owned(), hash);
607        Ok(bytes)
608    }
609
610    /// 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.
613    pub fn get_label_handle<'b, A: Asset>(
614        &mut self,
615        label: impl Into<CowArc<'b, str>>,
616    ) -> Handle<A> {
617        let path = self.asset_path.clone().with_label(label);
618        let 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.
620        let index = (&handle).try_into().unwrap();
621        self.dependencies.insert(index);
622        handle
623    }
624
625    /// Returns the labeled asset if it exists.
626    pub fn get_labeled(&self, label: impl AsRef<str>) -> Option<&ErasedLoadedAsset> {
627        let index = self.label_to_asset_index.get(label.as_ref())?;
628        let labeled = &self.labeled_assets[*index];
629        Some(&labeled.asset)
630    }
631
632    /// 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>`].
636    pub fn get_labeled_by_id(&self, id: impl Into<UntypedAssetId>) -> Option<&ErasedLoadedAsset> {
637        let index = self.asset_id_to_asset_index.get(&id.into())?;
638        let labeled = &self.labeled_assets[*index];
639        Some(&labeled.asset)
640    }
641
642    pub(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> {
650        let loaded_asset = self
651            .asset_server
652            .load_with_settings_loader_and_reader(
653                &path,
654                settings,
655                loader,
656                reader,
657                self.should_load_dependencies,
658                self.populate_hashes,
659            )
660            .await
661            .map_err(|error| LoadDirectError::LoadError {
662                dependency: path.clone(),
663                error,
664            })?;
665        let hash = processed_info.map(|i| i.full_hash).unwrap_or_default();
666        self.loader_dependencies.insert(path, hash);
667        Ok(loaded_asset)
668    }
669
670    /// Create a builder for loading nested assets in this context.
671    #[must_use]
672    pub fn load_builder(&mut self) -> NestedLoadBuilder<'a, '_> {
673        NestedLoadBuilder::new(self)
674    }
675
676    /// 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`].
684    pub fn load<'b, A: Asset>(&mut self, path: impl Into<AssetPath<'b>>) -> Handle<A> {
685        self.load_builder().load(path)
686    }
687}
688
689/// 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}\"")]
693    EmptyPath(AssetPath<'static>),
694    #[error(transparent)]
695    DeserializeMetaError(#[from] DeserializeMetaError),
696    #[error(transparent)]
697    AssetReaderError(#[from] AssetReaderError),
698    #[error(transparent)]
699    MissingAssetSourceError(#[from] MissingAssetSourceError),
700    #[error(transparent)]
701    MissingProcessedAssetReaderError(#[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())]
704    Io {
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.")]
709    MissingAssetHash,
710}