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