bevy_asset/
loader_builders.rs

1//! Implementations of the builder-pattern used for loading dependent assets via
2//! [`LoadContext::loader`].
3
4use crate::{
5    io::Reader,
6    meta::{meta_transform_settings, AssetMetaDyn, MetaTransform, Settings},
7    Asset, AssetLoadError, AssetPath, ErasedAssetLoader, ErasedLoadedAsset, Handle, LoadContext,
8    LoadDirectError, LoadedAsset, LoadedUntypedAsset, UntypedHandle,
9};
10use alloc::sync::Arc;
11use core::any::TypeId;
12
13// Utility type for handling the sources of reader references
14enum ReaderRef<'a> {
15    Borrowed(&'a mut dyn Reader),
16    Boxed(Box<dyn Reader + 'a>),
17}
18
19impl ReaderRef<'_> {
20    pub fn as_mut(&mut self) -> &mut dyn Reader {
21        match self {
22            ReaderRef::Borrowed(r) => &mut **r,
23            ReaderRef::Boxed(b) => &mut **b,
24        }
25    }
26}
27
28/// A builder for loading nested assets inside a [`LoadContext`].
29///
30/// # Loader state
31///
32/// The type parameters `T` and `M` determine how this will load assets:
33/// - `T`: the typing of this loader. How do we know what type of asset to load?
34///
35///   See [`StaticTyped`] (the default), [`DynamicTyped`], and [`UnknownTyped`].
36///
37/// - `M`: the load mode. Do we want to load this asset right now (in which case
38///   you will have to `await` the operation), or do we just want a [`Handle`],
39///   and leave the actual asset loading to later?
40///
41///   See [`Deferred`] (the default) and [`Immediate`].
42///
43/// When configuring this builder, you can freely switch between these modes
44/// via functions like [`deferred`] and [`immediate`].
45///
46/// ## Typing
47///
48/// To inform the loader of what type of asset to load:
49/// - in [`StaticTyped`]: statically providing a type parameter `A: Asset` to
50///   [`load`].
51///
52///   This is the simplest way to get a [`Handle<A>`] to the loaded asset, as
53///   long as you know the type of `A` at compile time.
54///
55/// - in [`DynamicTyped`]: providing the [`TypeId`] of the asset at runtime.
56///
57///   If you know the type ID of the asset at runtime, but not at compile time,
58///   use [`with_dynamic_type`] followed by [`load`] to start loading an asset
59///   of that type. This lets you get an [`UntypedHandle`] (via [`Deferred`]),
60///   or a [`ErasedLoadedAsset`] (via [`Immediate`]).
61///
62/// - in [`UnknownTyped`]: loading either a type-erased version of the asset
63///   ([`ErasedLoadedAsset`]), or a handle *to a handle* of the actual asset
64///   ([`LoadedUntypedAsset`]).
65///
66///   If you have no idea what type of asset you will be loading (not even at
67///   runtime with a [`TypeId`]), use this.
68///
69/// ## Load mode
70///
71/// To inform the loader how you want to load the asset:
72/// - in [`Deferred`]: when you request to load the asset, you get a [`Handle`]
73///   for it, but the actual loading won't be completed until later.
74///
75///   Use this if you only need a [`Handle`] or [`UntypedHandle`].
76///
77/// - in [`Immediate`]: the load request will load the asset right then and
78///   there, waiting until the asset is fully loaded and giving you access to
79///   it.
80///
81///   Note that this requires you to `await` a future, so you must be in an
82///   async context to use direct loading. In an asset loader, you will be in
83///   an async context.
84///
85///   Use this if you need the *value* of another asset in order to load the
86///   current asset. For example, if you are deriving a new asset from the
87///   referenced asset, or you are building a collection of assets. This will
88///   add the path of the asset as a "load dependency".
89///
90///   If the current loader is used in a [`Process`] "asset preprocessor",
91///   such as a [`LoadTransformAndSave`] preprocessor, changing a "load
92///   dependency" will result in re-processing of the asset.
93///
94/// # Load kickoff
95///
96/// If the current context is a normal [`AssetServer::load`], an actual asset
97/// load will be kicked off immediately, which ensures the load happens as soon
98/// as possible. "Normal loads" kicked from within a normal Bevy App will
99/// generally configure the context to kick off loads immediately.
100///
101/// If the current context is configured to not load dependencies automatically
102/// (ex: [`AssetProcessor`]), a load will not be kicked off automatically. It is
103/// then the calling context's responsibility to begin a load if necessary.
104///
105/// # Lifetimes
106///
107/// - `ctx`: the lifetime of the associated [`AssetServer`](crate::AssetServer) reference
108/// - `builder`: the lifetime of the temporary builder structs
109///
110/// [`deferred`]: Self::deferred
111/// [`immediate`]: Self::immediate
112/// [`load`]: Self::load
113/// [`with_dynamic_type`]: Self::with_dynamic_type
114/// [`AssetServer::load`]: crate::AssetServer::load
115/// [`AssetProcessor`]: crate::processor::AssetProcessor
116/// [`Process`]: crate::processor::Process
117/// [`LoadTransformAndSave`]: crate::processor::LoadTransformAndSave
118pub struct NestedLoader<'ctx, 'builder, T, M> {
119    load_context: &'builder mut LoadContext<'ctx>,
120    meta_transform: Option<MetaTransform>,
121    typing: T,
122    mode: M,
123}
124
125mod sealed {
126    pub trait Typing {}
127
128    pub trait Mode {}
129}
130
131/// [`NestedLoader`] will be provided the type of asset as a type parameter on
132/// [`load`].
133///
134/// [`load`]: NestedLoader::load
135pub struct StaticTyped(());
136
137impl sealed::Typing for StaticTyped {}
138
139/// [`NestedLoader`] has been configured with info on what type of asset to load
140/// at runtime.
141pub struct DynamicTyped {
142    asset_type_id: TypeId,
143}
144
145impl sealed::Typing for DynamicTyped {}
146
147/// [`NestedLoader`] does not know what type of asset it will be loading.
148pub struct UnknownTyped(());
149
150impl sealed::Typing for UnknownTyped {}
151
152/// [`NestedLoader`] will create and return asset handles immediately, but only
153/// actually load the asset later.
154pub struct Deferred(());
155
156impl sealed::Mode for Deferred {}
157
158/// [`NestedLoader`] will immediately load an asset when requested.
159pub struct Immediate<'builder, 'reader> {
160    reader: Option<&'builder mut (dyn Reader + 'reader)>,
161}
162
163impl sealed::Mode for Immediate<'_, '_> {}
164
165// common to all states
166
167impl<'ctx, 'builder> NestedLoader<'ctx, 'builder, StaticTyped, Deferred> {
168    pub(crate) fn new(load_context: &'builder mut LoadContext<'ctx>) -> Self {
169        NestedLoader {
170            load_context,
171            meta_transform: None,
172            typing: StaticTyped(()),
173            mode: Deferred(()),
174        }
175    }
176}
177
178impl<'ctx, 'builder, T: sealed::Typing, M: sealed::Mode> NestedLoader<'ctx, 'builder, T, M> {
179    fn with_transform(
180        mut self,
181        transform: impl Fn(&mut dyn AssetMetaDyn) + Send + Sync + 'static,
182    ) -> Self {
183        if let Some(prev_transform) = self.meta_transform {
184            self.meta_transform = Some(Box::new(move |meta| {
185                prev_transform(meta);
186                transform(meta);
187            }));
188        } else {
189            self.meta_transform = Some(Box::new(transform));
190        }
191        self
192    }
193
194    /// Configure the settings used to load the asset.
195    ///
196    /// If the settings type `S` does not match the settings expected by `A`'s asset loader, an error will be printed to the log
197    /// and the asset load will fail.
198    #[must_use]
199    pub fn with_settings<S: Settings>(
200        self,
201        settings: impl Fn(&mut S) + Send + Sync + 'static,
202    ) -> Self {
203        self.with_transform(move |meta| meta_transform_settings(meta, &settings))
204    }
205
206    // convert between `T`s
207
208    /// When [`load`]ing, you must pass in the asset type as a type parameter
209    /// statically.
210    ///
211    /// If you don't know the type statically (at compile time), consider
212    /// [`with_dynamic_type`] or [`with_unknown_type`].
213    ///
214    /// [`load`]: Self::load
215    /// [`with_dynamic_type`]: Self::with_dynamic_type
216    /// [`with_unknown_type`]: Self::with_unknown_type
217    #[must_use]
218    pub fn with_static_type(self) -> NestedLoader<'ctx, 'builder, StaticTyped, M> {
219        NestedLoader {
220            load_context: self.load_context,
221            meta_transform: self.meta_transform,
222            typing: StaticTyped(()),
223            mode: self.mode,
224        }
225    }
226
227    /// When [`load`]ing, the loader will attempt to load an asset with the
228    /// given [`TypeId`].
229    ///
230    /// [`load`]: Self::load
231    #[must_use]
232    pub fn with_dynamic_type(
233        self,
234        asset_type_id: TypeId,
235    ) -> NestedLoader<'ctx, 'builder, DynamicTyped, M> {
236        NestedLoader {
237            load_context: self.load_context,
238            meta_transform: self.meta_transform,
239            typing: DynamicTyped { asset_type_id },
240            mode: self.mode,
241        }
242    }
243
244    /// When [`load`]ing, we will infer what type of asset to load from
245    /// metadata.
246    ///
247    /// [`load`]: Self::load
248    #[must_use]
249    pub fn with_unknown_type(self) -> NestedLoader<'ctx, 'builder, UnknownTyped, M> {
250        NestedLoader {
251            load_context: self.load_context,
252            meta_transform: self.meta_transform,
253            typing: UnknownTyped(()),
254            mode: self.mode,
255        }
256    }
257
258    // convert between `M`s
259
260    /// When [`load`]ing, create only asset handles, rather than returning the
261    /// actual asset.
262    ///
263    /// [`load`]: Self::load
264    pub fn deferred(self) -> NestedLoader<'ctx, 'builder, T, Deferred> {
265        NestedLoader {
266            load_context: self.load_context,
267            meta_transform: self.meta_transform,
268            typing: self.typing,
269            mode: Deferred(()),
270        }
271    }
272
273    /// The [`load`] call itself will load an asset, rather than scheduling the
274    /// loading to happen later.
275    ///
276    /// This gives you access to the loaded asset, but requires you to be in an
277    /// async context, and be able to `await` the resulting future.
278    ///
279    /// [`load`]: Self::load
280    #[must_use]
281    pub fn immediate<'c>(self) -> NestedLoader<'ctx, 'builder, T, Immediate<'builder, 'c>> {
282        NestedLoader {
283            load_context: self.load_context,
284            meta_transform: self.meta_transform,
285            typing: self.typing,
286            mode: Immediate { reader: None },
287        }
288    }
289}
290
291// deferred loading logic
292
293impl NestedLoader<'_, '_, StaticTyped, Deferred> {
294    /// Retrieves a handle for the asset at the given path and adds that path as
295    /// a dependency of this asset.
296    ///
297    /// This requires you to know the type of asset statically.
298    /// - If you have runtime info for what type of asset you're loading (e.g. a
299    ///   [`TypeId`]), use [`with_dynamic_type`].
300    /// - If you do not know at all what type of asset you're loading, use
301    ///   [`with_unknown_type`].
302    ///
303    /// [`with_dynamic_type`]: Self::with_dynamic_type
304    /// [`with_unknown_type`]: Self::with_unknown_type
305    pub fn load<'c, A: Asset>(self, path: impl Into<AssetPath<'c>>) -> Handle<A> {
306        let path = path.into().to_owned();
307        let handle = if self.load_context.should_load_dependencies {
308            self.load_context
309                .asset_server
310                .load_with_meta_transform(path, self.meta_transform, ())
311        } else {
312            self.load_context
313                .asset_server
314                .get_or_create_path_handle(path, None)
315        };
316        self.load_context.dependencies.insert(handle.id().untyped());
317        handle
318    }
319}
320
321impl NestedLoader<'_, '_, DynamicTyped, Deferred> {
322    /// Retrieves a handle for the asset at the given path and adds that path as
323    /// a dependency of this asset.
324    ///
325    /// This requires you to pass in the asset type ID into
326    /// [`with_dynamic_type`].
327    ///
328    /// [`with_dynamic_type`]: Self::with_dynamic_type
329    pub fn load<'p>(self, path: impl Into<AssetPath<'p>>) -> UntypedHandle {
330        let path = path.into().to_owned();
331        let handle = if self.load_context.should_load_dependencies {
332            self.load_context
333                .asset_server
334                .load_erased_with_meta_transform(
335                    path,
336                    self.typing.asset_type_id,
337                    self.meta_transform,
338                    (),
339                )
340        } else {
341            self.load_context
342                .asset_server
343                .get_or_create_path_handle_erased(
344                    path,
345                    self.typing.asset_type_id,
346                    self.meta_transform,
347                )
348        };
349        self.load_context.dependencies.insert(handle.id());
350        handle
351    }
352}
353
354impl NestedLoader<'_, '_, UnknownTyped, Deferred> {
355    /// Retrieves a handle for the asset at the given path and adds that path as
356    /// a dependency of this asset.
357    ///
358    /// This will infer the asset type from metadata.
359    pub fn load<'p>(self, path: impl Into<AssetPath<'p>>) -> Handle<LoadedUntypedAsset> {
360        let path = path.into().to_owned();
361        let handle = if self.load_context.should_load_dependencies {
362            self.load_context
363                .asset_server
364                .load_unknown_type_with_meta_transform(path, self.meta_transform)
365        } else {
366            self.load_context
367                .asset_server
368                .get_or_create_path_handle(path, self.meta_transform)
369        };
370        self.load_context.dependencies.insert(handle.id().untyped());
371        handle
372    }
373}
374
375// immediate loading logic
376
377impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>> {
378    /// Specify the reader to use to read the asset data.
379    #[must_use]
380    pub fn with_reader(mut self, reader: &'builder mut (dyn Reader + 'reader)) -> Self {
381        self.mode.reader = Some(reader);
382        self
383    }
384
385    async fn load_internal(
386        self,
387        path: &AssetPath<'static>,
388        asset_type_id: Option<TypeId>,
389    ) -> Result<(Arc<dyn ErasedAssetLoader>, ErasedLoadedAsset), LoadDirectError> {
390        let (mut meta, loader, mut reader) = if let Some(reader) = self.mode.reader {
391            let loader = if let Some(asset_type_id) = asset_type_id {
392                self.load_context
393                    .asset_server
394                    .get_asset_loader_with_asset_type_id(asset_type_id)
395                    .await
396                    .map_err(|error| LoadDirectError {
397                        dependency: path.clone(),
398                        error: error.into(),
399                    })?
400            } else {
401                self.load_context
402                    .asset_server
403                    .get_path_asset_loader(path)
404                    .await
405                    .map_err(|error| LoadDirectError {
406                        dependency: path.clone(),
407                        error: error.into(),
408                    })?
409            };
410            let meta = loader.default_meta();
411            (meta, loader, ReaderRef::Borrowed(reader))
412        } else {
413            let (meta, loader, reader) = self
414                .load_context
415                .asset_server
416                .get_meta_loader_and_reader(path, asset_type_id)
417                .await
418                .map_err(|error| LoadDirectError {
419                    dependency: path.clone(),
420                    error,
421                })?;
422            (meta, loader, ReaderRef::Boxed(reader))
423        };
424
425        if let Some(meta_transform) = self.meta_transform {
426            meta_transform(&mut *meta);
427        }
428
429        let asset = self
430            .load_context
431            .load_direct_internal(path.clone(), meta, &*loader, reader.as_mut())
432            .await?;
433        Ok((loader, asset))
434    }
435}
436
437impl NestedLoader<'_, '_, StaticTyped, Immediate<'_, '_>> {
438    /// Attempts to load the asset at the given `path` immediately.
439    ///
440    /// This requires you to know the type of asset statically.
441    /// - If you have runtime info for what type of asset you're loading (e.g. a
442    ///   [`TypeId`]), use [`with_dynamic_type`].
443    /// - If you do not know at all what type of asset you're loading, use
444    ///   [`with_unknown_type`].
445    ///
446    /// [`with_dynamic_type`]: Self::with_dynamic_type
447    /// [`with_unknown_type`]: Self::with_unknown_type
448    pub async fn load<'p, A: Asset>(
449        self,
450        path: impl Into<AssetPath<'p>>,
451    ) -> Result<LoadedAsset<A>, LoadDirectError> {
452        let path = path.into().into_owned();
453        self.load_internal(&path, Some(TypeId::of::<A>()))
454            .await
455            .and_then(move |(loader, untyped_asset)| {
456                untyped_asset.downcast::<A>().map_err(|_| LoadDirectError {
457                    dependency: path.clone(),
458                    error: AssetLoadError::RequestedHandleTypeMismatch {
459                        path,
460                        requested: TypeId::of::<A>(),
461                        actual_asset_name: loader.asset_type_name(),
462                        loader_name: loader.type_name(),
463                    },
464                })
465            })
466    }
467}
468
469impl NestedLoader<'_, '_, DynamicTyped, Immediate<'_, '_>> {
470    /// Attempts to load the asset at the given `path` immediately.
471    ///
472    /// This requires you to pass in the asset type ID into
473    /// [`with_dynamic_type`].
474    ///
475    /// [`with_dynamic_type`]: Self::with_dynamic_type
476    pub async fn load<'p>(
477        self,
478        path: impl Into<AssetPath<'p>>,
479    ) -> Result<ErasedLoadedAsset, LoadDirectError> {
480        let path = path.into().into_owned();
481        let asset_type_id = Some(self.typing.asset_type_id);
482        self.load_internal(&path, asset_type_id)
483            .await
484            .map(|(_, asset)| asset)
485    }
486}
487
488impl NestedLoader<'_, '_, UnknownTyped, Immediate<'_, '_>> {
489    /// Attempts to load the asset at the given `path` immediately.
490    ///
491    /// This will infer the asset type from metadata.
492    pub async fn load<'p>(
493        self,
494        path: impl Into<AssetPath<'p>>,
495    ) -> Result<ErasedLoadedAsset, LoadDirectError> {
496        let path = path.into().into_owned();
497        self.load_internal(&path, None)
498            .await
499            .map(|(_, asset)| asset)
500    }
501}