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