bevy_asset/
loader_builders.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
//! Implementations of the builder-pattern used for loading dependent assets via
//! [`LoadContext::loader`].

use crate::{
    io::Reader,
    meta::{meta_transform_settings, AssetMetaDyn, MetaTransform, Settings},
    Asset, AssetLoadError, AssetPath, ErasedAssetLoader, ErasedLoadedAsset, Handle, LoadContext,
    LoadDirectError, LoadedAsset, LoadedUntypedAsset, UntypedHandle,
};
use alloc::sync::Arc;
use core::any::TypeId;

// Utility type for handling the sources of reader references
enum ReaderRef<'a> {
    Borrowed(&'a mut dyn Reader),
    Boxed(Box<dyn Reader + 'a>),
}

impl ReaderRef<'_> {
    pub fn as_mut(&mut self) -> &mut dyn Reader {
        match self {
            ReaderRef::Borrowed(r) => &mut **r,
            ReaderRef::Boxed(b) => &mut **b,
        }
    }
}

/// A builder for loading nested assets inside a [`LoadContext`].
///
/// # Loader state
///
/// The type parameters `T` and `M` determine how this will load assets:
/// - `T`: the typing of this loader. How do we know what type of asset to load?
///
///   See [`StaticTyped`] (the default), [`DynamicTyped`], and [`UnknownTyped`].
///
/// - `M`: the load mode. Do we want to load this asset right now (in which case
///   you will have to `await` the operation), or do we just want a [`Handle`],
///   and leave the actual asset loading to later?
///
///   See [`Deferred`] (the default) and [`Immediate`].
///
/// When configuring this builder, you can freely switch between these modes
/// via functions like [`deferred`] and [`immediate`].
///
/// ## Typing
///
/// To inform the loader of what type of asset to load:
/// - in [`StaticTyped`]: statically providing a type parameter `A: Asset` to
///   [`load`].
///
///   This is the simplest way to get a [`Handle<A>`] to the loaded asset, as
///   long as you know the type of `A` at compile time.
///
/// - in [`DynamicTyped`]: providing the [`TypeId`] of the asset at runtime.
///
///   If you know the type ID of the asset at runtime, but not at compile time,
///   use [`with_dynamic_type`] followed by [`load`] to start loading an asset
///   of that type. This lets you get an [`UntypedHandle`] (via [`Deferred`]),
///   or a [`ErasedLoadedAsset`] (via [`Immediate`]).
///
/// - in [`UnknownTyped`]: loading either a type-erased version of the asset
///   ([`ErasedLoadedAsset`]), or a handle *to a handle* of the actual asset
///   ([`LoadedUntypedAsset`]).
///
///   If you have no idea what type of asset you will be loading (not even at
///   runtime with a [`TypeId`]), use this.
///
/// ## Load mode
///
/// To inform the loader how you want to load the asset:
/// - in [`Deferred`]: when you request to load the asset, you get a [`Handle`]
///   for it, but the actual loading won't be completed until later.
///
///   Use this if you only need a [`Handle`] or [`UntypedHandle`].
///
/// - in [`Immediate`]: the load request will load the asset right then and
///   there, waiting until the asset is fully loaded and giving you access to
///   it.
///
///   Note that this requires you to `await` a future, so you must be in an
///   async context to use direct loading. In an asset loader, you will be in
///   an async context.
///
///   Use this if you need the *value* of another asset in order to load the
///   current asset. For example, if you are deriving a new asset from the
///   referenced asset, or you are building a collection of assets. This will
///   add the path of the asset as a "load dependency".
///
///   If the current loader is used in a [`Process`] "asset preprocessor",
///   such as a [`LoadTransformAndSave`] preprocessor, changing a "load
///   dependency" will result in re-processing of the asset.
///
/// # Load kickoff
///
/// If the current context is a normal [`AssetServer::load`], an actual asset
/// load will be kicked off immediately, which ensures the load happens as soon
/// as possible. "Normal loads" kicked from within a normal Bevy App will
/// generally configure the context to kick off loads immediately.
///
/// If the current context is configured to not load dependencies automatically
/// (ex: [`AssetProcessor`]), a load will not be kicked off automatically. It is
/// then the calling context's responsibility to begin a load if necessary.
///
/// # Lifetimes
///
/// - `ctx`: the lifetime of the associated [`AssetServer`](crate::AssetServer) reference
/// - `builder`: the lifetime of the temporary builder structs
///
/// [`deferred`]: Self::deferred
/// [`immediate`]: Self::immediate
/// [`load`]: Self::load
/// [`with_dynamic_type`]: Self::with_dynamic_type
/// [`AssetServer::load`]: crate::AssetServer::load
/// [`AssetProcessor`]: crate::processor::AssetProcessor
/// [`Process`]: crate::processor::Process
/// [`LoadTransformAndSave`]: crate::processor::LoadTransformAndSave
pub struct NestedLoader<'ctx, 'builder, T, M> {
    load_context: &'builder mut LoadContext<'ctx>,
    meta_transform: Option<MetaTransform>,
    typing: T,
    mode: M,
}

