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::{borrow::ToOwned, boxed::Box, 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.asset_server.load_with_meta_transform(
309 path,
310 self.meta_transform,
311 (),
312 true,
313 )
314 } else {
315 self.load_context
316 .asset_server
317 .get_or_create_path_handle(path, None)
318 };
319 self.load_context.dependencies.insert(handle.id().untyped());
320 handle
321 }
322}
323
324impl NestedLoader<'_, '_, DynamicTyped, Deferred> {
325 /// Retrieves a handle for the asset at the given path and adds that path as
326 /// a dependency of this asset.
327 ///
328 /// This requires you to pass in the asset type ID into
329 /// [`with_dynamic_type`].
330 ///
331 /// [`with_dynamic_type`]: Self::with_dynamic_type
332 pub fn load<'p>(self, path: impl Into<AssetPath<'p>>) -> UntypedHandle {
333 let path = path.into().to_owned();
334 let handle = if self.load_context.should_load_dependencies {
335 self.load_context
336 .asset_server
337 .load_erased_with_meta_transform(
338 path,
339 self.typing.asset_type_id,
340 self.meta_transform,
341 (),
342 )
343 } else {
344 self.load_context
345 .asset_server
346 .get_or_create_path_handle_erased(
347 path,
348 self.typing.asset_type_id,
349 self.meta_transform,
350 )
351 };
352 self.load_context.dependencies.insert(handle.id());
353 handle
354 }
355}
356
357impl NestedLoader<'_, '_, UnknownTyped, Deferred> {
358 /// Retrieves a handle for the asset at the given path and adds that path as
359 /// a dependency of this asset.
360 ///
361 /// This will infer the asset type from metadata.
362 pub fn load<'p>(self, path: impl Into<AssetPath<'p>>) -> Handle<LoadedUntypedAsset> {
363 let path = path.into().to_owned();
364 let handle = if self.load_context.should_load_dependencies {
365 self.load_context
366 .asset_server
367 .load_unknown_type_with_meta_transform(path, self.meta_transform)
368 } else {
369 self.load_context
370 .asset_server
371 .get_or_create_path_handle(path, self.meta_transform)
372 };
373 self.load_context.dependencies.insert(handle.id().untyped());
374 handle
375 }
376}
377
378// immediate loading logic
379
380impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>> {
381 /// Specify the reader to use to read the asset data.
382 #[must_use]
383 pub fn with_reader(mut self, reader: &'builder mut (dyn Reader + 'reader)) -> Self {
384 self.mode.reader = Some(reader);
385 self
386 }
387
388 async fn load_internal(
389 self,
390 path: &AssetPath<'static>,
391 asset_type_id: Option<TypeId>,
392 ) -> Result<(Arc<dyn ErasedAssetLoader>, ErasedLoadedAsset), LoadDirectError> {
393 if path.label().is_some() {
394 return Err(LoadDirectError::RequestedSubasset(path.clone()));
395 }
396 let (mut meta, loader, mut reader) = if let Some(reader) = self.mode.reader {
397 let loader = if let Some(asset_type_id) = asset_type_id {
398 self.load_context
399 .asset_server
400 .get_asset_loader_with_asset_type_id(asset_type_id)
401 .await
402 .map_err(|error| LoadDirectError::LoadError {
403 dependency: path.clone(),
404 error: error.into(),
405 })?
406 } else {
407 self.load_context
408 .asset_server
409 .get_path_asset_loader(path)
410 .await
411 .map_err(|error| LoadDirectError::LoadError {
412 dependency: path.clone(),
413 error: error.into(),
414 })?
415 };
416 let meta = loader.default_meta();
417 (meta, loader, ReaderRef::Borrowed(reader))
418 } else {
419 let (meta, loader, reader) = self
420 .load_context
421 .asset_server
422 .get_meta_loader_and_reader(path, asset_type_id)
423 .await
424 .map_err(|error| LoadDirectError::LoadError {
425 dependency: path.clone(),
426 error,
427 })?;
428 (meta, loader, ReaderRef::Boxed(reader))
429 };
430
431 if let Some(meta_transform) = self.meta_transform {
432 meta_transform(&mut *meta);
433 }
434
435 let asset = self
436 .load_context
437 .load_direct_internal(path.clone(), meta.as_ref(), &*loader, reader.as_mut())
438 .await?;
439 Ok((loader, asset))
440 }
441}
442
443impl NestedLoader<'_, '_, StaticTyped, Immediate<'_, '_>> {
444 /// Attempts to load the asset at the given `path` immediately.
445 ///
446 /// This requires you to know the type of asset statically.
447 /// - If you have runtime info for what type of asset you're loading (e.g. a
448 /// [`TypeId`]), use [`with_dynamic_type`].
449 /// - If you do not know at all what type of asset you're loading, use
450 /// [`with_unknown_type`].
451 ///
452 /// [`with_dynamic_type`]: Self::with_dynamic_type
453 /// [`with_unknown_type`]: Self::with_unknown_type
454 pub async fn load<'p, A: Asset>(
455 self,
456 path: impl Into<AssetPath<'p>>,
457 ) -> Result<LoadedAsset<A>, LoadDirectError> {
458 let path = path.into().into_owned();
459 self.load_internal(&path, Some(TypeId::of::<A>()))
460 .await
461 .and_then(move |(loader, untyped_asset)| {
462 untyped_asset
463 .downcast::<A>()
464 .map_err(|_| LoadDirectError::LoadError {
465 dependency: path.clone(),
466 error: AssetLoadError::RequestedHandleTypeMismatch {
467 path,
468 requested: TypeId::of::<A>(),
469 actual_asset_name: loader.asset_type_name(),
470 loader_name: loader.type_name(),
471 },
472 })
473 })
474 }
475}
476
477impl NestedLoader<'_, '_, DynamicTyped, Immediate<'_, '_>> {
478 /// Attempts to load the asset at the given `path` immediately.
479 ///
480 /// This requires you to pass in the asset type ID into
481 /// [`with_dynamic_type`].
482 ///
483 /// [`with_dynamic_type`]: Self::with_dynamic_type
484 pub async fn load<'p>(
485 self,
486 path: impl Into<AssetPath<'p>>,
487 ) -> Result<ErasedLoadedAsset, LoadDirectError> {
488 let path = path.into().into_owned();
489 let asset_type_id = Some(self.typing.asset_type_id);
490 self.load_internal(&path, asset_type_id)
491 .await
492 .map(|(_, asset)| asset)
493 }
494}
495
496impl NestedLoader<'_, '_, UnknownTyped, Immediate<'_, '_>> {
497 /// Attempts to load the asset at the given `path` immediately.
498 ///
499 /// This will infer the asset type from metadata.
500 pub async fn load<'p>(
501 self,
502 path: impl Into<AssetPath<'p>>,
503 ) -> Result<ErasedLoadedAsset, LoadDirectError> {
504 let path = path.into().into_owned();
505 self.load_internal(&path, None)
506 .await
507 .map(|(_, asset)| asset)
508 }
509}