1use crate::{
2 io::{AssetReaderError, MissingAssetSourceError, MissingProcessedAssetReaderError, Reader},
3 loader_builders::NestedLoader,
4 meta::{AssetHash, AssetMeta, AssetMetaDyn, ProcessedInfoMinimal, Settings},
5 path::AssetPath,
6 Asset, AssetLoadError, AssetServer, AssetServerMode, Assets, Handle, UntypedAssetId,
7 UntypedHandle,
8};
9use bevy_ecs::world::World;
10use bevy_utils::{BoxedFuture, ConditionalSendFuture, CowArc, HashMap, HashSet};
11use downcast_rs::{impl_downcast, Downcast};
12use futures_lite::AsyncReadExt;
13use ron::error::SpannedError;
14use serde::{Deserialize, Serialize};
15use std::{
16 any::{Any, TypeId},
17 path::{Path, PathBuf},
18};
19use thiserror::Error;
20
21pub trait AssetLoader: Send + Sync + 'static {
24 type Asset: crate::Asset;
26 type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
28 type Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>;
30 fn load<'a>(
32 &'a self,
33 reader: &'a mut Reader,
34 settings: &'a Self::Settings,
35 load_context: &'a mut LoadContext,
36 ) -> impl ConditionalSendFuture<Output = Result<Self::Asset, Self::Error>>;
37
38 fn extensions(&self) -> &[&str] {
41 &[]
42 }
43}
44
45pub trait ErasedAssetLoader: Send + Sync + 'static {
47 fn load<'a>(
49 &'a self,
50 reader: &'a mut Reader,
51 meta: Box<dyn AssetMetaDyn>,
52 load_context: LoadContext<'a>,
53 ) -> BoxedFuture<
54 'a,
55 Result<ErasedLoadedAsset, Box<dyn std::error::Error + Send + Sync + 'static>>,
56 >;
57
58 fn extensions(&self) -> &[&str];
60 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
62 fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
64 fn type_name(&self) -> &'static str;
66 fn type_id(&self) -> TypeId;
68 fn asset_type_name(&self) -> &'static str;
70 fn asset_type_id(&self) -> TypeId;
72}
73
74impl<L> ErasedAssetLoader for L
75where
76 L: AssetLoader + Send + Sync,
77{
78 fn load<'a>(
80 &'a self,
81 reader: &'a mut Reader,
82 meta: Box<dyn AssetMetaDyn>,
83 mut load_context: LoadContext<'a>,
84 ) -> BoxedFuture<
85 'a,
86 Result<ErasedLoadedAsset, Box<dyn std::error::Error + Send + Sync + 'static>>,
87 > {
88 Box::pin(async move {
89 let settings = meta
90 .loader_settings()
91 .expect("Loader settings should exist")
92 .downcast_ref::<L::Settings>()
93 .expect("AssetLoader settings should match the loader type");
94 let asset = <L as AssetLoader>::load(self, reader, settings, &mut load_context)
95 .await
96 .map_err(|error| error.into())?;
97 Ok(load_context.finish(asset, Some(meta)).into())
98 })
99 }
100
101 fn extensions(&self) -> &[&str] {
102 <L as AssetLoader>::extensions(self)
103 }
104
105 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
106 let meta = AssetMeta::<L, ()>::deserialize(meta)?;
107 Ok(Box::new(meta))
108 }
109
110 fn default_meta(&self) -> Box<dyn AssetMetaDyn> {
111 Box::new(AssetMeta::<L, ()>::new(crate::meta::AssetAction::Load {
112 loader: self.type_name().to_string(),
113 settings: L::Settings::default(),
114 }))
115 }
116
117 fn type_name(&self) -> &'static str {
118 std::any::type_name::<L>()
119 }
120
121 fn type_id(&self) -> TypeId {
122 TypeId::of::<L>()
123 }
124
125 fn asset_type_name(&self) -> &'static str {
126 std::any::type_name::<L::Asset>()
127 }
128
129 fn asset_type_id(&self) -> TypeId {
130 TypeId::of::<L::Asset>()
131 }
132}
133
134pub(crate) struct LabeledAsset {
135 pub(crate) asset: ErasedLoadedAsset,
136 pub(crate) handle: UntypedHandle,
137}
138
139pub struct LoadedAsset<A: Asset> {
144 pub(crate) value: A,
145 pub(crate) dependencies: HashSet<UntypedAssetId>,
146 pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
147 pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
148 pub(crate) meta: Option<Box<dyn AssetMetaDyn>>,
149}
150
151impl<A: Asset> LoadedAsset<A> {
152 pub fn new_with_dependencies(value: A, meta: Option<Box<dyn AssetMetaDyn>>) -> Self {
154 let mut dependencies = HashSet::new();
155 value.visit_dependencies(&mut |id| {
156 dependencies.insert(id);
157 });
158 LoadedAsset {
159 value,
160 dependencies,
161 loader_dependencies: HashMap::default(),
162 labeled_assets: HashMap::default(),
163 meta,
164 }
165 }
166
167 pub fn take(self) -> A {
169 self.value
170 }
171
172 pub fn get(&self) -> &A {
174 &self.value
175 }
176
177 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 pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
187 self.labeled_assets.keys().map(|s| &**s)
188 }
189}
190
191impl<A: Asset> From<A> for LoadedAsset<A> {
192 fn from(asset: A) -> Self {
193 LoadedAsset::new_with_dependencies(asset, None)
194 }
195}
196
197pub 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 pub(crate) meta: Option<Box<dyn AssetMetaDyn>>,
204}
205
206impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset {
207 fn from(asset: LoadedAsset<A>) -> Self {
208 ErasedLoadedAsset {
209 value: Box::new(asset.value),
210 dependencies: asset.dependencies,
211 loader_dependencies: asset.loader_dependencies,
212 labeled_assets: asset.labeled_assets,
213 meta: asset.meta,
214 }
215 }
216}
217
218impl ErasedLoadedAsset {
219 pub fn take<A: Asset>(self) -> Option<A> {
222 self.value.downcast::<A>().map(|a| *a).ok()
223 }
224
225 pub fn get<A: Asset>(&self) -> Option<&A> {
227 self.value.downcast_ref::<A>()
228 }
229
230 pub fn asset_type_id(&self) -> TypeId {
232 (*self.value).type_id()
233 }
234
235 pub fn asset_type_name(&self) -> &'static str {
237 self.value.asset_type_name()
238 }
239
240 pub fn get_labeled(
242 &self,
243 label: impl Into<CowArc<'static, str>>,
244 ) -> Option<&ErasedLoadedAsset> {
245 self.labeled_assets.get(&label.into()).map(|a| &a.asset)
246 }
247
248 pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
250 self.labeled_assets.keys().map(|s| &**s)
251 }
252
253 #[allow(clippy::result_large_err)]
256 pub fn downcast<A: Asset>(mut self) -> Result<LoadedAsset<A>, ErasedLoadedAsset> {
257 match self.value.downcast::<A>() {
258 Ok(value) => Ok(LoadedAsset {
259 value: *value,
260 dependencies: self.dependencies,
261 loader_dependencies: self.loader_dependencies,
262 labeled_assets: self.labeled_assets,
263 meta: self.meta,
264 }),
265 Err(value) => {
266 self.value = value;
267 Err(self)
268 }
269 }
270 }
271}
272
273pub trait AssetContainer: Downcast + Any + Send + Sync + 'static {
275 fn insert(self: Box<Self>, id: UntypedAssetId, world: &mut World);
276 fn asset_type_name(&self) -> &'static str;
277}
278
279impl_downcast!(AssetContainer);
280
281impl<A: Asset> AssetContainer for A {
282 fn insert(self: Box<Self>, id: UntypedAssetId, world: &mut World) {
283 world.resource_mut::<Assets<A>>().insert(id.typed(), *self);
284 }
285
286 fn asset_type_name(&self) -> &'static str {
287 std::any::type_name::<A>()
288 }
289}
290
291#[derive(Error, Debug)]
293#[error("Failed to load dependency {dependency:?} {error}")]
294pub struct LoadDirectError {
295 pub dependency: AssetPath<'static>,
296 pub error: AssetLoadError,
297}
298
299#[derive(Error, Debug, Clone, PartialEq, Eq)]
301pub enum DeserializeMetaError {
302 #[error("Failed to deserialize asset meta: {0:?}")]
303 DeserializeSettings(#[from] SpannedError),
304 #[error("Failed to deserialize minimal asset meta: {0:?}")]
305 DeserializeMinimal(SpannedError),
306}
307
308pub struct LoadContext<'a> {
311 pub(crate) asset_server: &'a AssetServer,
312 pub(crate) should_load_dependencies: bool,
313 populate_hashes: bool,
314 asset_path: AssetPath<'static>,
315 pub(crate) dependencies: HashSet<UntypedAssetId>,
316 pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
318 pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
319}
320
321impl<'a> LoadContext<'a> {
322 pub(crate) fn new(
324 asset_server: &'a AssetServer,
325 asset_path: AssetPath<'static>,
326 should_load_dependencies: bool,
327 populate_hashes: bool,
328 ) -> Self {
329 Self {
330 asset_server,
331 asset_path,
332 populate_hashes,
333 should_load_dependencies,
334 dependencies: HashSet::default(),
335 loader_dependencies: HashMap::default(),
336 labeled_assets: HashMap::default(),
337 }
338 }
339
340 pub fn begin_labeled_asset(&self) -> LoadContext {
370 LoadContext::new(
371 self.asset_server,
372 self.asset_path.clone(),
373 self.should_load_dependencies,
374 self.populate_hashes,
375 )
376 }
377
378 pub fn labeled_asset_scope<A: Asset>(
387 &mut self,
388 label: String,
389 load: impl FnOnce(&mut LoadContext) -> A,
390 ) -> Handle<A> {
391 let mut context = self.begin_labeled_asset();
392 let asset = load(&mut context);
393 let loaded_asset = context.finish(asset, None);
394 self.add_loaded_labeled_asset(label, loaded_asset)
395 }
396
397 pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
408 self.labeled_asset_scope(label, |_| asset)
409 }
410
411 pub fn add_loaded_labeled_asset<A: Asset>(
417 &mut self,
418 label: impl Into<CowArc<'static, str>>,
419 loaded_asset: LoadedAsset<A>,
420 ) -> Handle<A> {
421 let label = label.into();
422 let loaded_asset: ErasedLoadedAsset = loaded_asset.into();
423 let labeled_path = self.asset_path.clone().with_label(label.clone());
424 let handle = self
425 .asset_server
426 .get_or_create_path_handle(labeled_path, None);
427 self.labeled_assets.insert(
428 label,
429 LabeledAsset {
430 asset: loaded_asset,
431 handle: handle.clone().untyped(),
432 },
433 );
434 handle
435 }
436
437 pub fn has_labeled_asset<'b>(&self, label: impl Into<CowArc<'b, str>>) -> bool {
441 let path = self.asset_path.clone().with_label(label.into());
442 !self.asset_server.get_handles_untyped(&path).is_empty()
443 }
444
445 pub fn finish<A: Asset>(self, value: A, meta: Option<Box<dyn AssetMetaDyn>>) -> LoadedAsset<A> {
448 LoadedAsset {
449 value,
450 dependencies: self.dependencies,
451 loader_dependencies: self.loader_dependencies,
452 labeled_assets: self.labeled_assets,
453 meta,
454 }
455 }
456
457 pub fn path(&self) -> &Path {
459 self.asset_path.path()
460 }
461
462 pub fn asset_path(&self) -> &AssetPath<'static> {
464 &self.asset_path
465 }
466
467 pub async fn read_asset_bytes<'b, 'c>(
469 &'b mut self,
470 path: impl Into<AssetPath<'c>>,
471 ) -> Result<Vec<u8>, ReadAssetBytesError> {
472 let path = path.into();
473 let source = self.asset_server.get_source(path.source())?;
474 let asset_reader = match self.asset_server.mode() {
475 AssetServerMode::Unprocessed { .. } => source.reader(),
476 AssetServerMode::Processed { .. } => source.processed_reader()?,
477 };
478 let mut reader = asset_reader.read(path.path()).await?;
479 let hash = if self.populate_hashes {
480 let meta_bytes = asset_reader.read_meta_bytes(path.path()).await?;
483 let minimal: ProcessedInfoMinimal = ron::de::from_bytes(&meta_bytes)
484 .map_err(DeserializeMetaError::DeserializeMinimal)?;
485 let processed_info = minimal
486 .processed_info
487 .ok_or(ReadAssetBytesError::MissingAssetHash)?;
488 processed_info.full_hash
489 } else {
490 Default::default()
491 };
492 let mut bytes = Vec::new();
493 reader
494 .read_to_end(&mut bytes)
495 .await
496 .map_err(|source| ReadAssetBytesError::Io {
497 path: path.path().to_path_buf(),
498 source,
499 })?;
500 self.loader_dependencies.insert(path.clone_owned(), hash);
501 Ok(bytes)
502 }
503
504 pub fn get_label_handle<'b, A: Asset>(
508 &mut self,
509 label: impl Into<CowArc<'b, str>>,
510 ) -> Handle<A> {
511 let path = self.asset_path.clone().with_label(label);
512 let handle = self.asset_server.get_or_create_path_handle::<A>(path, None);
513 self.dependencies.insert(handle.id().untyped());
514 handle
515 }
516
517 pub(crate) async fn load_direct_internal(
518 &mut self,
519 path: AssetPath<'static>,
520 meta: Box<dyn AssetMetaDyn>,
521 loader: &dyn ErasedAssetLoader,
522 reader: &mut Reader<'_>,
523 ) -> Result<ErasedLoadedAsset, LoadDirectError> {
524 let loaded_asset = self
525 .asset_server
526 .load_with_meta_loader_and_reader(
527 &path,
528 meta,
529 loader,
530 reader,
531 false,
532 self.populate_hashes,
533 )
534 .await
535 .map_err(|error| LoadDirectError {
536 dependency: path.clone(),
537 error,
538 })?;
539 let info = loaded_asset
540 .meta
541 .as_ref()
542 .and_then(|m| m.processed_info().as_ref());
543 let hash = info.map(|i| i.full_hash).unwrap_or(Default::default());
544 self.loader_dependencies.insert(path, hash);
545 Ok(loaded_asset)
546 }
547
548 #[must_use]
550 pub fn loader(&mut self) -> NestedLoader<'a, '_> {
551 NestedLoader::new(self)
552 }
553
554 pub fn load<'b, A: Asset>(&mut self, path: impl Into<AssetPath<'b>>) -> Handle<A> {
563 self.loader().load(path)
564 }
565}
566
567#[derive(Error, Debug)]
569pub enum ReadAssetBytesError {
570 #[error(transparent)]
571 DeserializeMetaError(#[from] DeserializeMetaError),
572 #[error(transparent)]
573 AssetReaderError(#[from] AssetReaderError),
574 #[error(transparent)]
575 MissingAssetSourceError(#[from] MissingAssetSourceError),
576 #[error(transparent)]
577 MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
578 #[error("Encountered an io error while loading asset at `{path}`: {source}")]
580 Io {
581 path: PathBuf,
582 #[source]
583 source: std::io::Error,
584 },
585 #[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")]
586 MissingAssetHash,
587}