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::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<Box<dyn core::error::Error + Send + Sync + 'static>>;
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<
62 'a,
63 Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
64 >;
65
66 fn extensions(&self) -> &[&str];
68 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
70 fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
72 fn type_name(&self) -> &'static str;
74 fn type_id(&self) -> TypeId;
76 fn asset_type_name(&self) -> &'static str;
78 fn asset_type_id(&self) -> TypeId;
80}
81
82impl<L> ErasedAssetLoader for L
83where
84 L: AssetLoader + Send + Sync,
85{
86 fn load<'a>(
88 &'a self,
89 reader: &'a mut dyn Reader,
90 meta: &'a dyn AssetMetaDyn,
91 mut load_context: LoadContext<'a>,
92 ) -> BoxedFuture<
93 'a,
94 Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
95 > {
96 Box::pin(async move {
97 let settings = meta
98 .loader_settings()
99 .expect("Loader settings should exist")
100 .downcast_ref::<L::Settings>()
101 .expect("AssetLoader settings should match the loader type");
102 let asset = <L as AssetLoader>::load(self, reader, settings, &mut load_context)
103 .await
104 .map_err(Into::into)?;
105 Ok(load_context.finish(asset).into())
106 })
107 }
108
109 fn extensions(&self) -> &[&str] {
110 <L as AssetLoader>::extensions(self)
111 }
112
113 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
114 let meta = AssetMeta::<L, ()>::deserialize(meta)?;
115 Ok(Box::new(meta))
116 }
117
118 fn default_meta(&self) -> Box<dyn AssetMetaDyn> {
119 Box::new(AssetMeta::<L, ()>::new(crate::meta::AssetAction::Load {
120 loader: self.type_name().to_string(),
121 settings: L::Settings::default(),
122 }))
123 }
124
125 fn type_name(&self) -> &'static str {
126 core::any::type_name::<L>()
127 }
128
129 fn type_id(&self) -> TypeId {
130 TypeId::of::<L>()
131 }
132
133 fn asset_type_name(&self) -> &'static str {
134 core::any::type_name::<L::Asset>()
135 }
136
137 fn asset_type_id(&self) -> TypeId {
138 TypeId::of::<L::Asset>()
139 }
140}
141
142pub(crate) struct LabeledAsset {
143 pub(crate) asset: ErasedLoadedAsset,
144 pub(crate) handle: UntypedHandle,
145}
146
147pub struct LoadedAsset<A: Asset> {
152 pub(crate) value: A,
153 pub(crate) dependencies: HashSet<UntypedAssetId>,
154 pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
155 pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
156}
157
158impl<A: Asset> LoadedAsset<A> {
159 pub fn new_with_dependencies(value: A) -> Self {
161 let mut dependencies = <HashSet<_>>::default();
162 value.visit_dependencies(&mut |id| {
163 dependencies.insert(id);
164 });
165 LoadedAsset {
166 value,
167 dependencies,
168 loader_dependencies: HashMap::default(),
169 labeled_assets: HashMap::default(),
170 }
171 }
172
173 pub fn take(self) -> A {
175 self.value
176 }
177
178 pub fn get(&self) -> &A {
180 &self.value
181 }
182
183 pub fn get_labeled(
185 &self,
186 label: impl Into<CowArc<'static, str>>,
187 ) -> Option<&ErasedLoadedAsset> {
188 self.labeled_assets.get(&label.into()).map(|a| &a.asset)
189 }
190
191 pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
193 self.labeled_assets.keys().map(|s| &**s)
194 }
195}
196
197impl<A: Asset> From<A> for LoadedAsset<A> {
198 fn from(asset: A) -> Self {
199 LoadedAsset::new_with_dependencies(asset)
200 }
201}
202
203pub struct ErasedLoadedAsset {
205 pub(crate) value: Box<dyn AssetContainer>,
206 pub(crate) dependencies: HashSet<UntypedAssetId>,
207 pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
208 pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
209}
210
211impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset {
212 fn from(asset: LoadedAsset<A>) -> Self {
213 ErasedLoadedAsset {
214 value: Box::new(asset.value),
215 dependencies: asset.dependencies,
216 loader_dependencies: asset.loader_dependencies,
217 labeled_assets: asset.labeled_assets,
218 }
219 }
220}
221
222impl ErasedLoadedAsset {
223 pub fn take<A: Asset>(self) -> Option<A> {
226 self.value.downcast::<A>().map(|a| *a).ok()
227 }
228
229 pub fn get<A: Asset>(&self) -> Option<&A> {
231 self.value.downcast_ref::<A>()
232 }
233
234 pub fn asset_type_id(&self) -> TypeId {
236 (*self.value).type_id()
237 }
238
239 pub fn asset_type_name(&self) -> &'static str {
241 self.value.asset_type_name()
242 }
243
244 pub fn get_labeled(
246 &self,
247 label: impl Into<CowArc<'static, str>>,
248 ) -> Option<&ErasedLoadedAsset> {
249 self.labeled_assets.get(&label.into()).map(|a| &a.asset)
250 }
251
252 pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
254 self.labeled_assets.keys().map(|s| &**s)
255 }
256
257 pub fn downcast<A: Asset>(mut self) -> Result<LoadedAsset<A>, ErasedLoadedAsset> {
260 match self.value.downcast::<A>() {
261 Ok(value) => Ok(LoadedAsset {
262 value: *value,
263 dependencies: self.dependencies,
264 loader_dependencies: self.loader_dependencies,
265 labeled_assets: self.labeled_assets,
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, 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,
324 populate_hashes: bool,
325 asset_path: AssetPath<'static>,
326 pub(crate) dependencies: HashSet<UntypedAssetId>,
327 pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
329 pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
330}
331
332impl<'a> LoadContext<'a> {
333 pub(crate) fn new(
335 asset_server: &'a AssetServer,
336 asset_path: AssetPath<'static>,
337 should_load_dependencies: bool,
338 populate_hashes: bool,
339 ) -> Self {
340 Self {
341 asset_server,
342 asset_path,
343 populate_hashes,
344 should_load_dependencies,
345 dependencies: HashSet::default(),
346 loader_dependencies: HashMap::default(),
347 labeled_assets: HashMap::default(),
348 }
349 }
350
351 pub fn begin_labeled_asset(&self) -> LoadContext {
381 LoadContext::new(
382 self.asset_server,
383 self.asset_path.clone(),
384 self.should_load_dependencies,
385 self.populate_hashes,
386 )
387 }
388
389 pub fn labeled_asset_scope<A: Asset>(
398 &mut self,
399 label: String,
400 load: impl FnOnce(&mut LoadContext) -> A,
401 ) -> Handle<A> {
402 let mut context = self.begin_labeled_asset();
403 let asset = load(&mut context);
404 let loaded_asset = context.finish(asset);
405 self.add_loaded_labeled_asset(label, loaded_asset)
406 }
407
408 pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
419 self.labeled_asset_scope(label, |_| asset)
420 }
421
422 pub fn add_loaded_labeled_asset<A: Asset>(
428 &mut self,
429 label: impl Into<CowArc<'static, str>>,
430 loaded_asset: LoadedAsset<A>,
431 ) -> Handle<A> {
432 let label = label.into();
433 let loaded_asset: ErasedLoadedAsset = loaded_asset.into();
434 let labeled_path = self.asset_path.clone().with_label(label.clone());
435 let handle = self
436 .asset_server
437 .get_or_create_path_handle(labeled_path, None);
438 self.labeled_assets.insert(
439 label,
440 LabeledAsset {
441 asset: loaded_asset,
442 handle: handle.clone().untyped(),
443 },
444 );
445 handle
446 }
447
448 pub fn has_labeled_asset<'b>(&self, label: impl Into<CowArc<'b, str>>) -> bool {
452 let path = self.asset_path.clone().with_label(label.into());
453 !self.asset_server.get_handles_untyped(&path).is_empty()
454 }
455
456 pub fn finish<A: Asset>(self, value: A) -> LoadedAsset<A> {
458 LoadedAsset {
459 value,
460 dependencies: self.dependencies,
461 loader_dependencies: self.loader_dependencies,
462 labeled_assets: self.labeled_assets,
463 }
464 }
465
466 pub fn path(&self) -> &Path {
468 self.asset_path.path()
469 }
470
471 pub fn asset_path(&self) -> &AssetPath<'static> {
473 &self.asset_path
474 }
475
476 pub async fn read_asset_bytes<'b, 'c>(
478 &'b mut self,
479 path: impl Into<AssetPath<'c>>,
480 ) -> Result<Vec<u8>, ReadAssetBytesError> {
481 let path = path.into();
482 let source = self.asset_server.get_source(path.source())?;
483 let asset_reader = match self.asset_server.mode() {
484 AssetServerMode::Unprocessed => source.reader(),
485 AssetServerMode::Processed => source.processed_reader()?,
486 };
487 let mut reader = asset_reader.read(path.path()).await?;
488 let hash = if self.populate_hashes {
489 let meta_bytes = asset_reader.read_meta_bytes(path.path()).await?;
492 let minimal: ProcessedInfoMinimal = ron::de::from_bytes(&meta_bytes)
493 .map_err(DeserializeMetaError::DeserializeMinimal)?;
494 let processed_info = minimal
495 .processed_info
496 .ok_or(ReadAssetBytesError::MissingAssetHash)?;
497 processed_info.full_hash
498 } else {
499 Default::default()
500 };
501 let mut bytes = Vec::new();
502 reader
503 .read_to_end(&mut bytes)
504 .await
505 .map_err(|source| ReadAssetBytesError::Io {
506 path: path.path().to_path_buf(),
507 source,
508 })?;
509 self.loader_dependencies.insert(path.clone_owned(), hash);
510 Ok(bytes)
511 }
512
513 pub fn get_label_handle<'b, A: Asset>(
517 &mut self,
518 label: impl Into<CowArc<'b, str>>,
519 ) -> Handle<A> {
520 let path = self.asset_path.clone().with_label(label);
521 let handle = self.asset_server.get_or_create_path_handle::<A>(path, None);
522 self.dependencies.insert(handle.id().untyped());
523 handle
524 }
525
526 pub(crate) async fn load_direct_internal(
527 &mut self,
528 path: AssetPath<'static>,
529 meta: &dyn AssetMetaDyn,
530 loader: &dyn ErasedAssetLoader,
531 reader: &mut dyn Reader,
532 ) -> Result<ErasedLoadedAsset, LoadDirectError> {
533 let loaded_asset = self
534 .asset_server
535 .load_with_meta_loader_and_reader(
536 &path,
537 meta,
538 loader,
539 reader,
540 false,
541 self.populate_hashes,
542 )
543 .await
544 .map_err(|error| LoadDirectError::LoadError {
545 dependency: path.clone(),
546 error,
547 })?;
548 let info = meta.processed_info().as_ref();
549 let hash = info.map(|i| i.full_hash).unwrap_or_default();
550 self.loader_dependencies.insert(path, hash);
551 Ok(loaded_asset)
552 }
553
554 #[must_use]
556 pub fn loader(&mut self) -> NestedLoader<'a, '_, StaticTyped, Deferred> {
557 NestedLoader::new(self)
558 }
559
560 pub fn load<'b, A: Asset>(&mut self, path: impl Into<AssetPath<'b>>) -> Handle<A> {
569 self.loader().load(path)
570 }
571}
572
573#[derive(Error, Debug)]
575pub enum ReadAssetBytesError {
576 #[error(transparent)]
577 DeserializeMetaError(#[from] DeserializeMetaError),
578 #[error(transparent)]
579 AssetReaderError(#[from] AssetReaderError),
580 #[error(transparent)]
581 MissingAssetSourceError(#[from] MissingAssetSourceError),
582 #[error(transparent)]
583 MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
584 #[error("Encountered an io error while loading asset at `{}`: {source}", path.display())]
586 Io {
587 path: PathBuf,
588 source: std::io::Error,
589 },
590 #[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")]
591 MissingAssetHash,
592}