mod sealed {
    pub trait Typing {}

    pub trait Mode {}
}

/// [`NestedLoader`] will be provided the type of asset as a type parameter on
/// [`load`].
///
/// [`load`]: NestedLoader::load
pub struct StaticTyped(());

impl sealed::Typing for StaticTyped {}

/// [`NestedLoader`] has been configured with info on what type of asset to load
/// at runtime.
pub struct DynamicTyped {
    asset_type_id: TypeId,
}

impl sealed::Typing for DynamicTyped {}

/// [`NestedLoader`] does not know what type of asset it will be loading.
pub struct UnknownTyped(());

impl sealed::Typing for UnknownTyped {}

/// [`NestedLoader`] will create and return asset handles immediately, but only
/// actually load the asset later.
pub struct Deferred(());

impl sealed::Mode for Deferred {}

/// [`NestedLoader`] will immediately load an asset when requested.
pub struct Immediate<'builder, 'reader> {
    reader: Option<&'builder mut (dyn Reader + 'reader)>,
}

impl sealed::Mode for Immediate<'_, '_> {}

// common to all states

impl<'ctx, 'builder> NestedLoader<'ctx, 'builder, StaticTyped, Deferred> {
    pub(crate) fn new(load_context: &'builder mut LoadContext<'ctx>) -> Self {
        NestedLoader {
            load_context,
            meta_transform: None,
            typing: StaticTyped(()),
            mode: Deferred(()),
        }
    }
}

impl<'ctx, 'builder, T: sealed::Typing, M: sealed::Mode> NestedLoader<'ctx, 'builder, T, M> {
    fn with_transform(
        mut self,
        transform: impl Fn(&mut dyn AssetMetaDyn) + Send + Sync + 'static,
    ) -> Self {
        if let Some(prev_transform) = self.meta_transform {
            self.meta_transform = Some(Box::new(move |meta| {
                prev_transform(meta);
                transform(meta);
            }));
        } else {
            self.meta_transform = Some(Box::new(transform));
        }
        self
    }

    /// Configure the settings used to load the asset.
    ///
    /// If the settings type `S` does not match the settings expected by `A`'s asset loader, an error will be printed to the log
    /// and the asset load will fail.
    #[must_use]
    pub fn with_settings<S: Settings>(
        self,
        settings: impl Fn(&mut S) + Send + Sync + 'static,
    ) -> Self {
        self.with_transform(move |meta| meta_transform_settings(meta, &settings))
    }

    // convert between `T`s

    /// When [`load`]ing, you must pass in the asset type as a type parameter
    /// statically.
    ///
    /// If you don't know the type statically (at compile time), consider
    /// [`with_dynamic_type`] or [`with_unknown_type`].
    ///
    /// [`load`]: Self::load
    /// [`with_dynamic_type`]: Self::with_dynamic_type
    /// [`with_unknown_type`]: Self::with_unknown_type
    #[must_use]
    pub fn with_static_type(self) -> NestedLoader<'ctx, 'builder, StaticTyped, M> {
        NestedLoader {
            load_context: self.load_context,
            meta_transform: self.meta_transform,
            typing: StaticTyped(()),
            mode: self.mode,
        }
    }

    /// When [`load`]ing, the loader will attempt to load an asset with the
    /// given [`TypeId`].
    ///
    /// [`load`]: Self::load
    #[must_use]
    pub fn with_dynamic_type(
        self,
        asset_type_id: TypeId,
    ) -> NestedLoader<'ctx, 'builder, DynamicTyped, M> {
        NestedLoader {
            load_context: self.load_context,
            meta_transform: self.meta_transform,
            typing: DynamicTyped { asset_type_id },
            mode: self.mode,
        }
    }

