1use crate::{
2 io::{
3 AssetReaderError, MissingAssetSourceError, MissingProcessedAssetReaderError, Reader,
4 ReaderRequiredFeatures,
5 },
6 loader_builders::{Deferred, NestedLoader, StaticTyped},
7 meta::{AssetHash, AssetMeta, AssetMetaDyn, ProcessedInfo, ProcessedInfoMinimal, Settings},
8 path::AssetPath,
9 Asset, AssetIndex, AssetLoadError, AssetServer, AssetServerMode, Assets, ErasedAssetIndex,
10 Handle, UntypedAssetId, UntypedHandle,
11};
12use alloc::{
13 boxed::Box,
14 string::{String, ToString},
15 vec::Vec,
16};
17use atomicow::CowArc;
18use bevy_ecs::{error::BevyError, world::World};
19use bevy_platform::collections::{HashMap, HashSet};
20use bevy_reflect::TypePath;
21use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
22use core::any::{Any, TypeId};
23use downcast_rs::{impl_downcast, Downcast};
24use ron::error::SpannedError;
25use serde::{Deserialize, Serialize};
26use std::path::PathBuf;
27use thiserror::Error;
28
29pub trait AssetLoader: TypePath + Send + Sync + 'static {
36 type Asset: Asset;
38 type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
40 type Error: Into<BevyError>;
42 fn load(
44 &self,
45 reader: &mut dyn Reader,
46 settings: &Self::Settings,
47 load_context: &mut LoadContext,
48 ) -> impl ConditionalSendFuture<Output = Result<Self::Asset, Self::Error>>;
49
50 fn reader_required_features(_settings: &Self::Settings) -> ReaderRequiredFeatures {
52 ReaderRequiredFeatures::default()
53 }
54
55 fn extensions(&self) -> &[&str] {
58 &[]
59 }
60}
61
62pub trait ErasedAssetLoader: Send + Sync + 'static {
64 fn load<'a>(
66 &'a self,
67 reader: &'a mut dyn Reader,
68 settings: &'a dyn Settings,
69 load_context: LoadContext<'a>,
70 ) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>>;
71
72 fn reader_required_features(&self, settings: &dyn Settings) -> ReaderRequiredFeatures;
75 fn extensions(&self) -> &[&str];
77 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
79 fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
81 fn type_path(&self) -> &'static str;
83 fn type_id(&self) -> TypeId;
85 fn asset_type_name(&self) -> &'static str;
87 fn asset_type_id(&self) -> TypeId;
89}
90
91impl<L> ErasedAssetLoader for L
92where
93 L: AssetLoader + Send + Sync,
94{
95 fn load<'a>(
97 &'a self,
98 reader: &'a mut dyn Reader,
99 settings: &'a dyn Settings,
100 mut load_context: LoadContext<'a>,
101 ) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>> {
102 Box::pin(async move {
103 let settings = settings
104 .downcast_ref::<L::Settings>()
105 .expect("AssetLoader settings should match the loader type");
106 let asset = <L as AssetLoader>::load(self, reader, settings, &mut load_context)
107 .await
108 .map_err(Into::into)?;
109 Ok(load_context.finish(asset).into())
110 })
111 }
112
113 fn reader_required_features(&self, settings: &dyn Settings) -> ReaderRequiredFeatures {
114 let settings = settings
115 .downcast_ref::<L::Settings>()
116 .expect("AssetLoader settings should match the loader type");
117 <L as AssetLoader>::reader_required_features(settings)
118 }
119
120 fn extensions(&self) -> &[&str] {
121 <L as AssetLoader>::extensions(self)
122 }
123
124 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
125 let meta = AssetMeta::<L, ()>::deserialize(meta)?;
126 Ok(Box::new(meta))
127 }
128
129 fn default_meta(&self) -> Box<dyn AssetMetaDyn> {
130 Box::new(AssetMeta::<L, ()>::new(crate::meta::AssetAction::Load {
131 loader: self.type_path().to_string(),
132 settings: L::Settings::default(),
133 }))
134 }
135
136 fn type_path(&self) -> &'static str {
137 L::type_path()
138 }
139
140 fn type_id(&self) -> TypeId {
141 TypeId::of::<L>()
142 }
143
144 fn asset_type_name(&self) -> &'static str {
145 core::any::type_name::<L::Asset>()
146 }
147
148 fn asset_type_id(&self) -> TypeId {
149 TypeId::of::<L::Asset>()
150 }
151}
152
153pub(crate) struct LabeledAsset {
154 pub(crate) asset: ErasedLoadedAsset,
155 pub(crate) handle: UntypedHandle,
156}
157
158pub struct LoadedAsset<A: Asset> {
163 pub(crate) value: A,
164 pub(crate) dependencies: HashSet<ErasedAssetIndex>,
165 pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
166 pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
167}
168
169impl<A: Asset> LoadedAsset<A> {
170 pub fn new_with_dependencies(value: A) -> Self {
172 let mut dependencies = <HashSet<_>>::default();
173 value.visit_dependencies(&mut |id| {
174 let Ok(asset_index) = id.try_into() else {
175 return;
176 };
177 dependencies.insert(asset_index);
178 });
179 LoadedAsset {
180 value,
181 dependencies,
182 loader_dependencies: HashMap::default(),
183 labeled_assets: HashMap::default(),
184 }
185 }
186
187 pub fn take(self) -> A {
189 self.value
190 }
191
192 pub fn get(&self) -> &A {
194 &self.value
195 }
196
197 pub fn get_labeled(
199 &self,
200 label: impl Into<CowArc<'static, str>>,
201 ) -> Option<&ErasedLoadedAsset> {
202 self.labeled_assets.get(&label.into()).map(|a| &a.asset)
203 }
204
205 pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
207 self.labeled_assets.keys().map(|s| &**s)
208 }
209}
210
211impl<A: Asset> From<A> for LoadedAsset<A> {
212 fn from(asset: A) -> Self {
213 LoadedAsset::new_with_dependencies(asset)
214 }
215}
216
217pub struct ErasedLoadedAsset {
219 pub(crate) value: Box<dyn AssetContainer>,
220 pub(crate) dependencies: HashSet<ErasedAssetIndex>,
221 pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
222 pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
223}
224
225impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset {
226 fn from(asset: LoadedAsset<A>) -> Self {
227 ErasedLoadedAsset {
228 value: Box::new(asset.value),
229 dependencies: asset.dependencies,
230 loader_dependencies: asset.loader_dependencies,
231 labeled_assets: asset.labeled_assets,
232 }
233 }
234}
235
236impl ErasedLoadedAsset {
237 pub fn take<A: Asset>(self) -> Option<A> {
240 self.value.downcast::<A>().map(|a| *a).ok()
241 }
242
243 pub fn get<A: Asset>(&self) -> Option<&A> {
245 self.value.downcast_ref::<A>()
246 }
247
248 pub fn asset_type_id(&self) -> TypeId {
250 (*self.value).type_id()
251 }
252
253 pub fn asset_type_name(&self) -> &'static str {
255 self.value.asset_type_name()
256 }
257
258 pub fn get_labeled(
260 &self,
261 label: impl Into<CowArc<'static, str>>,
262 ) -> Option<&ErasedLoadedAsset> {
263 self.labeled_assets.get(&label.into()).map(|a| &a.asset)
264 }
265
266 pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
268 self.labeled_assets.keys().map(|s| &**s)
269 }
270
271 pub fn downcast<A: Asset>(mut self) -> Result<LoadedAsset<A>, ErasedLoadedAsset> {
274 match self.value.downcast::<A>() {
275 Ok(value) => Ok(LoadedAsset {
276 value: *value,
277 dependencies: self.dependencies,
278 loader_dependencies: self.loader_dependencies,
279 labeled_assets: self.labeled_assets,
280 }),
281 Err(value) => {
282 self.value = value;
283 Err(self)
284 }
285 }
286 }
287}
288
289pub(crate) trait AssetContainer: Downcast + Any + Send + Sync + 'static {
291 fn insert(self: Box<Self>, id: AssetIndex, world: &mut World);
292 fn asset_type_name(&self) -> &'static str;
293}
294
295impl_downcast!(AssetContainer);
296
297impl<A: Asset> AssetContainer for A {
298 fn insert(self: Box<Self>, index: AssetIndex, world: &mut World) {
299 world
301 .resource_mut::<Assets<A>>()
302 .insert(index, *self)
303 .expect("the AssetIndex is still valid");
304 }
305
306 fn asset_type_name(&self) -> &'static str {
307 core::any::type_name::<A>()
308 }
309}
310
311#[derive(Error, Debug)]
317pub enum LoadDirectError {
318 #[error("Requested to load an asset path ({0:?}) with a subasset, but this is unsupported. See issue #18291")]
319 RequestedSubasset(AssetPath<'static>),
320 #[error("Failed to load dependency {dependency:?} {error}")]
321 LoadError {
322 dependency: AssetPath<'static>,
323 error: AssetLoadError,
324 },
325}
326
327#[derive(Error, Debug, Clone, PartialEq, Eq)]
329pub enum DeserializeMetaError {
330 #[error("Failed to deserialize asset meta: {0:?}")]
331 DeserializeSettings(#[from] SpannedError),
332 #[error("Failed to deserialize minimal asset meta: {0:?}")]
333 DeserializeMinimal(SpannedError),
334}
335
336pub struct LoadContext<'a> {
340 pub(crate) asset_server: &'a AssetServer,
341 pub(crate) should_load_dependencies: bool,
346 populate_hashes: bool,
347 asset_path: AssetPath<'static>,
348 pub(crate) dependencies: HashSet<ErasedAssetIndex>,
349 pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
351 pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
352}
353
354impl<'a> LoadContext<'a> {
355 pub(crate) fn new(
357 asset_server: &'a AssetServer,
358 asset_path: AssetPath<'static>,
359 should_load_dependencies: bool,
360 populate_hashes: bool,
361 ) -> Self {
362 Self {
363 asset_server,
364 asset_path,
365 populate_hashes,
366 should_load_dependencies,
367 dependencies: HashSet::default(),
368 loader_dependencies: HashMap::default(),
369 labeled_assets: HashMap::default(),
370 }
371 }
372
373 pub fn begin_labeled_asset(&self) -> LoadContext<'_> {
403 LoadContext::new(
404 self.asset_server,
405 self.asset_path.clone(),
406 self.should_load_dependencies,
407 self.populate_hashes,
408 )
409 }
410
411 pub fn labeled_asset_scope<A: Asset, E>(
420 &mut self,
421 label: String,
422 load: impl FnOnce(&mut LoadContext) -> Result<A, E>,
423 ) -> Result<Handle<A>, E> {
424 let mut context = self.begin_labeled_asset();
425 let asset = load(&mut context)?;
426 let loaded_asset = context.finish(asset);
427 Ok(self.add_loaded_labeled_asset(label, loaded_asset))
428 }
429
430 pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
441 self.labeled_asset_scope(label, |_| Ok::<_, ()>(asset))
442 .expect("the closure returns Ok")
443 }
444
445 pub fn add_loaded_labeled_asset<A: Asset>(
451 &mut self,
452 label: impl Into<CowArc<'static, str>>,
453 loaded_asset: LoadedAsset<A>,
454 ) -> Handle<A> {
455 let label = label.into();
456 let loaded_asset: ErasedLoadedAsset = loaded_asset.into();
457 let labeled_path = self.asset_path.clone().with_label(label.clone());
458 let handle = self
459 .asset_server
460 .get_or_create_path_handle(labeled_path, None);
461 self.labeled_assets.insert(
462 label,
463 LabeledAsset {
464 asset: loaded_asset,
465 handle: handle.clone().untyped(),
466 },
467 );
468 handle
469 }
470
471 pub fn has_labeled_asset<'b>(&self, label: impl Into<CowArc<'b, str>>) -> bool {
475 let path = self.asset_path.clone().with_label(label.into());
476 !self.asset_server.get_handles_untyped(&path).is_empty()
477 }
478
479 pub fn finish<A: Asset>(mut self, value: A) -> LoadedAsset<A> {
481 value.visit_dependencies(&mut |asset_id| {
488 let (type_id, index) = match asset_id {
489 UntypedAssetId::Index { type_id, index } => (type_id, index),
490 UntypedAssetId::Uuid { .. } => return,
492 };
493 self.dependencies
494 .insert(ErasedAssetIndex { index, type_id });
495 });
496 LoadedAsset {
497 value,
498 dependencies: self.dependencies,
499 loader_dependencies: self.loader_dependencies,
500 labeled_assets: self.labeled_assets,
501 }
502 }
503
504 pub fn path(&self) -> &AssetPath<'static> {
506 &self.asset_path
507 }
508
509 pub async fn read_asset_bytes<'b, 'c>(
511 &'b mut self,
512 path: impl Into<AssetPath<'c>>,
513 ) -> Result<Vec<u8>, ReadAssetBytesError> {
514 let path = path.into();
515 let source = self.asset_server.get_source(path.source())?;
516 let asset_reader = match self.asset_server.mode() {
517 AssetServerMode::Unprocessed => source.reader(),
518 AssetServerMode::Processed => source.processed_reader()?,
519 };
520 let mut reader = asset_reader
521 .read(path.path(), ReaderRequiredFeatures::default())
522 .await?;
523 let hash = if self.populate_hashes {
524 let meta_bytes = asset_reader.read_meta_bytes(path.path()).await?;
527 let minimal: ProcessedInfoMinimal = ron::de::from_bytes(&meta_bytes)
528 .map_err(DeserializeMetaError::DeserializeMinimal)?;
529 let processed_info = minimal
530 .processed_info
531 .ok_or(ReadAssetBytesError::MissingAssetHash)?;
532 processed_info.full_hash
533 } else {
534 Default::default()
535 };
536 let mut bytes = Vec::new();
537 reader
538 .read_to_end(&mut bytes)
539 .await
540 .map_err(|source| ReadAssetBytesError::Io {
541 path: path.path().to_path_buf(),
542 source,
543 })?;
544 self.loader_dependencies.insert(path.clone_owned(), hash);
545 Ok(bytes)
546 }
547
548 pub fn get_label_handle<'b, A: Asset>(
552 &mut self,
553 label: impl Into<CowArc<'b, str>>,
554 ) -> Handle<A> {
555 let path = self.asset_path.clone().with_label(label);
556 let handle = self.asset_server.get_or_create_path_handle::<A>(path, None);
557 let index = (&handle).try_into().unwrap();
559 self.dependencies.insert(index);
560 handle
561 }
562
563 pub(crate) async fn load_direct_internal(
564 &mut self,
565 path: AssetPath<'static>,
566 settings: &dyn Settings,
567 loader: &dyn ErasedAssetLoader,
568 reader: &mut dyn Reader,
569 processed_info: Option<&ProcessedInfo>,
570 ) -> Result<ErasedLoadedAsset, LoadDirectError> {
571 let loaded_asset = self
572 .asset_server
573 .load_with_settings_loader_and_reader(
574 &path,
575 settings,
576 loader,
577 reader,
578 self.should_load_dependencies,
579 self.populate_hashes,
580 )
581 .await
582 .map_err(|error| LoadDirectError::LoadError {
583 dependency: path.clone(),
584 error,
585 })?;
586 let hash = processed_info.map(|i| i.full_hash).unwrap_or_default();
587 self.loader_dependencies.insert(path, hash);
588 Ok(loaded_asset)
589 }
590
591 #[must_use]
593 pub fn loader(&mut self) -> NestedLoader<'a, '_, StaticTyped, Deferred> {
594 NestedLoader::new(self)
595 }
596
597 pub fn load<'b, A: Asset>(&mut self, path: impl Into<AssetPath<'b>>) -> Handle<A> {
606 self.loader().load(path)
607 }
608}
609
610#[derive(Error, Debug)]
612pub enum ReadAssetBytesError {
613 #[error(transparent)]
614 DeserializeMetaError(#[from] DeserializeMetaError),
615 #[error(transparent)]
616 AssetReaderError(#[from] AssetReaderError),
617 #[error(transparent)]
618 MissingAssetSourceError(#[from] MissingAssetSourceError),
619 #[error(transparent)]
620 MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
621 #[error("Encountered an io error while loading asset at `{}`: {source}", path.display())]
623 Io {
624 path: PathBuf,
625 source: std::io::Error,
626 },
627 #[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")]
628 MissingAssetHash,
629}