Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_asset/src/loader_builders.rs
9367 views
1
//! Implementations of the builder-pattern used for loading dependent assets via
2
//! [`LoadContext::loader`].
3
4
use 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
};
10
use alloc::{borrow::ToOwned, boxed::Box, sync::Arc};
11
use core::any::TypeId;
12
13
// Utility type for handling the sources of reader references
14
enum ReaderRef<'a> {
15
Borrowed(&'a mut dyn Reader),
16
Boxed(Box<dyn Reader + 'a>),
17
}
18
19
impl 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
118
pub 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
125
mod 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
135
pub struct StaticTyped(());
136
137
impl sealed::Typing for StaticTyped {}
138
139
/// [`NestedLoader`] has been configured with info on what type of asset to load
140
/// at runtime.
141
pub struct DynamicTyped {
142
asset_type_id: TypeId,
143
}
144
145
impl sealed::Typing for DynamicTyped {}
146
147
/// [`NestedLoader`] does not know what type of asset it will be loading.
148
pub struct UnknownTyped(());
149
150
impl sealed::Typing for UnknownTyped {}
151
152
/// [`NestedLoader`] will create and return asset handles immediately, but only
153
/// actually load the asset later.
154
pub struct Deferred(());
155
156
impl sealed::Mode for Deferred {}
157
158
/// [`NestedLoader`] will immediately load an asset when requested.
159
pub struct Immediate<'builder, 'reader> {
160
reader: Option<&'builder mut (dyn Reader + 'reader)>,
161
}
162
163
impl sealed::Mode for Immediate<'_, '_> {}
164
165
// common to all states
166
167
impl<'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
178
impl<'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
293
impl 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
327
impl 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
363
impl 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
389
impl<'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
463
impl 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
497
impl 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
516
impl 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
}
530
531