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