bevy_asset/
loader.rs

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