Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_asset/src/loader_builders.rs
6598 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
self.load_context.dependencies.insert(handle.id().untyped());
320
handle
321
}
322
}
323
324
impl 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
357
impl 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
380
impl<'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
443
impl 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
477
impl 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
496
impl 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
}
510
511