    /// When [`load`]ing, we will infer what type of asset to load from
    /// metadata.
    ///
    /// [`load`]: Self::load
    #[must_use]
    pub fn with_unknown_type(self) -> NestedLoader<'ctx, 'builder, UnknownTyped, M> {
        NestedLoader {
            load_context: self.load_context,
            meta_transform: self.meta_transform,
            typing: UnknownTyped(()),
            mode: self.mode,
        }
    }

    // convert between `M`s

    /// When [`load`]ing, create only asset handles, rather than returning the
    /// actual asset.
    ///
    /// [`load`]: Self::load
    pub fn deferred(self) -> NestedLoader<'ctx, 'builder, T, Deferred> {
        NestedLoader {
            load_context: self.load_context,
            meta_transform: self.meta_transform,
            typing: self.typing,
            mode: Deferred(()),
        }
    }

    /// The [`load`] call itself will load an asset, rather than scheduling the
    /// loading to happen later.
    ///
    /// This gives you access to the loaded asset, but requires you to be in an
    /// async context, and be able to `await` the resulting future.
    ///
    /// [`load`]: Self::load
    #[must_use]
    pub fn immediate<'c>(self) -> NestedLoader<'ctx, 'builder, T, Immediate<'builder, 'c>> {
        NestedLoader {
            load_context: self.load_context,
            meta_transform: self.meta_transform,
            typing: self.typing,
            mode: Immediate { reader: None },
        }
    }
}

// deferred loading logic

impl NestedLoader<'_, '_, StaticTyped, Deferred> {
    /// Retrieves a handle for the asset at the given path and adds that path as
    /// a dependency of this asset.
    ///
    /// This requires you to know the type of asset statically.
    /// - If you have runtime info for what type of asset you're loading (e.g. a
    ///   [`TypeId`]), use [`with_dynamic_type`].
    /// - If you do not know at all what type of asset you're loading, use
    ///   [`with_unknown_type`].
    ///
    /// [`with_dynamic_type`]: Self::with_dynamic_type
    /// [`with_unknown_type`]: Self::with_unknown_type
    pub fn load<'c, A: Asset>(self, path: impl Into<AssetPath<'c>>) -> Handle<A> {
        let path = path.into().to_owned();
        let handle = if self.load_context.should_load_dependencies {
            self.load_context
                .asset_server
                .load_with_meta_transform(path, self.meta_transform, ())
        } else {
            self.load_context
                .asset_server
                .get_or_create_path_handle(path, None)
        };
        self.load_context.dependencies.insert(handle.id().untyped());
        handle
    }
}

impl NestedLoader<'_, '_, DynamicTyped, Deferred> {
    /// Retrieves a handle for the asset at the given path and adds that path as
    /// a dependency of this asset.
    ///
    /// This requires you to pass in the asset type ID into
    /// [`with_dynamic_type`].
    ///
    /// [`with_dynamic_type`]: Self::with_dynamic_type
    pub fn load<'p>(self, path: impl Into<AssetPath<'p>>) -> UntypedHandle {
        let path = path.into().to_owned();
        let handle = if self.load_context.should_load_dependencies {
            self.load_context
                .asset_server
                .load_erased_with_meta_transform(
                    path,
                    self.typing.asset_type_id,
                    self.meta_transform,
                    (),
                )
        } else {
            self.load_context
                .asset_server
                .get_or_create_path_handle_erased(
                    path,
                    self.typing.asset_type_id,
                    self.meta_transform,
                )
        };
        self.load_context.dependencies.insert(handle.id());
        handle
    }
}

impl NestedLoader<'_, '_, UnknownTyped, Deferred> {
    /// Retrieves a handle for the asset at the given path and adds that path as
    /// a dependency of this asset.
    ///
    /// This will infer the asset type from metadata.
    pub fn load<'p>(self, path: impl Into<AssetPath<'p>>) -> Handle<LoadedUntypedAsset> {
        let path = path.into().to_owned();
        let handle = if self.load_context.should_load_dependencies {
            self.load_context
                .asset_server
                .load_unknown_type_with_meta_transform(path, self.meta_transform)
        } else {
            self.load_context
                .asset_server
                .get_or_create_path_handle(path, self.meta_transform)
        };
        self.load_context.dependencies.insert(handle.id().untyped());
        handle
    }
}

