Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_asset/src/loader.rs
6598 views
1
use crate::{
2
io::{AssetReaderError, MissingAssetSourceError, MissingProcessedAssetReaderError, Reader},
3
loader_builders::{Deferred, NestedLoader, StaticTyped},
4
meta::{AssetHash, AssetMeta, AssetMetaDyn, ProcessedInfoMinimal, Settings},
5
path::AssetPath,
6
Asset, AssetLoadError, AssetServer, AssetServerMode, Assets, Handle, UntypedAssetId,
7
UntypedHandle,
8
};
9
use alloc::{
10
boxed::Box,
11
string::{String, ToString},
12
vec::Vec,
13
};
14
use atomicow::CowArc;
15
use bevy_ecs::{error::BevyError, world::World};
16
use bevy_platform::collections::{HashMap, HashSet};
17
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
18
use core::any::{Any, TypeId};
19
use downcast_rs::{impl_downcast, Downcast};
20
use ron::error::SpannedError;
21
use serde::{Deserialize, Serialize};
22
use std::path::{Path, PathBuf};
23
use thiserror::Error;
24
25
/// Loads an [`Asset`] from a given byte [`Reader`]. This can accept [`AssetLoader::Settings`], which configure how the [`Asset`]
26
/// should be loaded.
27
///
28
/// This trait is generally used in concert with [`AssetReader`](crate::io::AssetReader) to load assets from a byte source.
29
///
30
/// For a complementary version of this trait that can save assets, see [`AssetSaver`](crate::saver::AssetSaver).
31
pub trait AssetLoader: Send + Sync + 'static {
32
/// The top level [`Asset`] loaded by this [`AssetLoader`].
33
type Asset: Asset;
34
/// The settings type used by this [`AssetLoader`].
35
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
36
/// The type of [error](`std::error::Error`) which could be encountered by this loader.
37
type Error: Into<BevyError>;
38
/// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`].
39
fn load(
40
&self,
41
reader: &mut dyn Reader,
42
settings: &Self::Settings,
43
load_context: &mut LoadContext,
44
) -> impl ConditionalSendFuture<Output = Result<Self::Asset, Self::Error>>;
45
46
/// Returns a list of extensions supported by this [`AssetLoader`], without the preceding dot.
47
/// Note that users of this [`AssetLoader`] may choose to load files with a non-matching extension.
48
fn extensions(&self) -> &[&str] {
49
&[]
50
}
51
}
52
53
/// Provides type-erased access to an [`AssetLoader`].
54
pub trait ErasedAssetLoader: Send + Sync + 'static {
55
/// Asynchronously loads the asset(s) from the bytes provided by [`Reader`].
56
fn load<'a>(
57
&'a self,
58
reader: &'a mut dyn Reader,
59
meta: &'a dyn AssetMetaDyn,
60
load_context: LoadContext<'a>,
61
) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>>;
62
63
/// Returns a list of extensions supported by this asset loader, without the preceding dot.
64
fn extensions(&self) -> &[&str];
65
/// Deserializes metadata from the input `meta` bytes into the appropriate type (erased as [`Box<dyn AssetMetaDyn>`]).
66
fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
67
/// Returns the default meta value for the [`AssetLoader`] (erased as [`Box<dyn AssetMetaDyn>`]).
68
fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
69
/// Returns the type name of the [`AssetLoader`].
70
fn type_name(&self) -> &'static str;
71
/// Returns the [`TypeId`] of the [`AssetLoader`].
72
fn type_id(&self) -> TypeId;
73
/// Returns the type name of the top-level [`Asset`] loaded by the [`AssetLoader`].
74
fn asset_type_name(&self) -> &'static str;
75
/// Returns the [`TypeId`] of the top-level [`Asset`] loaded by the [`AssetLoader`].
76
fn asset_type_id(&self) -> TypeId;
77
}
78
79
impl<L> ErasedAssetLoader for L
80
where
81
L: AssetLoader + Send + Sync,
82
{
83
/// Processes the asset in an asynchronous closure.
84
fn load<'a>(
85
&'a self,
86
reader: &'a mut dyn Reader,
87
meta: &'a dyn AssetMetaDyn,
88
mut load_context: LoadContext<'a>,
89
) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>> {
90
Box::pin(async move {
91
let settings = meta
92
.loader_settings()
93
.expect("Loader settings should exist")
94
.downcast_ref::<L::Settings>()
95
.expect("AssetLoader settings should match the loader type");
96
let asset = <L as AssetLoader>::load(self, reader, settings, &mut load_context)
97
.await
98
.map_err(Into::into)?;
99
Ok(load_context.finish(asset).into())
100
})
101
}
102
103
fn extensions(&self) -> &[&str] {
104
<L as AssetLoader>::extensions(self)
105
}
106
107
fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
108
let meta = AssetMeta::<L, ()>::deserialize(meta)?;
109
Ok(Box::new(meta))
110
}
111
112
fn default_meta(&self) -> Box<dyn AssetMetaDyn> {
113
Box::new(AssetMeta::<L, ()>::new(crate::meta::AssetAction::Load {
114
loader: self.type_name().to_string(),
115
settings: L::Settings::default(),
116
}))
117
}
118
119
fn type_name(&self) -> &'static str {
120
core::any::type_name::<L>()
121
}
122
123
fn type_id(&self) -> TypeId {
124
TypeId::of::<L>()
125
}
126
127
fn asset_type_name(&self) -> &'static str {
128
core::any::type_name::<L::Asset>()
129
}
130
131
fn asset_type_id(&self) -> TypeId {
132
TypeId::of::<L::Asset>()
133
}
134
}
135
136
pub(crate) struct LabeledAsset {
137
pub(crate) asset: ErasedLoadedAsset,
138
pub(crate) handle: UntypedHandle,
139
}
140
141
/// The successful result of an [`AssetLoader::load`] call. This contains the loaded "root" asset and any other "labeled" assets produced
142
/// by the loader. It also holds the input [`AssetMeta`] (if it exists) and tracks dependencies:
143
/// * normal dependencies: dependencies that must be loaded as part of this asset load (ex: assets a given asset has handles to).
144
/// * Loader dependencies: dependencies whose actual asset values are used during the load process
145
pub struct LoadedAsset<A: Asset> {
146
pub(crate) value: A,
147
pub(crate) dependencies: HashSet<UntypedAssetId>,
148
pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
149
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
150
}
151
152
impl<A: Asset> LoadedAsset<A> {
153
/// Create a new loaded asset. This will use [`VisitAssetDependencies`](crate::VisitAssetDependencies) to populate `dependencies`.
154
pub fn new_with_dependencies(value: A) -> Self {
155
let mut dependencies = <HashSet<_>>::default();
156
value.visit_dependencies(&mut |id| {
157
dependencies.insert(id);
158
});
159
LoadedAsset {
160
value,
161
dependencies,
162
loader_dependencies: HashMap::default(),
163
labeled_assets: HashMap::default(),
164
}
165
}
166
167
/// Cast (and take ownership) of the [`Asset`] value of the given type.
168
pub fn take(self) -> A {
169
self.value
170
}
171
172
/// Retrieves a reference to the internal [`Asset`] type.
173
pub fn get(&self) -> &A {
174
&self.value
175
}
176
177
/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
178
pub fn get_labeled(
179
&self,
180
label: impl Into<CowArc<'static, str>>,
181
) -> Option<&ErasedLoadedAsset> {
182
self.labeled_assets.get(&label.into()).map(|a| &a.asset)
183
}
184
185
/// Iterate over all labels for "labeled assets" in the loaded asset
186
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
187
self.labeled_assets.keys().map(|s| &**s)
188
}
189
}
190
191
impl<A: Asset> From<A> for LoadedAsset<A> {
192
fn from(asset: A) -> Self {
193
LoadedAsset::new_with_dependencies(asset)
194
}
195
}
196
197
/// A "type erased / boxed" counterpart to [`LoadedAsset`]. This is used in places where the loaded type is not statically known.
198
pub struct ErasedLoadedAsset {
199
pub(crate) value: Box<dyn AssetContainer>,
200
pub(crate) dependencies: HashSet<UntypedAssetId>,
201
pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
202
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
203
}
204
205
impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset {
206
fn from(asset: LoadedAsset<A>) -> Self {
207
ErasedLoadedAsset {
208
value: Box::new(asset.value),
209
dependencies: asset.dependencies,
210
loader_dependencies: asset.loader_dependencies,
211
labeled_assets: asset.labeled_assets,
212
}
213
}
214
}
215
216
impl ErasedLoadedAsset {
217
/// Cast (and take ownership) of the [`Asset`] value of the given type. This will return [`Some`] if
218
/// the stored type matches `A` and [`None`] if it does not.
219
pub fn take<A: Asset>(self) -> Option<A> {
220
self.value.downcast::<A>().map(|a| *a).ok()
221
}
222
223
/// Retrieves a reference to the internal [`Asset`] type, if it matches the type `A`. Otherwise returns [`None`].
224
pub fn get<A: Asset>(&self) -> Option<&A> {
225
self.value.downcast_ref::<A>()
226
}
227
228
/// Retrieves the [`TypeId`] of the stored [`Asset`] type.
229
pub fn asset_type_id(&self) -> TypeId {
230
(*self.value).type_id()
231
}
232
233
/// Retrieves the `type_name` of the stored [`Asset`] type.
234
pub fn asset_type_name(&self) -> &'static str {
235
self.value.asset_type_name()
236
}
237
238
/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
239
pub fn get_labeled(
240
&self,
241
label: impl Into<CowArc<'static, str>>,
242
) -> Option<&ErasedLoadedAsset> {
243
self.labeled_assets.get(&label.into()).map(|a| &a.asset)
244
}
245
246
/// Iterate over all labels for "labeled assets" in the loaded asset
247
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
248
self.labeled_assets.keys().map(|s| &**s)
249
}
250
251
/// Cast this loaded asset as the given type. If the type does not match,
252
/// the original type-erased asset is returned.
253
pub fn downcast<A: Asset>(mut self) -> Result<LoadedAsset<A>, ErasedLoadedAsset> {
254
match self.value.downcast::<A>() {
255
Ok(value) => Ok(LoadedAsset {
256
value: *value,
257
dependencies: self.dependencies,
258
loader_dependencies: self.loader_dependencies,
259
labeled_assets: self.labeled_assets,
260
}),
261
Err(value) => {
262
self.value = value;
263
Err(self)
264
}
265
}
266
}
267
}
268
269
/// A type erased container for an [`Asset`] value that is capable of inserting the [`Asset`] into a [`World`]'s [`Assets`] collection.
270
pub trait AssetContainer: Downcast + Any + Send + Sync + 'static {
271
fn insert(self: Box<Self>, id: UntypedAssetId, world: &mut World);
272
fn asset_type_name(&self) -> &'static str;
273
}
274
275
impl_downcast!(AssetContainer);
276
277
impl<A: Asset> AssetContainer for A {
278
fn insert(self: Box<Self>, id: UntypedAssetId, world: &mut World) {
279
// We only ever call this if we know the asset is still alive, so it is fine to unwrap here.
280
world
281
.resource_mut::<Assets<A>>()
282
.insert(id.typed(), *self)
283
.expect("the AssetId is still valid");
284
}
285
286
fn asset_type_name(&self) -> &'static str {
287
core::any::type_name::<A>()
288
}
289
}
290
291
/// An error that occurs when attempting to call [`NestedLoader::load`] which
292
/// is configured to work [immediately].
293
///
294
/// [`NestedLoader::load`]: crate::NestedLoader::load
295
/// [immediately]: crate::Immediate
296
#[derive(Error, Debug)]
297
pub enum LoadDirectError {
298
#[error("Requested to load an asset path ({0:?}) with a subasset, but this is unsupported. See issue #18291")]
299
RequestedSubasset(AssetPath<'static>),
300
#[error("Failed to load dependency {dependency:?} {error}")]
301
LoadError {
302
dependency: AssetPath<'static>,
303
error: AssetLoadError,
304
},
305
}
306
307
/// An error that occurs while deserializing [`AssetMeta`].
308
#[derive(Error, Debug, Clone, PartialEq, Eq)]
309
pub enum DeserializeMetaError {
310
#[error("Failed to deserialize asset meta: {0:?}")]
311
DeserializeSettings(#[from] SpannedError),
312
#[error("Failed to deserialize minimal asset meta: {0:?}")]
313
DeserializeMinimal(SpannedError),
314
}
315
316
/// A context that provides access to assets in [`AssetLoader`]s, tracks dependencies, and collects asset load state.
317
///
318
/// Any asset state accessed by [`LoadContext`] will be tracked and stored for use in dependency events and asset preprocessing.
319
pub struct LoadContext<'a> {
320
pub(crate) asset_server: &'a AssetServer,
321
pub(crate) should_load_dependencies: bool,
322
populate_hashes: bool,
323
asset_path: AssetPath<'static>,
324
pub(crate) dependencies: HashSet<UntypedAssetId>,
325
/// Direct dependencies used by this loader.
326
pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
327
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
328
}
329
330
impl<'a> LoadContext<'a> {
331
/// Creates a new [`LoadContext`] instance.
332
pub(crate) fn new(
333
asset_server: &'a AssetServer,
334
asset_path: AssetPath<'static>,
335
should_load_dependencies: bool,
336
populate_hashes: bool,
337
) -> Self {
338
Self {
339
asset_server,
340
asset_path,
341
populate_hashes,
342
should_load_dependencies,
343
dependencies: HashSet::default(),
344
loader_dependencies: HashMap::default(),
345
labeled_assets: HashMap::default(),
346
}
347
}
348
349
/// Begins a new labeled asset load. Use the returned [`LoadContext`] to load
350
/// dependencies for the new asset and call [`LoadContext::finish`] to finalize the asset load.
351
/// When finished, make sure you call [`LoadContext::add_loaded_labeled_asset`] to add the results back to the parent
352
/// context.
353
/// Prefer [`LoadContext::labeled_asset_scope`] when possible, which will automatically add
354
/// the labeled [`LoadContext`] back to the parent context.
355
/// [`LoadContext::begin_labeled_asset`] exists largely to enable parallel asset loading.
356
///
357
/// See [`AssetPath`] for more on labeled assets.
358
///
359
/// ```no_run
360
/// # use bevy_asset::{Asset, LoadContext};
361
/// # use bevy_reflect::TypePath;
362
/// # #[derive(Asset, TypePath, Default)]
363
/// # struct Image;
364
/// # let load_context: LoadContext = panic!();
365
/// let mut handles = Vec::new();
366
/// for i in 0..2 {
367
/// let labeled = load_context.begin_labeled_asset();
368
/// handles.push(std::thread::spawn(move || {
369
/// (i.to_string(), labeled.finish(Image::default()))
370
/// }));
371
/// }
372
///
373
/// for handle in handles {
374
/// let (label, loaded_asset) = handle.join().unwrap();
375
/// load_context.add_loaded_labeled_asset(label, loaded_asset);
376
/// }
377
/// ```
378
pub fn begin_labeled_asset(&self) -> LoadContext<'_> {
379
LoadContext::new(
380
self.asset_server,
381
self.asset_path.clone(),
382
self.should_load_dependencies,
383
self.populate_hashes,
384
)
385
}
386
387
/// Creates a new [`LoadContext`] for the given `label`. The `load` function is responsible for loading an [`Asset`] of
388
/// type `A`. `load` will be called immediately and the result will be used to finalize the [`LoadContext`], resulting in a new
389
/// [`LoadedAsset`], which is registered under the `label` label.
390
///
391
/// This exists to remove the need to manually call [`LoadContext::begin_labeled_asset`] and then manually register the
392
/// result with [`LoadContext::add_loaded_labeled_asset`].
393
///
394
/// See [`AssetPath`] for more on labeled assets.
395
pub fn labeled_asset_scope<A: Asset, E>(
396
&mut self,
397
label: String,
398
load: impl FnOnce(&mut LoadContext) -> Result<A, E>,
399
) -> Result<Handle<A>, E> {
400
let mut context = self.begin_labeled_asset();
401
let asset = load(&mut context)?;
402
let loaded_asset = context.finish(asset);
403
Ok(self.add_loaded_labeled_asset(label, loaded_asset))
404
}
405
406
/// This will add the given `asset` as a "labeled [`Asset`]" with the `label` label.
407
///
408
/// # Warning
409
///
410
/// This will not assign dependencies to the given `asset`. If adding an asset
411
/// with dependencies generated from calls such as [`LoadContext::load`], use
412
/// [`LoadContext::labeled_asset_scope`] or [`LoadContext::begin_labeled_asset`] to generate a
413
/// new [`LoadContext`] to track the dependencies for the labeled asset.
414
///
415
/// See [`AssetPath`] for more on labeled assets.
416
pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
417
self.labeled_asset_scope(label, |_| Ok::<_, ()>(asset))
418
.expect("the closure returns Ok")
419
}
420
421
/// Add a [`LoadedAsset`] that is a "labeled sub asset" of the root path of this load context.
422
/// This can be used in combination with [`LoadContext::begin_labeled_asset`] to parallelize
423
/// sub asset loading.
424
///
425
/// See [`AssetPath`] for more on labeled assets.
426
pub fn add_loaded_labeled_asset<A: Asset>(
427
&mut self,
428
label: impl Into<CowArc<'static, str>>,
429
loaded_asset: LoadedAsset<A>,
430
) -> Handle<A> {
431
let label = label.into();
432
let loaded_asset: ErasedLoadedAsset = loaded_asset.into();
433
let labeled_path = self.asset_path.clone().with_label(label.clone());
434
let handle = self
435
.asset_server
436
.get_or_create_path_handle(labeled_path, None);
437
self.labeled_assets.insert(
438
label,
439
LabeledAsset {
440
asset: loaded_asset,
441
handle: handle.clone().untyped(),
442
},
443
);
444
handle
445
}
446
447
/// Returns `true` if an asset with the label `label` exists in this context.
448
///
449
/// See [`AssetPath`] for more on labeled assets.
450
pub fn has_labeled_asset<'b>(&self, label: impl Into<CowArc<'b, str>>) -> bool {
451
let path = self.asset_path.clone().with_label(label.into());
452
!self.asset_server.get_handles_untyped(&path).is_empty()
453
}
454
455
/// "Finishes" this context by populating the final [`Asset`] value.
456
pub fn finish<A: Asset>(self, value: A) -> LoadedAsset<A> {
457
LoadedAsset {
458
value,
459
dependencies: self.dependencies,
460
loader_dependencies: self.loader_dependencies,
461
labeled_assets: self.labeled_assets,
462
}
463
}
464
465
/// Gets the source path for this load context.
466
pub fn path(&self) -> &Path {
467
self.asset_path.path()
468
}
469
470
/// Gets the source asset path for this load context.
471
pub fn asset_path(&self) -> &AssetPath<'static> {
472
&self.asset_path
473
}
474
475
/// Reads the asset at the given path and returns its bytes
476
pub async fn read_asset_bytes<'b, 'c>(
477
&'b mut self,
478
path: impl Into<AssetPath<'c>>,
479
) -> Result<Vec<u8>, ReadAssetBytesError> {
480
let path = path.into();
481
let source = self.asset_server.get_source(path.source())?;
482
let asset_reader = match self.asset_server.mode() {
483
AssetServerMode::Unprocessed => source.reader(),
484
AssetServerMode::Processed => source.processed_reader()?,
485
};
486
let mut reader = asset_reader.read(path.path()).await?;
487
let hash = if self.populate_hashes {
488
// NOTE: ensure meta is read while the asset bytes reader is still active to ensure transactionality
489
// See `ProcessorGatedReader` for more info
490
let meta_bytes = asset_reader.read_meta_bytes(path.path()).await?;
491
let minimal: ProcessedInfoMinimal = ron::de::from_bytes(&meta_bytes)
492
.map_err(DeserializeMetaError::DeserializeMinimal)?;
493
let processed_info = minimal
494
.processed_info
495
.ok_or(ReadAssetBytesError::MissingAssetHash)?;
496
processed_info.full_hash
497
} else {
498
Default::default()
499
};
500
let mut bytes = Vec::new();
501
reader
502
.read_to_end(&mut bytes)
503
.await
504
.map_err(|source| ReadAssetBytesError::Io {
505
path: path.path().to_path_buf(),
506
source,
507
})?;
508
self.loader_dependencies.insert(path.clone_owned(), hash);
509
Ok(bytes)
510
}
511
512
/// Returns a handle to an asset of type `A` with the label `label`. This [`LoadContext`] must produce an asset of the
513
/// given type and the given label or the dependencies of this asset will never be considered "fully loaded". However you
514
/// can call this method before _or_ after adding the labeled asset.
515
pub fn get_label_handle<'b, A: Asset>(
516
&mut self,
517
label: impl Into<CowArc<'b, str>>,
518
) -> Handle<A> {
519
let path = self.asset_path.clone().with_label(label);
520
let handle = self.asset_server.get_or_create_path_handle::<A>(path, None);
521
self.dependencies.insert(handle.id().untyped());
522
handle
523
}
524
525
pub(crate) async fn load_direct_internal(
526
&mut self,
527
path: AssetPath<'static>,
528
meta: &dyn AssetMetaDyn,
529
loader: &dyn ErasedAssetLoader,
530
reader: &mut dyn Reader,
531
) -> Result<ErasedLoadedAsset, LoadDirectError> {
532
let loaded_asset = self
533
.asset_server
534
.load_with_meta_loader_and_reader(
535
&path,
536
meta,
537
loader,
538
reader,
539
false,
540
self.populate_hashes,
541
)
542
.await
543
.map_err(|error| LoadDirectError::LoadError {
544
dependency: path.clone(),
545
error,
546
})?;
547
let info = meta.processed_info().as_ref();
548
let hash = info.map(|i| i.full_hash).unwrap_or_default();
549
self.loader_dependencies.insert(path, hash);
550
Ok(loaded_asset)
551
}
552
553
/// Create a builder for loading nested assets in this context.
554
#[must_use]
555
pub fn loader(&mut self) -> NestedLoader<'a, '_, StaticTyped, Deferred> {
556
NestedLoader::new(self)
557
}
558
559
/// Retrieves a handle for the asset at the given path and adds that path as a dependency of the asset.
560
/// If the current context is a normal [`AssetServer::load`], an actual asset load will be kicked off immediately, which ensures the load happens
561
/// as soon as possible.
562
/// "Normal loads" kicked from within a normal Bevy App will generally configure the context to kick off loads immediately.
563
/// If the current context is configured to not load dependencies automatically (ex: [`AssetProcessor`](crate::processor::AssetProcessor)),
564
/// a load will not be kicked off automatically. It is then the calling context's responsibility to begin a load if necessary.
565
///
566
/// If you need to override asset settings, asset type, or load directly, please see [`LoadContext::loader`].
567
pub fn load<'b, A: Asset>(&mut self, path: impl Into<AssetPath<'b>>) -> Handle<A> {
568
self.loader().load(path)
569
}
570
}
571
572
/// An error produced when calling [`LoadContext::read_asset_bytes`]
573
#[derive(Error, Debug)]
574
pub enum ReadAssetBytesError {
575
#[error(transparent)]
576
DeserializeMetaError(#[from] DeserializeMetaError),
577
#[error(transparent)]
578
AssetReaderError(#[from] AssetReaderError),
579
#[error(transparent)]
580
MissingAssetSourceError(#[from] MissingAssetSourceError),
581
#[error(transparent)]
582
MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
583
/// Encountered an I/O error while loading an asset.
584
#[error("Encountered an io error while loading asset at `{}`: {source}", path.display())]
585
Io {
586
path: PathBuf,
587
source: std::io::Error,
588
},
589
#[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")]
590
MissingAssetHash,
591
}
592
593