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,
9};
10use std::any::TypeId;
11use std::sync::Arc;
12
13// Utility type for handling the sources of reader references
14enum ReaderRef<'a, 'b> {
15    Borrowed(&'a mut Reader<'b>),
16    Boxed(Box<Reader<'b>>),
17}
18
19impl<'a, 'b> ReaderRef<'a, 'b> {
20    pub fn as_mut(&mut self) -> &mut Reader {
21        match self {
22            ReaderRef::Borrowed(r) => r,
23            ReaderRef::Boxed(b) => &mut *b,
24        }
25    }
26}
27
28/// A builder for loading nested assets inside a `LoadContext`.
29///
30/// # Lifetimes
31/// - `ctx`: the lifetime of the associated [`AssetServer`] reference
32/// - `builder`: the lifetime of the temporary builder structs
33pub struct NestedLoader<'ctx, 'builder> {
34    load_context: &'builder mut LoadContext<'ctx>,
35    meta_transform: Option<MetaTransform>,
36    asset_type_id: Option<TypeId>,
37}
38
39impl<'ctx, 'builder> NestedLoader<'ctx, 'builder> {
40    pub(crate) fn new(
41        load_context: &'builder mut LoadContext<'ctx>,
42    ) -> NestedLoader<'ctx, 'builder> {
43        NestedLoader {
44            load_context,
45            meta_transform: None,
46            asset_type_id: None,
47        }
48    }
49
50    fn with_transform(
51        mut self,
52        transform: impl Fn(&mut dyn AssetMetaDyn) + Send + Sync + 'static,
53    ) -> Self {
54        if let Some(prev_transform) = self.meta_transform {
55            self.meta_transform = Some(Box::new(move |meta| {
56                prev_transform(meta);
57                transform(meta);
58            }));
59        } else {
60            self.meta_transform = Some(Box::new(transform));
61        }
62        self
63    }
64
65    /// Configure the settings used to load the asset.
66    ///
67    /// If the settings type `S` does not match the settings expected by `A`'s asset loader, an error will be printed to the log
68    /// and the asset load will fail.
69    #[must_use]
70    pub fn with_settings<S: Settings>(
71        self,
72        settings: impl Fn(&mut S) + Send + Sync + 'static,
73    ) -> Self {
74        self.with_transform(move |meta| meta_transform_settings(meta, &settings))
75    }
76
77    /// Specify the output asset type.
78    #[must_use]
79    pub fn with_asset_type<A: Asset>(mut self) -> Self {
80        self.asset_type_id = Some(TypeId::of::<A>());
81        self
82    }
83
84    /// Specify the output asset type.
85    #[must_use]
86    pub fn with_asset_type_id(mut self, asset_type_id: TypeId) -> Self {
87        self.asset_type_id = Some(asset_type_id);
88        self
89    }
90
91    /// Load assets directly, rather than creating handles.
92    #[must_use]
93    pub fn direct<'c>(self) -> DirectNestedLoader<'ctx, 'builder, 'c> {
94        DirectNestedLoader {
95            base: self,
96            reader: None,
97        }
98    }
99
100    /// Load assets without static type information.
101    ///
102    /// If you need to specify the type of asset, but cannot do it statically,
103    /// use `.with_asset_type_id()`.
104    #[must_use]
105    pub fn untyped(self) -> UntypedNestedLoader<'ctx, 'builder> {
106        UntypedNestedLoader { base: self }
107    }
108
109    /// Retrieves a handle for the asset at the given path and adds that path as a dependency of the asset.
110    /// If the current context is a normal [`AssetServer::load`], an actual asset load will be kicked off immediately, which ensures the load happens
111    /// as soon as possible.
112    /// "Normal loads" kicked from within a normal Bevy App will generally configure the context to kick off loads immediately.
113    /// If the current context is configured to not load dependencies automatically (ex: [`AssetProcessor`](crate::processor::AssetProcessor)),
114    /// a load will not be kicked off automatically. It is then the calling context's responsibility to begin a load if necessary.
115    pub fn load<'c, A: Asset>(self, path: impl Into<AssetPath<'c>>) -> Handle<A> {
116        let path = path.into().to_owned();
117        let handle = if self.load_context.should_load_dependencies {
118            self.load_context
119                .asset_server
120                .load_with_meta_transform(path, self.meta_transform, ())
121        } else {
122            self.load_context
123                .asset_server
124                .get_or_create_path_handle(path, None)
125        };
126        self.load_context.dependencies.insert(handle.id().untyped());
127        handle
128    }
129}
130
131/// A builder for loading untyped nested assets inside a [`LoadContext`].
132///
133/// # Lifetimes
134/// - `ctx`: the lifetime of the associated [`AssetServer`] reference
135/// - `builder`: the lifetime of the temporary builder structs
136pub struct UntypedNestedLoader<'ctx, 'builder> {
137    base: NestedLoader<'ctx, 'builder>,
138}
139
140impl<'ctx, 'builder> UntypedNestedLoader<'ctx, 'builder> {
141    /// Retrieves a handle for the asset at the given path and adds that path as a dependency of the asset without knowing its type.
142    pub fn load<'p>(self, path: impl Into<AssetPath<'p>>) -> Handle<LoadedUntypedAsset> {
143        let path = path.into().to_owned();
144        let handle = if self.base.load_context.should_load_dependencies {
145            self.base
146                .load_context
147                .asset_server
148                .load_untyped_with_meta_transform(path, self.base.meta_transform)
149        } else {
150            self.base
151                .load_context
152                .asset_server
153                .get_or_create_path_handle(path, self.base.meta_transform)
154        };
155        self.base
156            .load_context
157            .dependencies
158            .insert(handle.id().untyped());
159        handle
160    }
161}
162
163/// A builder for directly loading nested assets inside a `LoadContext`.
164///
165/// # Lifetimes
166/// - `ctx`: the lifetime of the associated [`AssetServer`] reference
167/// - `builder`: the lifetime of the temporary builder structs
168/// - `reader`: the lifetime of the [`Reader`] reference used to read the asset data
169pub struct DirectNestedLoader<'ctx, 'builder, 'reader> {
170    base: NestedLoader<'ctx, 'builder>,
171    reader: Option<&'builder mut Reader<'reader>>,
172}
173
174impl<'ctx: 'reader, 'builder, 'reader> DirectNestedLoader<'ctx, 'builder, 'reader> {
175    /// Specify the reader to use to read the asset data.
176    #[must_use]
177    pub fn with_reader(mut self, reader: &'builder mut Reader<'reader>) -> Self {
178        self.reader = Some(reader);
179        self
180    }
181
182    /// Load the asset without providing static type information.
183    ///
184    /// If you need to specify the type of asset, but cannot do it statically,
185    /// use `.with_asset_type_id()`.
186    #[must_use]
187    pub fn untyped(self) -> UntypedDirectNestedLoader<'ctx, 'builder, 'reader> {
188        UntypedDirectNestedLoader { base: self }
189    }
190
191    async fn load_internal(
192        self,
193        path: &AssetPath<'static>,
194    ) -> Result<(Arc<dyn ErasedAssetLoader>, ErasedLoadedAsset), LoadDirectError> {
195        let (mut meta, loader, mut reader) = if let Some(reader) = self.reader {
196            let loader = if let Some(asset_type_id) = self.base.asset_type_id {
197                self.base
198                    .load_context
199                    .asset_server
200                    .get_asset_loader_with_asset_type_id(asset_type_id)
201                    .await
202                    .map_err(|error| LoadDirectError {
203                        dependency: path.clone(),
204                        error: error.into(),
205                    })?
206            } else {
207                self.base
208                    .load_context
209                    .asset_server
210                    .get_path_asset_loader(path)
211                    .await
212                    .map_err(|error| LoadDirectError {
213                        dependency: path.clone(),
214                        error: error.into(),
215                    })?
216            };
217            let meta = loader.default_meta();
218            (meta, loader, ReaderRef::Borrowed(reader))
219        } else {
220            let (meta, loader, reader) = self
221                .base
222                .load_context
223                .asset_server
224                .get_meta_loader_and_reader(path, self.base.asset_type_id)
225                .await
226                .map_err(|error| LoadDirectError {
227                    dependency: path.clone(),
228                    error,
229                })?;
230            (meta, loader, ReaderRef::Boxed(reader))
231        };
232
233        if let Some(meta_transform) = self.base.meta_transform {
234            meta_transform(&mut *meta);
235        }
236
237        let asset = self
238            .base
239            .load_context
240            .load_direct_internal(path.clone(), meta, &*loader, reader.as_mut())
241            .await?;
242        Ok((loader, asset))
243    }
244
245    /// Loads the asset at the given `path` directly. This is an async function that will wait until the asset is fully loaded before
246    /// returning. Use this if you need the _value_ of another asset in order to load the current asset. For example, if you are
247    /// deriving a new asset from the referenced asset, or you are building a collection of assets. This will add the `path` as a
248    /// "load dependency".
249    ///
250    /// If the current loader is used in a [`Process`] "asset preprocessor", such as a [`LoadTransformAndSave`] preprocessor,
251    /// changing a "load dependency" will result in re-processing of the asset.
252    ///
253    /// [`Process`]: crate::processor::Process
254    /// [`LoadTransformAndSave`]: crate::processor::LoadTransformAndSave
255    pub async fn load<'p, A: Asset>(
256        mut self,
257        path: impl Into<AssetPath<'p>>,
258    ) -> Result<LoadedAsset<A>, LoadDirectError> {
259        self.base.asset_type_id = Some(TypeId::of::<A>());
260        let path = path.into().into_owned();
261        self.load_internal(&path)
262            .await
263            .and_then(move |(loader, untyped_asset)| {
264                untyped_asset.downcast::<A>().map_err(|_| LoadDirectError {
265                    dependency: path.clone(),
266                    error: AssetLoadError::RequestedHandleTypeMismatch {
267                        path,
268                        requested: TypeId::of::<A>(),
269                        actual_asset_name: loader.asset_type_name(),
270                        loader_name: loader.type_name(),
271                    },
272                })
273            })
274    }
275}
276
277/// A builder for directly loading untyped nested assets inside a `LoadContext`.
278///
279/// # Lifetimes
280/// - `ctx`: the lifetime of the associated [`AssetServer`] reference
281/// - `builder`: the lifetime of the temporary builder structs
282/// - `reader`: the lifetime of the [`Reader`] reference used to read the asset data
283pub struct UntypedDirectNestedLoader<'ctx, 'builder, 'reader> {
284    base: DirectNestedLoader<'ctx, 'builder, 'reader>,
285}
286
287impl<'ctx: 'reader, 'builder, 'reader> UntypedDirectNestedLoader<'ctx, 'builder, 'reader> {
288    /// Loads the asset at the given `path` directly. This is an async function that will wait until the asset is fully loaded before
289    /// returning. Use this if you need the _value_ of another asset in order to load the current asset. For example, if you are
290    /// deriving a new asset from the referenced asset, or you are building a collection of assets. This will add the `path` as a
291    /// "load dependency".
292    ///
293    /// If the current loader is used in a [`Process`] "asset preprocessor", such as a [`LoadTransformAndSave`] preprocessor,
294    /// changing a "load dependency" will result in re-processing of the asset.
295    ///
296    /// [`Process`]: crate::processor::Process
297    /// [`LoadTransformAndSave`]: crate::processor::LoadTransformAndSave
298    pub async fn load<'p>(
299        self,
300        path: impl Into<AssetPath<'p>>,
301    ) -> Result<ErasedLoadedAsset, LoadDirectError> {
302        let path = path.into().into_owned();
303        self.base.load_internal(&path).await.map(|(_, asset)| asset)
304    }
305}