1use crate::{
2 io::{AssetReaderError, MissingAssetSourceError, MissingProcessedAssetReaderError, Reader},
3 loader_builders::{Deferred, NestedLoader, StaticTyped},
4 meta::{AssetHash, AssetMeta, AssetMetaDyn, ProcessedInfo, ProcessedInfoMinimal, Settings},
5 path::AssetPath,
6 Asset, AssetIndex, AssetLoadError, AssetServer, AssetServerMode, Assets, ErasedAssetIndex,
7 Handle, UntypedAssetId, 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_reflect::TypePath;
18use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
19use core::any::{Any, TypeId};
20use downcast_rs::{impl_downcast, Downcast};
21use ron::error::SpannedError;
22use serde::{Deserialize, Serialize};
23use std::path::PathBuf;
24use thiserror::Error;
25
26pub trait AssetLoader: TypePath + Send + Sync + 'static {
33 type Asset: Asset;
35 type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
37 type Error: Into<BevyError>;
39 fn load(
41 &self,
42 reader: &mut dyn Reader,
43 settings: &Self::Settings,
44 load_context: &mut LoadContext,
45 ) -> impl ConditionalSendFuture<Output = Result<Self::Asset, Self::Error>>;
46
47 fn extensions(&self) -> &[&str] {
50 &[]
51 }
52}
53
54pub trait ErasedAssetLoader: Send + Sync + 'static {
56 fn load<'a>(
58 &'a self,
59 reader: &'a mut dyn Reader,
60 settings: &'a dyn Settings,
61 load_context: LoadContext<'a>,
62 ) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>>;
63
64 fn extensions(&self) -> &[&str];
66 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
68 fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
70 fn type_path(&self) -> &'static str;
72 fn type_id(&self) -> TypeId;
74 fn asset_type_name(&self) -> &'static str;
76 fn asset_type_id(&self) -> TypeId;
78}
79
80impl<L> ErasedAssetLoader for L
81where
82 L: AssetLoader + Send + Sync,
83{
84 fn load<'a>(
86 &'a self,
87 reader: &'a mut dyn Reader,
88 settings: &'a dyn Settings,
89 mut load_context: LoadContext<'a>,
90 ) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>> {
91 Box::pin(async move {
92 let settings = settings
93 .downcast_ref::<L::Settings>()
94 .expect("AssetLoader settings should match the loader type");
95 let asset = <L as AssetLoader>::load(self, reader, settings, &mut load_context)
96 .await
97 .map_err(Into::into)?;
98 Ok(load_context.finish(asset).into())
99 })
100 }
101
102 fn extensions(&self) -> &[&str] {
103 <L as AssetLoader>::extensions(self)
104 }
105
106 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
107 let meta = AssetMeta::<L, ()>::deserialize(meta)?;
108 Ok(Box::new(meta))
109 }
110
111 fn default_meta(&self) -> Box<dyn AssetMetaDyn> {
112 Box::new(AssetMeta::<L, ()>::new(crate::meta::AssetAction::Load {
113 loader: self.type_path().to_string(),
114 settings: L::Settings::default(),
115 }))
116 }
117
118 fn type_path(&self) -> &'static str {
119 L::type_path()
120 }
121
122 fn type_id(&self) -> TypeId {
123 TypeId::of::<L>()
124 }
125
126 fn asset_type_name(&self) -> &'static str {
127 core::any::type_name::<L::Asset>()
128 }
129
130 fn asset_type_id(&self) -> TypeId {
131 TypeId::of::<L::Asset>()
132 }
133}
134
135pub(crate) struct LabeledAsset {
136 pub(crate) asset: ErasedLoadedAsset,
137 pub(crate) handle: UntypedHandle,
138}
139
140pub struct LoadedAsset<A: Asset> {
145 pub(crate) value: A,
146 pub(crate) dependencies: HashSet<ErasedAssetIndex>,
147 pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
148 pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
149}
150
151impl<A: Asset> LoadedAsset<A> {
152 pub fn new_with_dependencies(value: A) -> Self {
154 let mut dependencies = <HashSet<_>>::default();
155 value.visit_dependencies(&mut |id| {
156 let Ok(asset_index) = id.try_into() else {
157 return;
158 };
159 dependencies.insert(asset_index);
160 });
161 LoadedAsset {
162 value,
163 dependencies,
164 loader_dependencies: HashMap::default(),
165 labeled_assets: HashMap::default(),
166 }
167 }
168
169 pub fn take(self) -> A {
171 self.value
172 }
173
174 pub fn get(&self) -> &A {
176 &self.value
177 }
178
179 pub fn get_labeled(
181 &self,
182 label: impl Into<CowArc<'static, str>>,
183 ) -> Option<&ErasedLoadedAsset> {
184 self.labeled_assets.get(&label.into()).map(|a| &a.asset)
185 }
186
187 pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
189 self.labeled_assets.keys().map(|s| &**s)
190 }
191}
192
193impl<A: Asset> From<A> for LoadedAsset<A> {
194 fn from(asset: A) -> Self {
195 LoadedAsset::new_with_dependencies(asset)
196 }
197}
198
199pub struct ErasedLoadedAsset {
201 pub(crate) value: Box<dyn AssetContainer>,
202 pub(crate) dependencies: HashSet<ErasedAssetIndex>,
203 pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
204 pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
205}
206
207impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset {
208 fn from(asset: LoadedAsset<A>) -> Self {
209 ErasedLoadedAsset {
210 value: Box::new(asset.value),
211 dependencies: asset.dependencies,
212 loader_dependencies: asset.loader_dependencies,
213 labeled_assets: asset.labeled_assets,
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 pub fn downcast<A: Asset>(mut self) -> Result<LoadedAsset<A>, ErasedLoadedAsset> {
256 match self.value.downcast::<A>() {
257 Ok(value) => Ok(LoadedAsset {
258 value: *value,
259 dependencies: self.dependencies,
260 loader_dependencies: self.loader_dependencies,
261 labeled_assets: self.labeled_assets,
262 }),
263 Err(value) => {
264 self.value = value;
265 Err(self)
266 }
267 }
268 }
269}
270
271pub(crate) trait AssetContainer: Downcast + Any + Send + Sync + 'static {
273 fn insert(self: Box<Self>, id: AssetIndex, world: &mut World);
274 fn asset_type_name(&self) -> &'static str;
275}
276
277impl_downcast!(AssetContainer);
278
279impl<A: Asset> AssetContainer for A {
280 fn insert(self: Box<Self>, index: AssetIndex, world: &mut World) {
281 world
283 .resource_mut::<Assets<A>>()
284 .insert(index, *self)
285 .expect("the AssetIndex is still valid");
286 }
287
288 fn asset_type_name(&self) -> &'static str {
289 core::any::type_name::<A>()
290 }
291}
292
293#[derive(Error, Debug)]
299pub enum LoadDirectError {
300 #[error("Requested to load an asset path ({0:?}) with a subasset, but this is unsupported. See issue #18291")]
301 RequestedSubasset(AssetPath<'static>),
302 #[error("Failed to load dependency {dependency:?} {error}")]
303 LoadError {
304 dependency: AssetPath<'static>,
305 error: AssetLoadError,
306 },
307}
308
309#[derive(Error, Debug, Clone, PartialEq, Eq)]
311pub enum DeserializeMetaError {
312 #[error("Failed to deserialize asset meta: {0:?}")]
313 DeserializeSettings(#[from] SpannedError),
314 #[error("Failed to deserialize minimal asset meta: {0:?}")]
315 DeserializeMinimal(SpannedError),
316}
317
318pub struct LoadContext<'a> {
322 pub(crate) asset_server: &'a AssetServer,
323 pub(crate) should_load_dependencies: bool,
328 populate_hashes: bool,
329 asset_path: AssetPath<'static>,
330 pub(crate) dependencies: HashSet<ErasedAssetIndex>,
331 pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
333 pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
334}
335
336impl<'a> LoadContext<'a> {
337 pub(crate) fn new(
339 asset_server: &'a AssetServer,
340 asset_path: AssetPath<'static>,
341 should_load_dependencies: bool,
342 populate_hashes: bool,
343 ) -> Self {
344 Self {
345 asset_server,
346 asset_path,
347 populate_hashes,
348 should_load_dependencies,
349 dependencies: HashSet::default(),
350 loader_dependencies: HashMap::default(),
351 labeled_assets: HashMap::default(),
352 }
353 }
354
355 pub fn begin_labeled_asset(&self) -> LoadContext<'_> {
385 LoadContext::new(
386 self.asset_server,
387 self.asset_path.clone(),
388 self.should_load_dependencies,
389 self.populate_hashes,
390 )
391 }
392
393 pub fn labeled_asset_scope<A: Asset, E>(
402 &mut self,
403 label: String,
404 load: impl FnOnce(&mut LoadContext) -> Result<A, E>,
405 ) -> Result<Handle<A>, E> {
406 let mut context = self.begin_labeled_asset();
407 let asset = load(&mut context)?;
408 let loaded_asset = context.finish(asset);
409 Ok(self.add_loaded_labeled_asset(label, loaded_asset))
410 }
411
412 pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
423 self.labeled_asset_scope(label, |_| Ok::<_, ()>(asset))
424 .expect("the closure returns Ok")
425 }
426
427 pub fn add_loaded_labeled_asset<A: Asset>(
433 &mut self,
434 label: impl Into<CowArc<'static, str>>,
435 loaded_asset: LoadedAsset<A>,
436 ) -> Handle<A> {
437 let label = label.into();
438 let loaded_asset: ErasedLoadedAsset = loaded_asset.into();
439 let labeled_path = self.asset_path.clone().with_label(label.clone());
440 let handle = self
441 .asset_server
442 .get_or_create_path_handle(labeled_path, None);
443 self.labeled_assets.insert(
444 label,
445 LabeledAsset {
446 asset: loaded_asset,
447 handle: handle.clone().untyped(),
448 },
449 );
450 handle
451 }
452
453 pub fn has_labeled_asset<'b>(&self, label: impl Into<CowArc<'b, str>>) -> bool {
457 let path = self.asset_path.clone().with_label(label.into());
458 !self.asset_server.get_handles_untyped(&path).is_empty()
459 }
460
461 pub fn finish<A: Asset>(mut self, value: A) -> LoadedAsset<A> {
463 value.visit_dependencies(&mut |asset_id| {
470 let (type_id, index) = match asset_id {
471 UntypedAssetId::Index { type_id, index } => (type_id, index),
472 UntypedAssetId::Uuid { .. } => return,
474 };
475 self.dependencies
476 .insert(ErasedAssetIndex { index, type_id });
477 });
478 LoadedAsset {
479 value,
480 dependencies: self.dependencies,
481 loader_dependencies: self.loader_dependencies,
482 labeled_assets: self.labeled_assets,
483 }
484 }
485
486 pub fn path(&self) -> &AssetPath<'static> {
488 &self.asset_path
489 }
490
491 pub async fn read_asset_bytes<'b, 'c>(
493 &'b mut self,
494 path: impl Into<AssetPath<'c>>,
495 ) -> Result<Vec<u8>, ReadAssetBytesError> {
496 let path = path.into();
497 let source = self.asset_server.get_source(path.source())?;
498 let asset_reader = match self.asset_server.mode() {
499 AssetServerMode::Unprocessed => source.reader(),
500 AssetServerMode::Processed => source.processed_reader()?,
501 };
502 let mut reader = asset_reader.read(path.path()).await?;
503 let hash = if self.populate_hashes {
504 let meta_bytes = asset_reader.read_meta_bytes(path.path()).await?;
507 let minimal: ProcessedInfoMinimal = ron::de::from_bytes(&meta_bytes)
508 .map_err(DeserializeMetaError::DeserializeMinimal)?;
509 let processed_info = minimal
510 .processed_info
511 .ok_or(ReadAssetBytesError::MissingAssetHash)?;
512 processed_info.full_hash
513 } else {
514 Default::default()
515 };
516 let mut bytes = Vec::new();
517 reader
518 .read_to_end(&mut bytes)
519 .await
520 .map_err(|source| ReadAssetBytesError::Io {
521 path: path.path().to_path_buf(),
522 source,
523 })?;
524 self.loader_dependencies.insert(path.clone_owned(), hash);
525 Ok(bytes)
526 }
527
528 pub fn get_label_handle<'b, A: Asset>(
532 &mut self,
533 label: impl Into<CowArc<'b, str>>,
534 ) -> Handle<A> {
535 let path = self.asset_path.clone().with_label(label);
536 let handle = self.asset_server.get_or_create_path_handle::<A>(path, None);
537 let index = (&handle).try_into().unwrap();
539 self.dependencies.insert(index);
540 handle
541 }
542
543 pub(crate) async fn load_direct_internal(
544 &mut self,
545 path: AssetPath<'static>,
546 settings: &dyn Settings,
547 loader: &dyn ErasedAssetLoader,
548 reader: &mut dyn Reader,
549 processed_info: Option<&ProcessedInfo>,
550 ) -> Result<ErasedLoadedAsset, LoadDirectError> {
551 let loaded_asset = self
552 .asset_server
553 .load_with_settings_loader_and_reader(
554 &path,
555 settings,
556 loader,
557 reader,
558 self.should_load_dependencies,
559 self.populate_hashes,
560 )
561 .await
562 .map_err(|error| LoadDirectError::LoadError {
563 dependency: path.clone(),
564 error,
565 })?;
566 let hash = processed_info.map(|i| i.full_hash).unwrap_or_default();
567 self.loader_dependencies.insert(path, hash);
568 Ok(loaded_asset)
569 }
570
571 #[must_use]
573 pub fn loader(&mut self) -> NestedLoader<'a, '_, StaticTyped, Deferred> {
574 NestedLoader::new(self)
575 }
576
577 pub fn load<'b, A: Asset>(&mut self, path: impl Into<AssetPath<'b>>) -> Handle<A> {
586 self.loader().load(path)
587 }
588}
589
590#[derive(Error, Debug)]
592pub enum ReadAssetBytesError {
593 #[error(transparent)]
594 DeserializeMetaError(#[from] DeserializeMetaError),
595 #[error(transparent)]
596 AssetReaderError(#[from] AssetReaderError),
597 #[error(transparent)]
598 MissingAssetSourceError(#[from] MissingAssetSourceError),
599 #[error(transparent)]
600 MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
601 #[error("Encountered an io error while loading asset at `{}`: {source}", path.display())]
603 Io {
604 path: PathBuf,
605 source: std::io::Error,
606 },
607 #[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")]
608 MissingAssetHash,
609}