Skip to main content

bevy_asset/
loader_builders.rs

1//! Implementations of the builder-pattern used for loading dependent assets via
2//! [`LoadContext::load_builder`].
3
4use crate::{
5    io::Reader,
6    meta::{loader_settings_meta_transform, MetaTransform, Settings},
7    Asset, AssetLoadError, AssetPath, ErasedAssetLoader, ErasedLoadedAsset, Handle, LoadContext,
8    LoadDirectError, LoadedAsset, LoadedUntypedAsset, UntypedHandle,
9};
10use alloc::{borrow::ToOwned, boxed::Box, sync::Arc};
11use core::any::{type_name, TypeId};
12use std::path::Path;
13use tracing::error;
14
15// Utility type for handling the sources of reader references
16enum ReaderRef<'a> {
17    Borrowed(&'a mut dyn Reader),
18    Boxed(Box<dyn Reader + 'a>),
19}
20
21impl ReaderRef<'_> {
22    pub fn as_mut(&mut self) -> &mut dyn Reader {
23        match self {
24            ReaderRef::Borrowed(r) => &mut **r,
25            ReaderRef::Boxed(b) => &mut **b,
26        }
27    }
28}
29
30/// A builder for loading nested assets inside a [`LoadContext`].
31pub struct NestedLoadBuilder<'ctx, 'builder> {
32    load_context: &'builder mut LoadContext<'ctx>,
33    /// A function to modify the meta for an asset loader. In practice, this just mutates the loader
34    /// settings of a load.
35    meta_transform: Option<MetaTransform>,
36    /// Whether unapproved paths are allowed to be loaded.
37    override_unapproved: bool,
38}
39
40impl<'ctx, 'builder> NestedLoadBuilder<'ctx, 'builder> {
41    pub(crate) fn new(load_context: &'builder mut LoadContext<'ctx>) -> Self {
42        NestedLoadBuilder {
43            load_context,
44            meta_transform: None,
45            override_unapproved: false,
46        }
47    }
48}
49
50impl<'ctx, 'builder> NestedLoadBuilder<'ctx, 'builder> {
51    /// Use the given `settings` function to override the asset's [`AssetLoader`] settings.
52    ///
53    /// The type `S` must match the configured [`AssetLoader::Settings`] or `settings` changes will
54    /// be ignored and an error will be printed to the log.
55    ///
56    /// Repeatedly calling this method will "chain" the operations (matching the order of these
57    /// calls).
58    ///
59    /// [`AssetLoader`]: crate::AssetLoader
60    /// [`AssetLoader::Settings`]: crate::AssetLoader::Settings
61    #[must_use]
62    pub fn with_settings<S: Settings>(
63        mut self,
64        settings: impl Fn(&mut S) + Send + Sync + 'static,
65    ) -> Self {
66        let new_transform = loader_settings_meta_transform(settings);
67        if let Some(prev_transform) = self.meta_transform.take() {
68            self.meta_transform = Some(Box::new(move |meta| {
69                prev_transform(meta);
70                new_transform(meta);
71            }));
72        } else {
73            self.meta_transform = Some(new_transform);
74        }
75        self
76    }
77
78    /// Loads from unapproved paths are allowed, even if
79    /// [`AssetPlugin::unapproved_path_mode`](crate::AssetPlugin::unapproved_path_mode) is
80    /// [`Deny`](crate::UnapprovedPathMode::Deny).
81    #[must_use = "the load doesn't start until LoadBuilder has been consumed"]
82    pub fn override_unapproved(mut self) -> Self {
83        self.override_unapproved = true;
84        self
85    }
86
87    /// Loads the provided path as the given type and returns the handle.
88    ///
89    /// This is a "deferred" load, meaning the caller will not have access to the loaded data; to
90    /// access the loaded data, use [`Self::load_value`].
91    pub fn load<'a, A: Asset>(self, path: impl Into<AssetPath<'a>>) -> Handle<A> {
92        // The doc comment slightly lies: if `LoadContext::should_load_dependencies` is true, the
93        // load will not be started, but the matching handle will still be returned. The caller
94        // can't tell the difference.
95        self.load_internal(TypeId::of::<A>(), Some(type_name::<A>()), path.into())
96            .typed_debug_checked()
97    }
98
99    /// Loads the provided path as the given type and returns the handle.
100    ///
101    /// This is a "deferred" load, meaning the caller will not have access to the loaded data; to
102    /// access the loaded data, use [`Self::load_erased_value`].
103    pub fn load_erased<'a>(self, type_id: TypeId, path: impl Into<AssetPath<'a>>) -> UntypedHandle {
104        self.load_internal(type_id, None, path.into())
105    }
106
107    /// Loads the provided path with an unknown type (which is guessed based on the path or meta
108    /// file).
109    ///
110    /// This is a "deferred" load, meaning the caller will not have access to the loaded data; to
111    /// access the loaded data, use [`Self::load_untyped_value`].
112    pub fn load_untyped<'a>(self, path: impl Into<AssetPath<'a>>) -> Handle<LoadedUntypedAsset> {
113        let path = path.into().to_owned();
114        if path.path() == Path::new("") {
115            error!("Attempted to load an asset with an empty path \"{path}\"!");
116            return Handle::default();
117        }
118        let handle = if self.load_context.should_load_dependencies {
119            self.load_context
120                .asset_server
121                .load_unknown_type_with_meta_transform(
122                    path,
123                    self.meta_transform,
124                    (),
125                    self.override_unapproved,
126                )
127        } else {
128            self.load_context
129                .asset_server
130                .get_or_create_path_handle(path, self.meta_transform)
131        };
132        // `load_unknown_type_with_meta_transform` and `get_or_create_path_handle` always returns a
133        // Strong variant, so we are safe to unwrap.
134        let index = (&handle).try_into().unwrap();
135        self.load_context.dependencies.insert(index);
136        handle
137    }
138
139    /// Loads the provided path as the given type, returning the loaded data.
140    ///
141    /// This load is async and therefore needs to be awaited before returning the loaded data.
142    pub async fn load_value<'a, A: Asset>(
143        self,
144        path: impl Into<AssetPath<'a>>,
145    ) -> Result<LoadedAsset<A>, LoadDirectError> {
146        self.load_typed_value_internal(path.into().into_owned(), None)
147            .await
148    }
149
150    /// Loads the provided path as the given type, returning the loaded data.
151    ///
152    /// This load is async and therefore needs to be awaited before returning the loaded data.
153    pub async fn load_erased_value<'a>(
154        self,
155        type_id: TypeId,
156        path: impl Into<AssetPath<'a>>,
157    ) -> Result<ErasedLoadedAsset, LoadDirectError> {
158        self.load_value_internal(Some(type_id), &path.into().into_owned(), None)
159            .await
160            .map(|(_, asset)| asset)
161    }
162
163    /// Loads the provided path with an unknown type (which is guessed based on the path or meta
164    /// file), returning the loaded data.
165    ///
166    /// This load is async and therefore needs to be awaited before returning the loaded data.
167    pub async fn load_untyped_value<'a>(
168        self,
169        path: impl Into<AssetPath<'a>>,
170    ) -> Result<ErasedLoadedAsset, LoadDirectError> {
171        self.load_value_internal(None, &path.into().into_owned(), None)
172            .await
173            .map(|(_, asset)| asset)
174    }
175
176    /// Loads the given type from the given `reader`, returning the loaded data.
177    ///
178    /// This load is async and therefore needs to be awaited before returning the loaded data. The
179    /// provided path determines the path used for handles of subassets, as well as any relative
180    /// paths of assets used by the nested loader.
181    pub async fn load_value_from_reader<'a, A: Asset>(
182        self,
183        path: impl Into<AssetPath<'a>>,
184        reader: &'builder mut dyn Reader,
185    ) -> Result<LoadedAsset<A>, LoadDirectError> {
186        self.load_typed_value_internal(path.into().into_owned(), Some(reader))
187            .await
188    }
189
190    /// Loads the given type from the given `reader`, returning the loaded data.
191    ///
192    /// This load is async and therefore needs to be awaited before returning the loaded data. The
193    /// provided path determines the path used for handles of subassets, as well as any relative
194    /// paths of assets used by the nested loader.
195    pub async fn load_erased_value_from_reader<'a>(
196        self,
197        type_id: TypeId,
198        path: impl Into<AssetPath<'a>>,
199        reader: &'builder mut dyn Reader,
200    ) -> Result<ErasedLoadedAsset, LoadDirectError> {
201        self.load_value_internal(Some(type_id), &path.into().into_owned(), Some(reader))
202            .await
203            .map(|(_, asset)| asset)
204    }
205
206    /// Loads an asset from the given `reader` with an unknown type (which is guessed based on the
207    /// path or meta file), returning the loaded data.
208    ///
209    /// This load is async and therefore needs to be awaited before returning the loaded data. The
210    /// provided path determines the path used for handles of subassets, as well as any relative
211    /// paths of assets used by the nested loader.
212    pub async fn load_untyped_value_from_reader<'a>(
213        self,
214        path: impl Into<AssetPath<'a>>,
215        reader: &'builder mut dyn Reader,
216    ) -> Result<ErasedLoadedAsset, LoadDirectError> {
217        self.load_value_internal(None, &path.into().into_owned(), Some(reader))
218            .await
219            .map(|(_, asset)| asset)
220    }
221
222    /// Acquires the handle for the given type and path, and if necessary, begins a corresponding
223    /// (deferred) load.
224    fn load_internal<'a>(
225        self,
226        type_id: TypeId,
227        type_name: Option<&str>,
228        path: AssetPath<'a>,
229    ) -> UntypedHandle {
230        let path = path.to_owned();
231        if path.path() == Path::new("") {
232            error!("Attempted to load an asset with an empty path \"{path}\"!");
233            return UntypedHandle::default_for_type(type_id);
234        }
235        let handle = if self.load_context.should_load_dependencies {
236            self.load_context.asset_server.load_with_meta_transform(
237                path,
238                type_id,
239                type_name,
240                self.meta_transform,
241                (),
242                self.override_unapproved,
243            )
244        } else {
245            self.load_context
246                .asset_server
247                .get_or_create_path_handle_erased(path, type_id, type_name, self.meta_transform)
248        };
249        // `load_with_meta_transform` and `get_or_create_path_handle` always returns a Strong
250        // variant, so we are safe to unwrap.
251        let index = (&handle).try_into().unwrap();
252        self.load_context.dependencies.insert(index);
253        handle
254    }
255
256    /// Creates a future to do a nested load.
257    ///
258    /// The type is either provided, or it is deduced from the path or meta file. If `reader` is
259    /// [`Some`], the load reads from the provided reader. Otherwise, the asset is loaded from
260    /// `path`.
261    async fn load_value_internal(
262        self,
263        type_id: Option<TypeId>,
264        path: &AssetPath<'static>,
265        reader: Option<&'builder mut dyn Reader>,
266    ) -> Result<(Arc<dyn ErasedAssetLoader>, ErasedLoadedAsset), LoadDirectError> {
267        if path.path() == Path::new("") {
268            error!("Attempted to load an asset with an empty path \"{path}\"!");
269            return Err(LoadDirectError::EmptyPath(path.clone_owned()));
270        }
271        if path.label().is_some() {
272            return Err(LoadDirectError::RequestedSubasset(path.clone()));
273        }
274        self.load_context
275            .asset_server
276            .write_infos()
277            .stats
278            .started_load_tasks += 1;
279        let (mut meta, loader, mut reader) = if let Some(reader) = reader {
280            let loader = if let Some(type_id) = type_id {
281                self.load_context
282                    .asset_server
283                    .get_asset_loader_with_asset_type_id(type_id)
284                    .await
285                    .map_err(|error| LoadDirectError::LoadError {
286                        dependency: path.clone(),
287                        error: error.into(),
288                    })?
289            } else {
290                self.load_context
291                    .asset_server
292                    .get_path_asset_loader(path)
293                    .await
294                    .map_err(|error| LoadDirectError::LoadError {
295                        dependency: path.clone(),
296                        error: error.into(),
297                    })?
298            };
299            let meta = loader.default_meta();
300            (meta, loader, ReaderRef::Borrowed(reader))
301        } else {
302            let (meta, loader, reader) = self
303                .load_context
304                .asset_server
305                .get_meta_loader_and_reader(path, type_id)
306                .await
307                .map_err(|error| LoadDirectError::LoadError {
308                    dependency: path.clone(),
309                    error,
310                })?;
311            (meta, loader, ReaderRef::Boxed(reader))
312        };
313
314        if let Some(meta_transform) = self.meta_transform {
315            meta_transform(&mut *meta);
316        }
317
318        let asset = self
319            .load_context
320            .load_direct_internal(
321                path.clone(),
322                meta.loader_settings().expect("meta corresponds to a load"),
323                &*loader,
324                reader.as_mut(),
325                meta.processed_info().as_ref(),
326            )
327            .await?;
328        Ok((loader, asset))
329    }
330
331    /// Same as [`Self::load_value_internal`], but with a generic to ensure the returned handle type
332    /// is correct.
333    #[cfg_attr(
334        not(target_arch = "wasm32"),
335        expect(
336            clippy::result_large_err,
337            reason = "we need to give the user the correct error type"
338        )
339    )]
340    async fn load_typed_value_internal<A: Asset>(
341        self,
342        path: AssetPath<'static>,
343        reader: Option<&'builder mut dyn Reader>,
344    ) -> Result<LoadedAsset<A>, LoadDirectError> {
345        self.load_value_internal(Some(TypeId::of::<A>()), &path, reader)
346            .await
347            .and_then(move |(loader, untyped_asset)| {
348                untyped_asset
349                    .downcast::<A>()
350                    .map_err(|_| LoadDirectError::LoadError {
351                        dependency: path.clone(),
352                        error: AssetLoadError::RequestedHandleTypeMismatch {
353                            path,
354                            requested: TypeId::of::<A>(),
355                            actual_asset_name: loader.asset_type_name(),
356                            loader_name: loader.type_path(),
357                        },
358                    })
359            })
360    }
361}