use crate::{ io::{AssetReaderError, MissingAssetSourceError, MissingProcessedAssetReaderError, Reader}, meta::{ loader_settings_meta_transform, AssetHash, AssetMeta, AssetMetaDyn, ProcessedInfoMinimal, Settings, }, path::AssetPath, Asset, AssetLoadError, AssetServer, AssetServerMode, Assets, Handle, UntypedAssetId, UntypedHandle, }; use bevy_ecs::world::World; use bevy_utils::{BoxedFuture, CowArc, HashMap, HashSet}; use downcast_rs::{impl_downcast, Downcast}; use futures_lite::AsyncReadExt; use ron::error::SpannedError; use serde::{Deserialize, Serialize}; use std::{ any::{Any, TypeId}, path::Path, }; use thiserror::Error; /// Loads an [`Asset`] from a given byte [`Reader`]. This can accept [`AssetLoader::Settings`], which configure how the [`Asset`] /// should be loaded. pub trait AssetLoader: Send + Sync + 'static { /// The top level [`Asset`] loaded by this [`AssetLoader`]. type Asset: crate::Asset; /// The settings type used by this [`AssetLoader`]. type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>; /// The type of [error](`std::error::Error`) which could be encountered by this loader. type Error: std::error::Error + Send + Sync + 'static; /// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`]. fn load<'a>( &'a self, reader: &'a mut Reader, settings: &'a Self::Settings, load_context: &'a mut LoadContext, ) -> BoxedFuture<'a, Result>; /// Returns a list of extensions supported by this asset loader, without the preceding dot. fn extensions(&self) -> &[&str]; } /// Provides type-erased access to an [`AssetLoader`]. pub trait ErasedAssetLoader: Send + Sync + 'static { /// Asynchronously loads the asset(s) from the bytes provided by [`Reader`]. fn load<'a>( &'a self, reader: &'a mut Reader, meta: Box, load_context: LoadContext<'a>, ) -> BoxedFuture< 'a, Result>, >; /// Returns a list of extensions supported by this asset loader, without the preceding dot. fn extensions(&self) -> &[&str]; /// Deserializes metadata from the input `meta` bytes into the appropriate type (erased as [`Box`]). fn deserialize_meta(&self, meta: &[u8]) -> Result, DeserializeMetaError>; /// Returns the default meta value for the [`AssetLoader`] (erased as [`Box`]). fn default_meta(&self) -> Box; /// Returns the type name of the [`AssetLoader`]. fn type_name(&self) -> &'static str; /// Returns the [`TypeId`] of the [`AssetLoader`]. fn type_id(&self) -> TypeId; /// Returns the type name of the top-level [`Asset`] loaded by the [`AssetLoader`]. fn asset_type_name(&self) -> &'static str; /// Returns the [`TypeId`] of the top-level [`Asset`] loaded by the [`AssetLoader`]. fn asset_type_id(&self) -> TypeId; } impl ErasedAssetLoader for L where L: AssetLoader + Send + Sync, { /// Processes the asset in an asynchronous closure. fn load<'a>( &'a self, reader: &'a mut Reader, meta: Box, mut load_context: LoadContext<'a>, ) -> BoxedFuture< 'a, Result>, > { Box::pin(async move { let settings = meta .loader_settings() .expect("Loader settings should exist") .downcast_ref::() .expect("AssetLoader settings should match the loader type"); let asset = ::load(self, reader, settings, &mut load_context).await?; Ok(load_context.finish(asset, Some(meta)).into()) }) } fn deserialize_meta(&self, meta: &[u8]) -> Result, DeserializeMetaError> { let meta = AssetMeta::::deserialize(meta)?; Ok(Box::new(meta)) } fn default_meta(&self) -> Box { Box::new(AssetMeta::::new(crate::meta::AssetAction::Load { loader: self.type_name().to_string(), settings: L::Settings::default(), })) } fn extensions(&self) -> &[&str] { ::extensions(self) } fn type_name(&self) -> &'static str { std::any::type_name::() } fn type_id(&self) -> TypeId { TypeId::of::() } fn asset_type_id(&self) -> TypeId { TypeId::of::() } fn asset_type_name(&self) -> &'static str { std::any::type_name::() } } pub(crate) struct LabeledAsset { pub(crate) asset: ErasedLoadedAsset, pub(crate) handle: UntypedHandle, } /// The successful result of an [`AssetLoader::load`] call. This contains the loaded "root" asset and any other "labeled" assets produced /// by the loader. It also holds the input [`AssetMeta`] (if it exists) and tracks dependencies: /// * normal dependencies: dependencies that must be loaded as part of this asset load (ex: assets a given asset has handles to). /// * Loader dependencies: dependencies whose actual asset values are used during the load process pub struct LoadedAsset { pub(crate) value: A, pub(crate) dependencies: HashSet, pub(crate) loader_dependencies: HashMap, AssetHash>, pub(crate) labeled_assets: HashMap, LabeledAsset>, pub(crate) meta: Option>, } impl LoadedAsset { /// Create a new loaded asset. This will use [`VisitAssetDependencies`](crate::VisitAssetDependencies) to populate `dependencies`. pub fn new_with_dependencies(value: A, meta: Option>) -> Self { let mut dependencies = HashSet::new(); value.visit_dependencies(&mut |id| { dependencies.insert(id); }); LoadedAsset { value, dependencies, loader_dependencies: HashMap::default(), labeled_assets: HashMap::default(), meta, } } } impl From for LoadedAsset { fn from(asset: A) -> Self { LoadedAsset::new_with_dependencies(asset, None) } } /// A "type erased / boxed" counterpart to [`LoadedAsset`]. This is used in places where the loaded type is not statically known. pub struct ErasedLoadedAsset { pub(crate) value: Box, pub(crate) dependencies: HashSet, pub(crate) loader_dependencies: HashMap, AssetHash>, pub(crate) labeled_assets: HashMap, LabeledAsset>, pub(crate) meta: Option>, } impl From> for ErasedLoadedAsset { fn from(asset: LoadedAsset) -> Self { ErasedLoadedAsset { value: Box::new(asset.value), dependencies: asset.dependencies, loader_dependencies: asset.loader_dependencies, labeled_assets: asset.labeled_assets, meta: asset.meta, } } } impl ErasedLoadedAsset { /// Cast (and take ownership) of the [`Asset`] value of the given type. This will return [`Some`] if /// the stored type matches `A` and [`None`] if it does not. pub fn take(self) -> Option { self.value.downcast::().map(|a| *a).ok() } /// Retrieves a reference to the internal [`Asset`] type, if it matches the type `A`. Otherwise returns [`None`]. pub fn get(&self) -> Option<&A> { self.value.downcast_ref::() } /// Retrieves the [`TypeId`] of the stored [`Asset`] type. pub fn asset_type_id(&self) -> TypeId { (*self.value).type_id() } /// Retrieves the `type_name` of the stored [`Asset`] type. pub fn asset_type_name(&self) -> &'static str { self.value.asset_type_name() } /// Returns the [`ErasedLoadedAsset`] for the given label, if it exists. pub fn get_labeled( &self, label: impl Into>, ) -> Option<&ErasedLoadedAsset> { self.labeled_assets.get(&label.into()).map(|a| &a.asset) } /// Iterate over all labels for "labeled assets" in the loaded asset pub fn iter_labels(&self) -> impl Iterator { self.labeled_assets.keys().map(|s| &**s) } } /// A type erased container for an [`Asset`] value that is capable of inserting the [`Asset`] into a [`World`]'s [`Assets`] collection. pub trait AssetContainer: Downcast + Any + Send + Sync + 'static { fn insert(self: Box, id: UntypedAssetId, world: &mut World); fn asset_type_name(&self) -> &'static str; } impl_downcast!(AssetContainer); impl AssetContainer for A { fn insert(self: Box, id: UntypedAssetId, world: &mut World) { world.resource_mut::>().insert(id.typed(), *self); } fn asset_type_name(&self) -> &'static str { std::any::type_name::() } } /// An error that occurs when attempting to call [`LoadContext::load_direct`] #[derive(Error, Debug)] #[error("Failed to load dependency {dependency:?} {error}")] pub struct LoadDirectError { pub dependency: AssetPath<'static>, pub error: AssetLoadError, } /// An error that occurs while deserializing [`AssetMeta`]. #[derive(Error, Debug)] pub enum DeserializeMetaError { #[error("Failed to deserialize asset meta: {0:?}")] DeserializeSettings(#[from] SpannedError), #[error("Failed to deserialize minimal asset meta: {0:?}")] DeserializeMinimal(SpannedError), } /// A context that provides access to assets in [`AssetLoader`]s, tracks dependencies, and collects asset load state. /// Any asset state accessed by [`LoadContext`] will be tracked and stored for use in dependency events and asset preprocessing. pub struct LoadContext<'a> { asset_server: &'a AssetServer, should_load_dependencies: bool, populate_hashes: bool, asset_path: AssetPath<'static>, dependencies: HashSet, /// Direct dependencies used by this loader. loader_dependencies: HashMap, AssetHash>, labeled_assets: HashMap, LabeledAsset>, } impl<'a> LoadContext<'a> { /// Creates a new [`LoadContext`] instance. pub(crate) fn new( asset_server: &'a AssetServer, asset_path: AssetPath<'static>, should_load_dependencies: bool, populate_hashes: bool, ) -> Self { Self { asset_server, asset_path, populate_hashes, should_load_dependencies, dependencies: HashSet::default(), loader_dependencies: HashMap::default(), labeled_assets: HashMap::default(), } } /// Begins a new labeled asset load. Use the returned [`LoadContext`] to load /// dependencies for the new asset and call [`LoadContext::finish`] to finalize the asset load. /// When finished, make sure you call [`LoadContext::add_labeled_asset`] to add the results back to the parent /// context. /// Prefer [`LoadContext::labeled_asset_scope`] when possible, which will automatically add /// the labeled [`LoadContext`] back to the parent context. /// [`LoadContext::begin_labeled_asset`] exists largely to enable parallel asset loading. /// /// See [`AssetPath`] for more on labeled assets. /// /// ```no_run /// # use bevy_asset::{Asset, LoadContext}; /// # use bevy_reflect::TypePath; /// # #[derive(Asset, TypePath, Default)] /// # struct Image; /// # let load_context: LoadContext = panic!(); /// let mut handles = Vec::new(); /// for i in 0..2 { /// let mut labeled = load_context.begin_labeled_asset(); /// handles.push(std::thread::spawn(move || { /// (i.to_string(), labeled.finish(Image::default(), None)) /// })); /// } /// for handle in handles { /// let (label, loaded_asset) = handle.join().unwrap(); /// load_context.add_loaded_labeled_asset(label, loaded_asset); /// } /// ``` pub fn begin_labeled_asset(&self) -> LoadContext { LoadContext::new( self.asset_server, self.asset_path.clone(), self.should_load_dependencies, self.populate_hashes, ) } /// Creates a new [`LoadContext`] for the given `label`. The `load` function is responsible for loading an [`Asset`] of /// type `A`. `load` will be called immediately and the result will be used to finalize the [`LoadContext`], resulting in a new /// [`LoadedAsset`], which is registered under the `label` label. /// /// This exists to remove the need to manually call [`LoadContext::begin_labeled_asset`] and then manually register the /// result with [`LoadContext::add_labeled_asset`]. /// /// See [`AssetPath`] for more on labeled assets. pub fn labeled_asset_scope( &mut self, label: String, load: impl FnOnce(&mut LoadContext) -> A, ) -> Handle { let mut context = self.begin_labeled_asset(); let asset = load(&mut context); let loaded_asset = context.finish(asset, None); self.add_loaded_labeled_asset(label, loaded_asset) } /// This will add the given `asset` as a "labeled [`Asset`]" with the `label` label. /// /// See [`AssetPath`] for more on labeled assets. pub fn add_labeled_asset(&mut self, label: String, asset: A) -> Handle { self.labeled_asset_scope(label, |_| asset) } /// Add a [`LoadedAsset`] that is a "labeled sub asset" of the root path of this load context. /// This can be used in combination with [`LoadContext::begin_labeled_asset`] to parallelize /// sub asset loading. /// /// See [`AssetPath`] for more on labeled assets. pub fn add_loaded_labeled_asset( &mut self, label: impl Into>, loaded_asset: LoadedAsset, ) -> Handle { let label = label.into(); let loaded_asset: ErasedLoadedAsset = loaded_asset.into(); let labeled_path = self.asset_path.clone().with_label(label.clone()); let handle = self .asset_server .get_or_create_path_handle(labeled_path, None); self.labeled_assets.insert( label, LabeledAsset { asset: loaded_asset, handle: handle.clone().untyped(), }, ); handle } /// Returns `true` if an asset with the label `label` exists in this context. /// /// See [`AssetPath`] for more on labeled assets. pub fn has_labeled_asset<'b>(&self, label: impl Into>) -> bool { let path = self.asset_path.clone().with_label(label.into()); self.asset_server.get_handle_untyped(&path).is_some() } /// "Finishes" this context by populating the final [`Asset`] value (and the erased [`AssetMeta`] value, if it exists). /// The relevant asset metadata collected in this context will be stored in the returned [`LoadedAsset`]. pub fn finish(self, value: A, meta: Option>) -> LoadedAsset { LoadedAsset { value, dependencies: self.dependencies, loader_dependencies: self.loader_dependencies, labeled_assets: self.labeled_assets, meta, } } /// Gets the source path for this load context. pub fn path(&self) -> &Path { self.asset_path.path() } /// Gets the source asset path for this load context. pub fn asset_path(&self) -> &AssetPath<'static> { &self.asset_path } /// Gets the source asset path for this load context. pub async fn read_asset_bytes<'b, 'c>( &'b mut self, path: impl Into>, ) -> Result, ReadAssetBytesError> { let path = path.into(); let source = self.asset_server.get_source(path.source())?; let asset_reader = match self.asset_server.mode() { AssetServerMode::Unprocessed { .. } => source.reader(), AssetServerMode::Processed { .. } => source.processed_reader()?, }; let mut reader = asset_reader.read(path.path()).await?; let hash = if self.populate_hashes { // NOTE: ensure meta is read while the asset bytes reader is still active to ensure transactionality // See `ProcessorGatedReader` for more info let meta_bytes = asset_reader.read_meta_bytes(path.path()).await?; let minimal: ProcessedInfoMinimal = ron::de::from_bytes(&meta_bytes) .map_err(DeserializeMetaError::DeserializeMinimal)?; let processed_info = minimal .processed_info .ok_or(ReadAssetBytesError::MissingAssetHash)?; processed_info.full_hash } else { Default::default() }; let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?; self.loader_dependencies.insert(path.clone_owned(), hash); Ok(bytes) } /// Retrieves a handle for the asset at the given path and adds that path as a dependency of the asset. /// If the current context is a normal [`AssetServer::load`], an actual asset load will be kicked off immediately, which ensures the load happens /// as soon as possible. /// "Normal loads" kicked from within a normal Bevy App will generally configure the context to kick off loads immediately. /// If the current context is configured to not load dependencies automatically (ex: [`AssetProcessor`](crate::processor::AssetProcessor)), /// a load will not be kicked off automatically. It is then the calling context's responsibility to begin a load if necessary. pub fn load<'b, A: Asset>(&mut self, path: impl Into>) -> Handle { let path = path.into().to_owned(); let handle = if self.should_load_dependencies { self.asset_server.load(path) } else { self.asset_server.get_or_create_path_handle(path, None) }; self.dependencies.insert(handle.id().untyped()); handle } /// Loads the [`Asset`] of type `A` at the given `path` with the given [`AssetLoader::Settings`] settings `S`. This is a "deferred" /// load. If the settings type `S` does not match the settings expected by `A`'s asset loader, an error will be printed to the log /// and the asset load will fail. pub fn load_with_settings<'b, A: Asset, S: Settings + Default>( &mut self, path: impl Into>, settings: impl Fn(&mut S) + Send + Sync + 'static, ) -> Handle { let path = path.into(); let handle = if self.should_load_dependencies { self.asset_server.load_with_settings(path.clone(), settings) } else { self.asset_server .get_or_create_path_handle(path, Some(loader_settings_meta_transform(settings))) }; self.dependencies.insert(handle.id().untyped()); handle } /// Returns a handle to an asset of type `A` with the label `label`. This [`LoadContext`] must produce an asset of the /// given type and the given label or the dependencies of this asset will never be considered "fully loaded". However you /// can call this method before _or_ after adding the labeled asset. pub fn get_label_handle<'b, A: Asset>( &mut self, label: impl Into>, ) -> Handle { let path = self.asset_path.clone().with_label(label); let handle = self.asset_server.get_or_create_path_handle::(path, None); self.dependencies.insert(handle.id().untyped()); handle } /// Loads the asset at the given `path` directly. This is an async function that will wait until the asset is fully loaded before /// returning. Use this if you need the _value_ of another asset in order to load the current asset. For example, if you are /// deriving a new asset from the referenced asset, or you are building a collection of assets. This will add the `path` as a /// "load dependency". /// /// If the current loader is used in a [`Process`] "asset preprocessor", such as a [`LoadAndSave`] preprocessor, /// changing a "load dependency" will result in re-processing of the asset. /// /// [`Process`]: crate::processor::Process /// [`LoadAndSave`]: crate::processor::LoadAndSave pub async fn load_direct<'b>( &mut self, path: impl Into>, ) -> Result { let path = path.into().into_owned(); let to_error = |e: AssetLoadError| -> LoadDirectError { LoadDirectError { dependency: path.clone(), error: e, } }; let loaded_asset = { let (meta, loader, mut reader) = self .asset_server .get_meta_loader_and_reader(&path) .await .map_err(to_error)?; self.asset_server .load_with_meta_loader_and_reader( &path, meta, &*loader, &mut *reader, false, self.populate_hashes, ) .await .map_err(to_error)? }; let info = loaded_asset .meta .as_ref() .and_then(|m| m.processed_info().as_ref()); let hash = info.map(|i| i.full_hash).unwrap_or(Default::default()); self.loader_dependencies.insert(path, hash); Ok(loaded_asset) } } /// An error produced when calling [`LoadContext::read_asset_bytes`] #[derive(Error, Debug)] pub enum ReadAssetBytesError { #[error(transparent)] DeserializeMetaError(#[from] DeserializeMetaError), #[error(transparent)] AssetReaderError(#[from] AssetReaderError), #[error(transparent)] MissingAssetSourceError(#[from] MissingAssetSourceError), #[error(transparent)] MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError), /// Encountered an I/O error while loading an asset. #[error("Encountered an io error while loading asset: {0}")] Io(#[from] std::io::Error), #[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")] MissingAssetHash, }