// immediate loading logic

impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>> {
    /// Specify the reader to use to read the asset data.
    #[must_use]
    pub fn with_reader(mut self, reader: &'builder mut (dyn Reader + 'reader)) -> Self {
        self.mode.reader = Some(reader);
        self
    }

    async fn load_internal(
        self,
        path: &AssetPath<'static>,
        asset_type_id: Option<TypeId>,
    ) -> Result<(Arc<dyn ErasedAssetLoader>, ErasedLoadedAsset), LoadDirectError> {
        let (mut meta, loader, mut reader) = if let Some(reader) = self.mode.reader {
            let loader = if let Some(asset_type_id) = asset_type_id {
                self.load_context
                    .asset_server
                    .get_asset_loader_with_asset_type_id(asset_type_id)
                    .await
                    .map_err(|error| LoadDirectError {
                        dependency: path.clone(),
                        error: error.into(),
                    })?
            } else {
                self.load_context
                    .asset_server
                    .get_path_asset_loader(path)
                    .await
                    .map_err(|error| LoadDirectError {
                        dependency: path.clone(),
                        error: error.into(),
                    })?
            };
            let meta = loader.default_meta();
            (meta, loader, ReaderRef::Borrowed(reader))
        } else {
            let (meta, loader, reader) = self
                .load_context
                .asset_server
                .get_meta_loader_and_reader(path, asset_type_id)
                .await
                .map_err(|error| LoadDirectError {
                    dependency: path.clone(),
                    error,
                })?;
            (meta, loader, ReaderRef::Boxed(reader))
        };

        if let Some(meta_transform) = self.meta_transform {
            meta_transform(&mut *meta);
        }

        let asset = self
            .load_context
            .load_direct_internal(path.clone(), meta, &*loader, reader.as_mut())
            .await?;
        Ok((loader, asset))
    }
}

impl NestedLoader<'_, '_, StaticTyped, Immediate<'_, '_>> {
    /// Attempts to load the asset at the given `path` immediately.
    ///
    /// This requires you to know the type of asset statically.
    /// - If you have runtime info for what type of asset you're loading (e.g. a
    ///   [`TypeId`]), use [`with_dynamic_type`].
    /// - If you do not know at all what type of asset you're loading, use
    ///   [`with_unknown_type`].
    ///
    /// [`with_dynamic_type`]: Self::with_dynamic_type
    /// [`with_unknown_type`]: Self::with_unknown_type
    pub async fn load<'p, A: Asset>(
        self,
        path: impl Into<AssetPath<'p>>,
    ) -> Result<LoadedAsset<A>, LoadDirectError> {
        let path = path.into().into_owned();
        self.load_internal(&path, Some(TypeId::of::<A>()))
            .await
            .and_then(move |(loader, untyped_asset)| {
                untyped_asset.downcast::<A>().map_err(|_| LoadDirectError {
                    dependency: path.clone(),
                    error: AssetLoadError::RequestedHandleTypeMismatch {
                        path,
                        requested: TypeId::of::<A>(),
                        actual_asset_name: loader.asset_type_name(),
                        loader_name: loader.type_name(),
                    },
                })
            })
    }
}

impl NestedLoader<'_, '_, DynamicTyped, Immediate<'_, '_>> {
    /// Attempts to load the asset at the given `path` immediately.
    ///
    /// This requires you to pass in the asset type ID into
    /// [`with_dynamic_type`].
    ///
    /// [`with_dynamic_type`]: Self::with_dynamic_type
    pub async fn load<'p>(
        self,
        path: impl Into<AssetPath<'p>>,
    ) -> Result<ErasedLoadedAsset, LoadDirectError> {
        let path = path.into().into_owned();
        let asset_type_id = Some(self.typing.asset_type_id);
        self.load_internal(&path, asset_type_id)
            .await
            .map(|(_, asset)| asset)
    }
}

impl NestedLoader<'_, '_, UnknownTyped, Immediate<'_, '_>> {
    /// Attempts to load the asset at the given `path` immediately.
    ///
    /// This will infer the asset type from metadata.
    pub async fn load<'p>(
        self,
        path: impl Into<AssetPath<'p>>,
    ) -> Result<ErasedLoadedAsset, LoadDirectError> {
        let path = path.into().into_owned();
        self.load_internal(&path, None)
            .await
            .map(|(_, asset)| asset)
    }
}