bevy_asset/
loader.rs

1use crate::{
2    io::{
3        AssetReaderError, MissingAssetSourceError, MissingProcessedAssetReaderError, Reader,
4        ReaderRequiredFeatures,
5    },
6    loader_builders::{Deferred, NestedLoader, StaticTyped},
7    meta::{AssetHash, AssetMeta, AssetMetaDyn, ProcessedInfo, ProcessedInfoMinimal, Settings},
8    path::AssetPath,
9    Asset, AssetIndex, AssetLoadError, AssetServer, AssetServerMode, Assets, ErasedAssetIndex,
10    Handle, UntypedAssetId, UntypedHandle,
11};
12use alloc::{
13    boxed::Box,
14    string::{String, ToString},
15    vec::Vec,
16};
17use atomicow::CowArc;
18use bevy_ecs::{error::BevyError, world::World};
19use bevy_platform::collections::{HashMap, HashSet};
20use bevy_reflect::TypePath;
21use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
22use core::any::{Any, TypeId};
23use downcast_rs::{impl_downcast, Downcast};
24use ron::error::SpannedError;
25use serde::{Deserialize, Serialize};
26use std::path::PathBuf;
27use thiserror::Error;
28
29/// Loads an [`Asset`] from a given byte [`Reader`]. This can accept [`AssetLoader::Settings`], which configure how the [`Asset`]
30/// should be loaded.
31///
32/// This trait is generally used in concert with [`AssetReader`](crate::io::AssetReader) to load assets from a byte source.
33///
34/// For a complementary version of this trait that can save assets, see [`AssetSaver`](crate::saver::AssetSaver).
35pub trait AssetLoader: TypePath + Send + Sync + 'static {
36    /// The top level [`Asset`] loaded by this [`AssetLoader`].
37    type Asset: Asset;
38    /// The settings type used by this [`AssetLoader`].
39    type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
40    /// The type of [error](`std::error::Error`) which could be encountered by this loader.
41    type Error: Into<BevyError>;
42    /// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`].
43    fn load(
44        &self,
45        reader: &mut dyn Reader,
46        settings: &Self::Settings,
47        load_context: &mut LoadContext,
48    ) -> impl ConditionalSendFuture<Output = Result<Self::Asset, Self::Error>>;
49
50    /// Returns the required features of the reader for this loader.
51    fn reader_required_features(_settings: &Self::Settings) -> ReaderRequiredFeatures {
52        ReaderRequiredFeatures::default()
53    }
54
55    /// Returns a list of extensions supported by this [`AssetLoader`], without the preceding dot.
56    /// Note that users of this [`AssetLoader`] may choose to load files with a non-matching extension.
57    fn extensions(&self) -> &[&str] {
58        &[]
59    }
60}
61
62/// Provides type-erased access to an [`AssetLoader`].
63pub trait ErasedAssetLoader: Send + Sync + 'static {
64    /// Asynchronously loads the asset(s) from the bytes provided by [`Reader`].
65    fn load<'a>(
66        &'a self,
67        reader: &'a mut dyn Reader,
68        settings: &'a dyn Settings,
69        load_context: LoadContext<'a>,
70    ) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>>;
71
72    /// Returns the required features of the reader for this loader.
73    // Note: This takes &self just to be dyn compatible.
74    fn reader_required_features(&self, settings: &dyn Settings) -> ReaderRequiredFeatures;
75    /// Returns a list of extensions supported by this asset loader, without the preceding dot.
76    fn extensions(&self) -> &[&str];
77    /// Deserializes metadata from the input `meta` bytes into the appropriate type (erased as [`Box<dyn AssetMetaDyn>`]).
78    fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
79    /// Returns the default meta value for the [`AssetLoader`] (erased as [`Box<dyn AssetMetaDyn>`]).
80    fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
81    /// Returns the type path of the [`AssetLoader`].
82    fn type_path(&self) -> &'static str;
83    /// Returns the [`TypeId`] of the [`AssetLoader`].
84    fn type_id(&self) -> TypeId;
85    /// Returns the type name of the top-level [`Asset`] loaded by the [`AssetLoader`].
86    fn asset_type_name(&self) -> &'static str;
87    /// Returns the [`TypeId`] of the top-level [`Asset`] loaded by the [`AssetLoader`].
88    fn asset_type_id(&self) -> TypeId;
89}
90
91impl<L> ErasedAssetLoader for L
92where
93    L: AssetLoader + Send + Sync,
94{
95    /// Processes the asset in an asynchronous closure.
96    fn load<'a>(
97        &'a self,
98        reader: &'a mut dyn Reader,
99        settings: &'a dyn Settings,
100        mut load_context: LoadContext<'a>,
101    ) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>> {
102        Box::pin(async move {
103            let settings = settings
104                .downcast_ref::<L::Settings>()
105                .expect("AssetLoader settings should match the loader type");
106            let asset = <L as AssetLoader>::load(self, reader, settings, &mut load_context)
107                .await
108                .map_err(Into::into)?;
109            Ok(load_context.finish(asset).into())
110        })
111    }
112
113    fn reader_required_features(&self, settings: &dyn Settings) -> ReaderRequiredFeatures {
114        let settings = settings
115            .downcast_ref::<L::Settings>()
116            .expect("AssetLoader settings should match the loader type");
117        <L as AssetLoader>::reader_required_features(settings)
118    }
119
120    fn extensions(&self) -> &[&str] {
121        <L as AssetLoader>::extensions(self)
122    }
123
124    fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
125        let meta = AssetMeta::<L, ()>::deserialize(meta)?;
126        Ok(Box::new(meta))
127    }
128
129    fn default_meta(&self) -> Box<dyn AssetMetaDyn> {
130        Box::new(AssetMeta::<L, ()>::new(crate::meta::AssetAction::Load {
131            loader: self.type_path().to_string(),
132            settings: L::Settings::default(),
133        }))
134    }
135
136    fn type_path(&self) -> &'static str {
137        L::type_path()
138    }
139
140    fn type_id(&self) -> TypeId {
141        TypeId::of::<L>()
142    }
143
144    fn asset_type_name(&self) -> &'static str {
145        core::any::type_name::<L::Asset>()
146    }
147
148    fn asset_type_id(&self) -> TypeId {
149        TypeId::of::<L::Asset>()
150    }
151}
152
153pub(crate) struct LabeledAsset {
154    pub(crate) asset: ErasedLoadedAsset,
155    pub(crate) handle: UntypedHandle,
156}
157
158/// The successful result of an [`AssetLoader::load`] call. This contains the loaded "root" asset and any other "labeled" assets produced
159/// by the loader. It also holds the input [`AssetMeta`] (if it exists) and tracks dependencies:
160/// * normal dependencies: dependencies that must be loaded as part of this asset load (ex: assets a given asset has handles to).
161/// * Loader dependencies: dependencies whose actual asset values are used during the load process
162pub struct LoadedAsset<A: Asset> {
163    pub(crate) value: A,
164    pub(crate) dependencies: HashSet<ErasedAssetIndex>,
165    pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
166    pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
167}
168
169impl<A: Asset> LoadedAsset<A> {
170    /// Create a new loaded asset. This will use [`VisitAssetDependencies`](crate::VisitAssetDependencies) to populate `dependencies`.
171    pub fn new_with_dependencies(value: A) -> Self {
172        let mut dependencies = <HashSet<_>>::default();
173        value.visit_dependencies(&mut |id| {
174            let Ok(asset_index) = id.try_into() else {
175                return;
176            };
177            dependencies.insert(asset_index);
178        });
179        LoadedAsset {
180            value,
181            dependencies,
182            loader_dependencies: HashMap::default(),
183            labeled_assets: HashMap::default(),
184        }
185    }
186
187    /// Cast (and take ownership) of the [`Asset`] value of the given type.
188    pub fn take(self) -> A {
189        self.value
190    }
191
192    /// Retrieves a reference to the internal [`Asset`] type.
193    pub fn get(&self) -> &A {
194        &self.value
195    }
196
197    /// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
198    pub fn get_labeled(
199        &self,
200        label: impl Into<CowArc<'static, str>>,
201    ) -> Option<&ErasedLoadedAsset> {
202        self.labeled_assets.get(&label.into()).map(|a| &a.asset)
203    }
204
205    /// Iterate over all labels for "labeled assets" in the loaded asset
206    pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
207        self.labeled_assets.keys().map(|s| &**s)
208    }
209}
210
211impl<A: Asset> From<A> for LoadedAsset<A> {
212    fn from(asset: A) -> Self {
213        LoadedAsset::new_with_dependencies(asset)
214    }
215}
216
217/// A "type erased / boxed" counterpart to [`LoadedAsset`]. This is used in places where the loaded type is not statically known.
218pub struct ErasedLoadedAsset {
219    pub(crate) value: Box<dyn AssetContainer>,
220    pub(crate) dependencies: HashSet<ErasedAssetIndex>,
221    pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
222    pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
223}
224
225impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset {
226    fn from(asset: LoadedAsset<A>) -> Self {
227        ErasedLoadedAsset {
228            value: Box::new(asset.value),
229            dependencies: asset.dependencies,
230            loader_dependencies: asset.loader_dependencies,
231            labeled_assets: asset.labeled_assets,
232        }
233    }
234}
235
236impl ErasedLoadedAsset {
237    /// Cast (and take ownership) of the [`Asset`] value of the given type. This will return [`Some`] if
238    /// the stored type matches `A` and [`None`] if it does not.
239    pub fn take<A: Asset>(self) -> Option<A> {
240        self.value.downcast::<A>().map(|a| *a).ok()
241    }
242
243    /// Retrieves a reference to the internal [`Asset`] type, if it matches the type `A`. Otherwise returns [`None`].
244    pub fn get<A: Asset>(&self) -> Option<&A> {
245        self.value.downcast_ref::<A>()
246    }
247
248    /// Retrieves the [`TypeId`] of the stored [`Asset`] type.
249    pub fn asset_type_id(&self) -> TypeId {
250        (*self.value).type_id()
251    }
252
253    /// Retrieves the `type_name` of the stored [`Asset`] type.
254    pub fn asset_type_name(&self) -> &'static str {
255        self.value.asset_type_name()
256    }
257
258    /// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
259    pub fn get_labeled(
260        &self,
261        label: impl Into<CowArc<'static, str>>,
262    ) -> Option<&ErasedLoadedAsset> {
263        self.labeled_assets.get(&label.into()).map(|a| &a.asset)
264    }
265
266    /// Iterate over all labels for "labeled assets" in the loaded asset
267    pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
268        self.labeled_assets.keys().map(|s| &**s)
269    }
270
271    /// Cast this loaded asset as the given type. If the type does not match,
272    /// the original type-erased asset is returned.
273    pub fn downcast<A: Asset>(mut self) -> Result<LoadedAsset<A>, ErasedLoadedAsset> {
274        match self.value.downcast::<A>() {
275            Ok(value) => Ok(LoadedAsset {
276                value: *value,
277                dependencies: self.dependencies,
278                loader_dependencies: self.loader_dependencies,
279                labeled_assets: self.labeled_assets,
280            }),
281            Err(value) => {
282                self.value = value;
283                Err(self)
284            }
285        }
286    }
287}
288
289/// A type erased container for an [`Asset`] value that is capable of inserting the [`Asset`] into a [`World`]'s [`Assets`] collection.
290pub(crate) trait AssetContainer: Downcast + Any + Send + Sync + 'static {
291    fn insert(self: Box<Self>, id: AssetIndex, world: &mut World);
292    fn asset_type_name(&self) -> &'static str;
293}
294
295impl_downcast!(AssetContainer);
296
297impl<A: Asset> AssetContainer for A {
298    fn insert(self: Box<Self>, index: AssetIndex, world: &mut World) {
299        // We only ever call this if we know the asset is still alive, so it is fine to unwrap here.
300        world
301            .resource_mut::<Assets<A>>()
302            .insert(index, *self)
303            .expect("the AssetIndex is still valid");
304    }
305
306    fn asset_type_name(&self) -> &'static str {
307        core::any::type_name::<A>()
308    }
309}
310
311/// An error that occurs when attempting to call [`NestedLoader::load`] which
312/// is configured to work [immediately].
313///
314/// [`NestedLoader::load`]: crate::NestedLoader::load
315/// [immediately]: crate::Immediate
316#[derive(Error, Debug)]
317pub enum LoadDirectError {
318    #[error("Requested to load an asset path ({0:?}) with a subasset, but this is unsupported. See issue #18291")]
319    RequestedSubasset(AssetPath<'static>),
320    #[error("Failed to load dependency {dependency:?} {error}")]
321    LoadError {
322        dependency: AssetPath<'static>,
323        error: AssetLoadError,
324    },
325}
326
327/// An error that occurs while deserializing [`AssetMeta`].
328#[derive(Error, Debug, Clone, PartialEq, Eq)]
329pub enum DeserializeMetaError {
330    #[error("Failed to deserialize asset meta: {0:?}")]
331    DeserializeSettings(#[from] SpannedError),
332    #[error("Failed to deserialize minimal asset meta: {0:?}")]
333    DeserializeMinimal(SpannedError),
334}
335
336/// A context that provides access to assets in [`AssetLoader`]s, tracks dependencies, and collects asset load state.
337///
338/// Any asset state accessed by [`LoadContext`] will be tracked and stored for use in dependency events and asset preprocessing.
339pub struct LoadContext<'a> {
340    pub(crate) asset_server: &'a AssetServer,
341    /// Specifies whether dependencies that are loaded deferred should be loaded.
342    ///
343    /// This allows us to skip loads for cases where we're never going to use the asset and we just
344    /// need the dependency information, for example during asset processing.
345    pub(crate) should_load_dependencies: bool,
346    populate_hashes: bool,
347    asset_path: AssetPath<'static>,
348    pub(crate) dependencies: HashSet<ErasedAssetIndex>,
349    /// Direct dependencies used by this loader.
350    pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
351    pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
352}
353
354impl<'a> LoadContext<'a> {
355    /// Creates a new [`LoadContext`] instance.
356    pub(crate) fn new(
357        asset_server: &'a AssetServer,
358        asset_path: AssetPath<'static>,
359        should_load_dependencies: bool,
360        populate_hashes: bool,
361    ) -> Self {
362        Self {
363            asset_server,
364            asset_path,
365            populate_hashes,
366            should_load_dependencies,
367            dependencies: HashSet::default(),
368            loader_dependencies: HashMap::default(),
369            labeled_assets: HashMap::default(),
370        }
371    }
372
373    /// Begins a new labeled asset load. Use the returned [`LoadContext`] to load
374    /// dependencies for the new asset and call [`LoadContext::finish`] to finalize the asset load.
375    /// When finished, make sure you call [`LoadContext::add_loaded_labeled_asset`] to add the results back to the parent
376    /// context.
377    /// Prefer [`LoadContext::labeled_asset_scope`] when possible, which will automatically add
378    /// the labeled [`LoadContext`] back to the parent context.
379    /// [`LoadContext::begin_labeled_asset`] exists largely to enable parallel asset loading.
380    ///
381    /// See [`AssetPath`] for more on labeled assets.
382    ///
383    /// ```no_run
384    /// # use bevy_asset::{Asset, LoadContext};
385    /// # use bevy_reflect::TypePath;
386    /// # #[derive(Asset, TypePath, Default)]
387    /// # struct Image;
388    /// # let load_context: LoadContext = panic!();
389    /// let mut handles = Vec::new();
390    /// for i in 0..2 {
391    ///     let labeled = load_context.begin_labeled_asset();
392    ///     handles.push(std::thread::spawn(move || {
393    ///         (i.to_string(), labeled.finish(Image::default()))
394    ///     }));
395    /// }
396    ///
397    /// for handle in handles {
398    ///     let (label, loaded_asset) = handle.join().unwrap();
399    ///     load_context.add_loaded_labeled_asset(label, loaded_asset);
400    /// }
401    /// ```
402    pub fn begin_labeled_asset(&self) -> LoadContext<'_> {
403        LoadContext::new(
404            self.asset_server,
405            self.asset_path.clone(),
406            self.should_load_dependencies,
407            self.populate_hashes,
408        )
409    }
410
411    /// Creates a new [`LoadContext`] for the given `label`. The `load` function is responsible for loading an [`Asset`] of
412    /// type `A`. `load` will be called immediately and the result will be used to finalize the [`LoadContext`], resulting in a new
413    /// [`LoadedAsset`], which is registered under the `label` label.
414    ///
415    /// This exists to remove the need to manually call [`LoadContext::begin_labeled_asset`] and then manually register the
416    /// result with [`LoadContext::add_loaded_labeled_asset`].
417    ///
418    /// See [`AssetPath`] for more on labeled assets.
419    pub fn labeled_asset_scope<A: Asset, E>(
420        &mut self,
421        label: String,
422        load: impl FnOnce(&mut LoadContext) -> Result<A, E>,
423    ) -> Result<Handle<A>, E> {
424        let mut context = self.begin_labeled_asset();
425        let asset = load(&mut context)?;
426        let loaded_asset = context.finish(asset);
427        Ok(self.add_loaded_labeled_asset(label, loaded_asset))
428    }
429
430    /// This will add the given `asset` as a "labeled [`Asset`]" with the `label` label.
431    ///
432    /// # Warning
433    ///
434    /// This will not assign dependencies to the given `asset`. If adding an asset
435    /// with dependencies generated from calls such as [`LoadContext::load`], use
436    /// [`LoadContext::labeled_asset_scope`] or [`LoadContext::begin_labeled_asset`] to generate a
437    /// new [`LoadContext`] to track the dependencies for the labeled asset.
438    ///
439    /// See [`AssetPath`] for more on labeled assets.
440    pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
441        self.labeled_asset_scope(label, |_| Ok::<_, ()>(asset))
442            .expect("the closure returns Ok")
443    }
444
445    /// Add a [`LoadedAsset`] that is a "labeled sub asset" of the root path of this load context.
446    /// This can be used in combination with [`LoadContext::begin_labeled_asset`] to parallelize
447    /// sub asset loading.
448    ///
449    /// See [`AssetPath`] for more on labeled assets.
450    pub fn add_loaded_labeled_asset<A: Asset>(
451        &mut self,
452        label: impl Into<CowArc<'static, str>>,
453        loaded_asset: LoadedAsset<A>,
454    ) -> Handle<A> {
455        let label = label.into();
456        let loaded_asset: ErasedLoadedAsset = loaded_asset.into();
457        let labeled_path = self.asset_path.clone().with_label(label.clone());
458        let handle = self
459            .asset_server
460            .get_or_create_path_handle(labeled_path, None);
461        self.labeled_assets.insert(
462            label,
463            LabeledAsset {
464                asset: loaded_asset,
465                handle: handle.clone().untyped(),
466            },
467        );
468        handle
469    }
470
471    /// Returns `true` if an asset with the label `label` exists in this context.
472    ///
473    /// See [`AssetPath`] for more on labeled assets.
474    pub fn has_labeled_asset<'b>(&self, label: impl Into<CowArc<'b, str>>) -> bool {
475        let path = self.asset_path.clone().with_label(label.into());
476        !self.asset_server.get_handles_untyped(&path).is_empty()
477    }
478
479    /// "Finishes" this context by populating the final [`Asset`] value.
480    pub fn finish<A: Asset>(mut self, value: A) -> LoadedAsset<A> {
481        // At this point, we assume the asset/subasset is "locked in" and won't be changed, so we
482        // can ensure all the dependencies are included (in case a handle was used without loading
483        // it through this `LoadContext`). If in the future we provide an API for mutating assets in
484        // `LoadedAsset`, `ErasedLoadedAsset`, or `LoadContext` (for mutating existing subassets),
485        // we should move this to some point after those mutations are not possible. This spot is
486        // convenient because we still have access to the static type of `A`.
487        value.visit_dependencies(&mut |asset_id| {
488            let (type_id, index) = match asset_id {
489                UntypedAssetId::Index { type_id, index } => (type_id, index),
490                // UUID assets can't be loaded anyway, so just ignore this ID.
491                UntypedAssetId::Uuid { .. } => return,
492            };
493            self.dependencies
494                .insert(ErasedAssetIndex { index, type_id });
495        });
496        LoadedAsset {
497            value,
498            dependencies: self.dependencies,
499            loader_dependencies: self.loader_dependencies,
500            labeled_assets: self.labeled_assets,
501        }
502    }
503
504    /// Gets the source asset path for this load context.
505    pub fn path(&self) -> &AssetPath<'static> {
506        &self.asset_path
507    }
508
509    /// Reads the asset at the given path and returns its bytes
510    pub async fn read_asset_bytes<'b, 'c>(
511        &'b mut self,
512        path: impl Into<AssetPath<'c>>,
513    ) -> Result<Vec<u8>, ReadAssetBytesError> {
514        let path = path.into();
515        let source = self.asset_server.get_source(path.source())?;
516        let asset_reader = match self.asset_server.mode() {
517            AssetServerMode::Unprocessed => source.reader(),
518            AssetServerMode::Processed => source.processed_reader()?,
519        };
520        let mut reader = asset_reader
521            .read(path.path(), ReaderRequiredFeatures::default())
522            .await?;
523        let hash = if self.populate_hashes {
524            // NOTE: ensure meta is read while the asset bytes reader is still active to ensure transactionality
525            // See `ProcessorGatedReader` for more info
526            let meta_bytes = asset_reader.read_meta_bytes(path.path()).await?;
527            let minimal: ProcessedInfoMinimal = ron::de::from_bytes(&meta_bytes)
528                .map_err(DeserializeMetaError::DeserializeMinimal)?;
529            let processed_info = minimal
530                .processed_info
531                .ok_or(ReadAssetBytesError::MissingAssetHash)?;
532            processed_info.full_hash
533        } else {
534            Default::default()
535        };
536        let mut bytes = Vec::new();
537        reader
538            .read_to_end(&mut bytes)
539            .await
540            .map_err(|source| ReadAssetBytesError::Io {
541                path: path.path().to_path_buf(),
542                source,
543            })?;
544        self.loader_dependencies.insert(path.clone_owned(), hash);
545        Ok(bytes)
546    }
547
548    /// Returns a handle to an asset of type `A` with the label `label`. This [`LoadContext`] must produce an asset of the
549    /// given type and the given label or the dependencies of this asset will never be considered "fully loaded". However you
550    /// can call this method before _or_ after adding the labeled asset.
551    pub fn get_label_handle<'b, A: Asset>(
552        &mut self,
553        label: impl Into<CowArc<'b, str>>,
554    ) -> Handle<A> {
555        let path = self.asset_path.clone().with_label(label);
556        let handle = self.asset_server.get_or_create_path_handle::<A>(path, None);
557        // `get_or_create_path_handle` always returns a Strong variant, so we are safe to unwrap.
558        let index = (&handle).try_into().unwrap();
559        self.dependencies.insert(index);
560        handle
561    }
562
563    pub(crate) async fn load_direct_internal(
564        &mut self,
565        path: AssetPath<'static>,
566        settings: &dyn Settings,
567        loader: &dyn ErasedAssetLoader,
568        reader: &mut dyn Reader,
569        processed_info: Option<&ProcessedInfo>,
570    ) -> Result<ErasedLoadedAsset, LoadDirectError> {
571        let loaded_asset = self
572            .asset_server
573            .load_with_settings_loader_and_reader(
574                &path,
575                settings,
576                loader,
577                reader,
578                self.should_load_dependencies,
579                self.populate_hashes,
580            )
581            .await
582            .map_err(|error| LoadDirectError::LoadError {
583                dependency: path.clone(),
584                error,
585            })?;
586        let hash = processed_info.map(|i| i.full_hash).unwrap_or_default();
587        self.loader_dependencies.insert(path, hash);
588        Ok(loaded_asset)
589    }
590
591    /// Create a builder for loading nested assets in this context.
592    #[must_use]
593    pub fn loader(&mut self) -> NestedLoader<'a, '_, StaticTyped, Deferred> {
594        NestedLoader::new(self)
595    }
596
597    /// Retrieves a handle for the asset at the given path and adds that path as a dependency of the asset.
598    /// If the current context is a normal [`AssetServer::load`], an actual asset load will be kicked off immediately, which ensures the load happens
599    /// as soon as possible.
600    /// "Normal loads" kicked from within a normal Bevy App will generally configure the context to kick off loads immediately.
601    /// If the current context is configured to not load dependencies automatically (ex: [`AssetProcessor`](crate::processor::AssetProcessor)),
602    /// a load will not be kicked off automatically. It is then the calling context's responsibility to begin a load if necessary.
603    ///
604    /// If you need to override asset settings, asset type, or load directly, please see [`LoadContext::loader`].
605    pub fn load<'b, A: Asset>(&mut self, path: impl Into<AssetPath<'b>>) -> Handle<A> {
606        self.loader().load(path)
607    }
608}
609
610/// An error produced when calling [`LoadContext::read_asset_bytes`]
611#[derive(Error, Debug)]
612pub enum ReadAssetBytesError {
613    #[error(transparent)]
614    DeserializeMetaError(#[from] DeserializeMetaError),
615    #[error(transparent)]
616    AssetReaderError(#[from] AssetReaderError),
617    #[error(transparent)]
618    MissingAssetSourceError(#[from] MissingAssetSourceError),
619    #[error(transparent)]
620    MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
621    /// Encountered an I/O error while loading an asset.
622    #[error("Encountered an io error while loading asset at `{}`: {source}", path.display())]
623    Io {
624        path: PathBuf,
625        source: std::io::Error,
626    },
627    #[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")]
628    MissingAssetHash,
629}