1use 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};
9use alloc::{
10 boxed::Box,
11 string::{String, ToString},
12 vec::Vec,
13};
14use atomicow::CowArc;
15use bevy_ecs::{error::BevyError, world::World};
16use bevy_platform::collections::{HashMap, HashSet};
17use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
18use core::any::{Any, TypeId};
19use downcast_rs::{impl_downcast, Downcast};
20use ron::error::SpannedError;
21use serde::{Deserialize, Serialize};
22use std::path::{Path, PathBuf};
23use thiserror::Error;
24
25pub trait AssetLoader: Send + Sync + 'static {
32 type Asset: Asset;
34 type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
36 type Error: Into<BevyError>;
38 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 fn extensions(&self) -> &[&str] {
49 &[]
50 }
51}
52
53pub trait ErasedAssetLoader: Send + Sync + 'static {
55 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 fn extensions(&self) -> &[&str];
65 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
67 fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
69 fn type_name(&self) -> &'static str;
71 fn type_id(&self) -> TypeId;
73 fn asset_type_name(&self) -> &'static str;
75 fn asset_type_id(&self) -> TypeId;
77}
78
79impl<L> ErasedAssetLoader for L
80where
81 L: AssetLoader + Send + Sync,
82{
83 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
136pub(crate) struct LabeledAsset {
137 pub(crate) asset: ErasedLoadedAsset,
138 pub(crate) handle: UntypedHandle,
139}
140
141pub 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
152impl<A: Asset> LoadedAsset<A> {
153 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 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)
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}
204
205impl<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
216impl ErasedLoadedAsset {
217 pub fn take<A: Asset>(self) -> Option<A> {
220 self.value.downcast::<A>().map(|a| *a).ok()
221 }
222
223 pub fn get<A: Asset>(&self) -> Option<&A> {
225 self.value.downcast_ref::<A>()
226 }
227
228 pub fn asset_type_id(&self) -> TypeId {
230 (*self.value).type_id()
231 }
232
233 pub fn asset_type_name(&self) -> &'static str {
235 self.value.asset_type_name()
236 }
237
238 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 pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
248 self.labeled_assets.keys().map(|s| &**s)
249 }
250
251 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
269pub 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
275impl_downcast!(AssetContainer);
276
277impl<A: Asset> AssetContainer for A {
278 fn insert(self: Box<Self>, id: UntypedAssetId, world: &mut World) {
279 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#[derive(Error, Debug)]
297pub 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#[derive(Error, Debug, Clone, PartialEq, Eq)]
309pub 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
316pub 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 pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
327 pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
328}
329
330impl<'a> LoadContext<'a> {
331 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 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 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 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 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 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 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 pub fn path(&self) -> &Path {
467 self.asset_path.path()
468 }
469
470 pub fn asset_path(&self) -> &AssetPath<'static> {
472 &self.asset_path
473 }
474
475 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 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 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 #[must_use]
555 pub fn loader(&mut self) -> NestedLoader<'a, '_, StaticTyped, Deferred> {
556 NestedLoader::new(self)
557 }
558
559 pub fn load<'b, A: Asset>(&mut self, path: impl Into<AssetPath<'b>>) -> Handle<A> {
568 self.loader().load(path)
569 }
570}
571
572#[derive(Error, Debug)]
574pub 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 #[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}