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 atomicow::CowArc;
10use bevy_ecs::world::World;
11use bevy_utils::{BoxedFuture, ConditionalSendFuture, HashMap, HashSet};
12use core::any::{Any, TypeId};
13use derive_more::derive::{Display, Error, From};
14use downcast_rs::{impl_downcast, Downcast};
15use ron::error::SpannedError;
16use serde::{Deserialize, Serialize};
17use std::path::{Path, PathBuf};
18
19pub trait AssetLoader: Send + Sync + 'static {
26 type Asset: Asset;
28 type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
30 type Error: Into<Box<dyn core::error::Error + Send + Sync + 'static>>;
32 fn load(
34 &self,
35 reader: &mut dyn Reader,
36 settings: &Self::Settings,
37 load_context: &mut LoadContext,
38 ) -> impl ConditionalSendFuture<Output = Result<Self::Asset, Self::Error>>;
39
40 fn extensions(&self) -> &[&str] {
43 &[]
44 }
45}
46
47pub trait ErasedAssetLoader: Send + Sync + 'static {
49 fn load<'a>(
51 &'a self,
52 reader: &'a mut dyn Reader,
53 meta: Box<dyn AssetMetaDyn>,
54 load_context: LoadContext<'a>,
55 ) -> BoxedFuture<
56 'a,
57 Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
58 >;
59
60 fn extensions(&self) -> &[&str];
62 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
64 fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
66 fn type_name(&self) -> &'static str;
68 fn type_id(&self) -> TypeId;
70 fn asset_type_name(&self) -> &'static str;
72 fn asset_type_id(&self) -> TypeId;
74}
75
76impl<L> ErasedAssetLoader for L
77where
78 L: AssetLoader + Send + Sync,
79{
80 fn load<'a>(
82 &'a self,
83 reader: &'a mut dyn Reader,
84 meta: Box<dyn AssetMetaDyn>,
85 mut load_context: LoadContext<'a>,
86 ) -> BoxedFuture<
87 'a,
88 Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
89 > {
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, Some(meta)).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 pub(crate) meta: Option<Box<dyn AssetMetaDyn>>,
151}
152
153impl<A: Asset> LoadedAsset<A> {
154 pub fn new_with_dependencies(value: A, meta: Option<Box<dyn AssetMetaDyn>>) -> Self {
156 let mut dependencies = HashSet::new();
157 value.visit_dependencies(&mut |id| {
158 dependencies.insert(id);
159 });
160 LoadedAsset {
161 value,
162 dependencies,
163 loader_dependencies: HashMap::default(),
164 labeled_assets: HashMap::default(),
165 meta,
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, None)
196 }
197}
198
199pub struct ErasedLoadedAsset {
201 pub(crate) value: Box<dyn AssetContainer>,
202 pub(crate) dependencies: HashSet<UntypedAssetId>,
203 pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
204 pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
205 pub(crate) meta: Option<Box<dyn AssetMetaDyn>>,
206}
207
208impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset {
209 fn from(asset: LoadedAsset<A>) -> Self {
210 ErasedLoadedAsset {
211 value: Box::new(asset.value),
212 dependencies: asset.dependencies,
213 loader_dependencies: asset.loader_dependencies,
214 labeled_assets: asset.labeled_assets,
215 meta: asset.meta,
216 }
217 }
218}
219
220impl ErasedLoadedAsset {
221 pub fn take<A: Asset>(self) -> Option<A> {
224 self.value.downcast::<A>().map(|a| *a).ok()
225 }
226
227 pub fn get<A: Asset>(&self) -> Option<&A> {
229 self.value.downcast_ref::<A>()
230 }
231
232 pub fn asset_type_id(&self) -> TypeId {
234 (*self.value).type_id()
235 }
236
237 pub fn asset_type_name(&self) -> &'static str {
239 self.value.asset_type_name()
240 }
241
242 pub fn get_labeled(
244 &self,
245 label: impl Into<CowArc<'static, str>>,
246 ) -> Option<&ErasedLoadedAsset> {
247 self.labeled_assets.get(&label.into()).map(|a| &a.asset)
248 }
249
250 pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
252 self.labeled_assets.keys().map(|s| &**s)
253 }
254
255 #[expect(clippy::result_large_err, reason = "Function returns `Self` on error.")]
258 pub fn downcast<A: Asset>(mut self) -> Result<LoadedAsset<A>, ErasedLoadedAsset> {
259 match self.value.downcast::<A>() {
260 Ok(value) => Ok(LoadedAsset {
261 value: *value,
262 dependencies: self.dependencies,
263 loader_dependencies: self.loader_dependencies,
264 labeled_assets: self.labeled_assets,
265 meta: self.meta,
266 }),
267 Err(value) => {
268 self.value = value;
269 Err(self)
270 }
271 }
272 }
273}
274
275pub trait AssetContainer: Downcast + Any + Send + Sync + 'static {
277 fn insert(self: Box<Self>, id: UntypedAssetId, world: &mut World);
278 fn asset_type_name(&self) -> &'static str;
279}
280
281impl_downcast!(AssetContainer);
282
283impl<A: Asset> AssetContainer for A {
284 fn insert(self: Box<Self>, id: UntypedAssetId, world: &mut World) {
285 world.resource_mut::<Assets<A>>().insert(id.typed(), *self);
286 }
287
288 fn asset_type_name(&self) -> &'static str {
289 core::any::type_name::<A>()
290 }
291}
292
293#[derive(Error, Display, Debug)]
299#[display("Failed to load dependency {dependency:?} {error}")]
300pub struct LoadDirectError {
301 pub dependency: AssetPath<'static>,
302 pub error: AssetLoadError,
303}
304
305#[derive(Error, Display, Debug, Clone, PartialEq, Eq, From)]
307pub enum DeserializeMetaError {
308 #[display("Failed to deserialize asset meta: {_0:?}")]
309 DeserializeSettings(SpannedError),
310 #[display("Failed to deserialize minimal asset meta: {_0:?}")]
311 #[from(ignore)]
312 DeserializeMinimal(SpannedError),
313}
314
315pub struct LoadContext<'a> {
319 pub(crate) asset_server: &'a AssetServer,
320 pub(crate) should_load_dependencies: bool,
321 populate_hashes: bool,
322 asset_path: AssetPath<'static>,
323 pub(crate) dependencies: HashSet<UntypedAssetId>,
324 pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
326 pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
327}
328
329impl<'a> LoadContext<'a> {
330 pub(crate) fn new(
332 asset_server: &'a AssetServer,
333 asset_path: AssetPath<'static>,
334 should_load_dependencies: bool,
335 populate_hashes: bool,
336 ) -> Self {
337 Self {
338 asset_server,
339 asset_path,
340 populate_hashes,
341 should_load_dependencies,
342 dependencies: HashSet::default(),
343 loader_dependencies: HashMap::default(),
344 labeled_assets: HashMap::default(),
345 }
346 }
347
348 pub fn begin_labeled_asset(&self) -> LoadContext {
378 LoadContext::new(
379 self.asset_server,
380 self.asset_path.clone(),
381 self.should_load_dependencies,
382 self.populate_hashes,
383 )
384 }
385
386 pub fn labeled_asset_scope<A: Asset>(
395 &mut self,
396 label: String,
397 load: impl FnOnce(&mut LoadContext) -> A,
398 ) -> Handle<A> {
399 let mut context = self.begin_labeled_asset();
400 let asset = load(&mut context);
401 let loaded_asset = context.finish(asset, None);
402 self.add_loaded_labeled_asset(label, loaded_asset)
403 }
404
405 pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
416 self.labeled_asset_scope(label, |_| asset)
417 }
418
419 pub fn add_loaded_labeled_asset<A: Asset>(
425 &mut self,
426 label: impl Into<CowArc<'static, str>>,
427 loaded_asset: LoadedAsset<A>,
428 ) -> Handle<A> {
429 let label = label.into();
430 let loaded_asset: ErasedLoadedAsset = loaded_asset.into();
431 let labeled_path = self.asset_path.clone().with_label(label.clone());
432 let handle = self
433 .asset_server
434 .get_or_create_path_handle(labeled_path, None);
435 self.labeled_assets.insert(
436 label,
437 LabeledAsset {
438 asset: loaded_asset,
439 handle: handle.clone().untyped(),
440 },
441 );
442 handle
443 }
444
445 pub fn has_labeled_asset<'b>(&self, label: impl Into<CowArc<'b, str>>) -> bool {
449 let path = self.asset_path.clone().with_label(label.into());
450 !self.asset_server.get_handles_untyped(&path).is_empty()
451 }
452
453 pub fn finish<A: Asset>(self, value: A, meta: Option<Box<dyn AssetMetaDyn>>) -> LoadedAsset<A> {
456 LoadedAsset {
457 value,
458 dependencies: self.dependencies,
459 loader_dependencies: self.loader_dependencies,
460 labeled_assets: self.labeled_assets,
461 meta,
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: Box<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 {
544 dependency: path.clone(),
545 error,
546 })?;
547 let info = loaded_asset
548 .meta
549 .as_ref()
550 .and_then(|m| m.processed_info().as_ref());
551 let hash = info.map(|i| i.full_hash).unwrap_or_default();
552 self.loader_dependencies.insert(path, hash);
553 Ok(loaded_asset)
554 }
555
556 #[must_use]
558 pub fn loader(&mut self) -> NestedLoader<'a, '_, StaticTyped, Deferred> {
559 NestedLoader::new(self)
560 }
561
562 pub fn load<'b, A: Asset>(&mut self, path: impl Into<AssetPath<'b>>) -> Handle<A> {
571 self.loader().load(path)
572 }
573}
574
575#[derive(Error, Display, Debug, From)]
577pub enum ReadAssetBytesError {
578 DeserializeMetaError(DeserializeMetaError),
579 AssetReaderError(AssetReaderError),
580 MissingAssetSourceError(MissingAssetSourceError),
581 MissingProcessedAssetReaderError(MissingProcessedAssetReaderError),
582 #[display("Encountered an io error while loading asset at `{}`: {source}", path.display())]
584 Io {
585 path: PathBuf,
586 source: std::io::Error,
587 },
588 #[display("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")]
589 MissingAssetHash,
590}