diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index b209c6f1f7d4b..627b286482a5d 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -38,6 +38,7 @@ pub trait AssetLoader: Send + Sync + 'static { ) -> BoxedFuture<'a, Result>; /// Returns a list of extensions supported by this [`AssetLoader`], without the preceding dot. + /// Note that users of this [`AssetLoader`] may choose to load files with a non-matching extension. fn extensions(&self) -> &[&str] { &[] } diff --git a/crates/bevy_asset/src/server/loaders.rs b/crates/bevy_asset/src/server/loaders.rs new file mode 100644 index 0000000000000..47dd54f9809a3 --- /dev/null +++ b/crates/bevy_asset/src/server/loaders.rs @@ -0,0 +1,807 @@ +use crate::{ + loader::{AssetLoader, ErasedAssetLoader}, + path::AssetPath, +}; +use async_broadcast::RecvError; +use bevy_log::{error, info, warn}; +use bevy_tasks::IoTaskPool; +use bevy_utils::{HashMap, TypeIdMap}; +use std::{any::TypeId, sync::Arc}; +use thiserror::Error; + +#[derive(Default)] +pub(crate) struct AssetLoaders { + loaders: Vec, + type_id_to_loaders: TypeIdMap>, + extension_to_loaders: HashMap>, + type_name_to_loader: HashMap<&'static str, usize>, + preregistered_loaders: HashMap<&'static str, usize>, +} + +impl AssetLoaders { + /// Get the [`AssetLoader`] stored at the specific index + fn get_by_index(&self, index: usize) -> Option { + self.loaders.get(index).cloned() + } + + /// Registers a new [`AssetLoader`]. [`AssetLoader`]s must be registered before they can be used. + pub(crate) fn push(&mut self, loader: L) { + let type_name = std::any::type_name::(); + let loader_asset_type = TypeId::of::(); + let loader_asset_type_name = std::any::type_name::(); + + let loader = Arc::new(loader); + + let (loader_index, is_new) = + if let Some(index) = self.preregistered_loaders.remove(type_name) { + (index, false) + } else { + (self.loaders.len(), true) + }; + + if is_new { + for extension in loader.extensions() { + let list = self + .extension_to_loaders + .entry(extension.to_string()) + .or_default(); + + if !list.is_empty() { + warn!("duplicate registration for extension `{extension}`."); + } + + list.push(loader_index); + } + + self.type_name_to_loader.insert(type_name, loader_index); + + let list = self + .type_id_to_loaders + .entry(loader_asset_type) + .or_default(); + + if !list.is_empty() { + info!("duplicate registration for type `{loader_asset_type_name}`."); + } + + list.push(loader_index); + + self.loaders.push(MaybeAssetLoader::Ready(loader)); + } else { + let maybe_loader = std::mem::replace( + self.loaders.get_mut(loader_index).unwrap(), + MaybeAssetLoader::Ready(loader.clone()), + ); + match maybe_loader { + MaybeAssetLoader::Ready(_) => unreachable!(), + MaybeAssetLoader::Pending { sender, .. } => { + IoTaskPool::get() + .spawn(async move { + let _ = sender.broadcast(loader).await; + }) + .detach(); + } + } + } + } + + /// Pre-register an [`AssetLoader`] that will later be added. + /// + /// Assets loaded with matching extensions will be blocked until the + /// real loader is added. + pub(crate) fn reserve(&mut self, extensions: &[&str]) { + let loader_asset_type = TypeId::of::(); + let loader_asset_type_name = std::any::type_name::(); + let type_name = std::any::type_name::(); + + let loader_index = self.loaders.len(); + + self.preregistered_loaders.insert(type_name, loader_index); + self.type_name_to_loader.insert(type_name, loader_index); + for extension in extensions { + let list = self + .extension_to_loaders + .entry(extension.to_string()) + .or_default(); + + if !list.is_empty() { + warn!("duplicate preregistration for extension `{extension}`."); + } + + list.push(loader_index); + } + + let list = self + .type_id_to_loaders + .entry(loader_asset_type) + .or_default(); + + if !list.is_empty() { + info!("duplicate preregistration for type `{loader_asset_type_name}`."); + } + + list.push(loader_index); + + let (mut sender, receiver) = async_broadcast::broadcast(1); + sender.set_overflow(true); + self.loaders + .push(MaybeAssetLoader::Pending { sender, receiver }); + } + + /// Get the [`AssetLoader`] by name + pub(crate) fn get_by_name(&self, name: &str) -> Option { + let index = self.type_name_to_loader.get(name).copied()?; + + self.get_by_index(index) + } + + /// Find an [`AssetLoader`] based on provided search criteria + pub(crate) fn find( + &self, + type_name: Option<&str>, + asset_type_id: Option, + extension: Option<&str>, + asset_path: Option<&AssetPath<'_>>, + ) -> Option { + // If provided the type name of the loader, return that immediately + if let Some(type_name) = type_name { + return self.get_by_name(type_name); + } + + // The presence of a label will affect loader choice + let label = asset_path.as_ref().and_then(|path| path.label()); + + // Try by asset type + let candidates = if let Some(type_id) = asset_type_id { + if label.is_none() { + Some(self.type_id_to_loaders.get(&type_id)?) + } else { + None + } + } else { + None + }; + + if let Some(candidates) = candidates { + if candidates.is_empty() { + return None; + } else if candidates.len() == 1 { + let index = candidates.first().copied().unwrap(); + return self.get_by_index(index); + } + } + + // Asset type is insufficient, use extension information + let try_extension = |extension| { + if let Some(indices) = self.extension_to_loaders.get(extension) { + if let Some(candidates) = candidates { + if candidates.is_empty() { + indices.last() + } else { + indices + .iter() + .rev() + .find(|index| candidates.contains(index)) + } + } else { + indices.last() + } + } else { + None + } + }; + + // Try the provided extension + if let Some(extension) = extension { + if let Some(&index) = try_extension(extension) { + return self.get_by_index(index); + } + } + + // Try extracting the extension from the path + if let Some(full_extension) = asset_path.and_then(|path| path.get_full_extension()) { + if let Some(&index) = try_extension(full_extension.as_str()) { + return self.get_by_index(index); + } + + // Try secondary extensions from the path + for extension in AssetPath::iter_secondary_extensions(&full_extension) { + if let Some(&index) = try_extension(extension) { + return self.get_by_index(index); + } + } + } + + // Fallback if no resolution step was conclusive + match candidates? + .last() + .copied() + .and_then(|index| self.get_by_index(index)) + { + Some(loader) => { + warn!( + "Multiple AssetLoaders found for Asset: {:?}; Path: {:?}; Extension: {:?}", + asset_type_id, asset_path, extension + ); + Some(loader) + } + None => { + warn!( + "No AssetLoader found for Asset: {:?}; Path: {:?}; Extension: {:?}", + asset_type_id, asset_path, extension + ); + None + } + } + } + + /// Get the [`AssetLoader`] for a given asset type + pub(crate) fn get_by_type(&self, type_id: TypeId) -> Option { + let index = self.type_id_to_loaders.get(&type_id)?.last().copied()?; + + self.get_by_index(index) + } + + /// Get the [`AssetLoader`] for a given extension + pub(crate) fn get_by_extension(&self, extension: &str) -> Option { + let index = self.extension_to_loaders.get(extension)?.last().copied()?; + + self.get_by_index(index) + } + + /// Get the [`AssetLoader`] for a given path + pub(crate) fn get_by_path(&self, path: &AssetPath<'_>) -> Option { + let extension = path.get_full_extension()?; + + let result = std::iter::once(extension.as_str()) + .chain(AssetPath::iter_secondary_extensions(&extension)) + .filter_map(|extension| self.extension_to_loaders.get(extension)?.last().copied()) + .find_map(|index| self.get_by_index(index))?; + + Some(result) + } +} + +#[derive(Error, Debug, Clone)] +pub(crate) enum GetLoaderError { + #[error(transparent)] + CouldNotResolve(#[from] RecvError), +} + +#[derive(Clone)] +pub(crate) enum MaybeAssetLoader { + Ready(Arc), + Pending { + sender: async_broadcast::Sender>, + receiver: async_broadcast::Receiver>, + }, +} + +impl MaybeAssetLoader { + pub(crate) async fn get(self) -> Result, GetLoaderError> { + match self { + MaybeAssetLoader::Ready(loader) => Ok(loader), + MaybeAssetLoader::Pending { mut receiver, .. } => Ok(receiver.recv().await?), + } + } +} + +#[cfg(test)] +mod tests { + use std::{ + marker::PhantomData, + path::Path, + sync::mpsc::{channel, Receiver, Sender}, + }; + + use bevy_reflect::TypePath; + use bevy_tasks::block_on; + + use crate::{self as bevy_asset, Asset}; + + use super::*; + + #[derive(Asset, TypePath, Debug)] + struct A(usize); + + #[derive(Asset, TypePath, Debug)] + struct B(usize); + + #[derive(Asset, TypePath, Debug)] + struct C(usize); + + struct Loader { + sender: Sender<()>, + _phantom: PhantomData, + } + + impl Loader { + fn new() -> (Self, Receiver<()>) { + let (tx, rx) = channel(); + + let loader = Self { + sender: tx, + _phantom: PhantomData, + }; + + (loader, rx) + } + } + + impl AssetLoader for Loader { + type Asset = T; + + type Settings = (); + + type Error = String; + + fn load<'a>( + &'a self, + _: &'a mut crate::io::Reader, + _: &'a Self::Settings, + _: &'a mut crate::LoadContext, + ) -> bevy_utils::BoxedFuture<'a, Result> { + self.sender.send(()).unwrap(); + + Box::pin(async move { + Err(format!( + "Loaded {}:{}", + std::any::type_name::(), + N + )) + }) + } + + fn extensions(&self) -> &[&str] { + self.sender.send(()).unwrap(); + + match E { + 1 => &["a"], + 2 => &["b"], + 3 => &["c"], + 4 => &["d"], + _ => &[], + } + } + } + + /// Basic framework for creating, storing, loading, and checking an [`AssetLoader`] inside an [`AssetLoaders`] + #[test] + fn basic() { + let mut loaders = AssetLoaders::default(); + + let (loader, rx) = Loader::::new(); + + assert!(rx.try_recv().is_err()); + + loaders.push(loader); + + assert!(rx.try_recv().is_ok()); + assert!(rx.try_recv().is_err()); + + let loader = block_on( + loaders + .get_by_name(std::any::type_name::>()) + .unwrap() + .get(), + ) + .unwrap(); + + loader.extensions(); + + assert!(rx.try_recv().is_ok()); + assert!(rx.try_recv().is_err()); + } + + /// Ensure that if multiple loaders have different types but no extensions, they can be found + #[test] + fn type_resolution() { + let mut loaders = AssetLoaders::default(); + + let (loader_a1, rx_a1) = Loader::::new(); + let (loader_b1, rx_b1) = Loader::::new(); + let (loader_c1, rx_c1) = Loader::::new(); + + loaders.push(loader_a1); + loaders.push(loader_b1); + loaders.push(loader_c1); + + assert!(rx_a1.try_recv().is_ok()); + assert!(rx_b1.try_recv().is_ok()); + assert!(rx_c1.try_recv().is_ok()); + + let loader = block_on(loaders.get_by_type(TypeId::of::()).unwrap().get()).unwrap(); + + loader.extensions(); + + assert!(rx_a1.try_recv().is_ok()); + assert!(rx_b1.try_recv().is_err()); + assert!(rx_c1.try_recv().is_err()); + + let loader = block_on(loaders.get_by_type(TypeId::of::()).unwrap().get()).unwrap(); + + loader.extensions(); + + assert!(rx_a1.try_recv().is_err()); + assert!(rx_b1.try_recv().is_ok()); + assert!(rx_c1.try_recv().is_err()); + + let loader = block_on(loaders.get_by_type(TypeId::of::()).unwrap().get()).unwrap(); + + loader.extensions(); + + assert!(rx_a1.try_recv().is_err()); + assert!(rx_b1.try_recv().is_err()); + assert!(rx_c1.try_recv().is_ok()); + } + + /// Ensure that the last loader added is selected + #[test] + fn type_resolution_shadow() { + let mut loaders = AssetLoaders::default(); + + let (loader_a1, rx_a1) = Loader::::new(); + let (loader_a2, rx_a2) = Loader::::new(); + let (loader_a3, rx_a3) = Loader::::new(); + + loaders.push(loader_a1); + loaders.push(loader_a2); + loaders.push(loader_a3); + + assert!(rx_a1.try_recv().is_ok()); + assert!(rx_a2.try_recv().is_ok()); + assert!(rx_a3.try_recv().is_ok()); + + let loader = block_on(loaders.get_by_type(TypeId::of::()).unwrap().get()).unwrap(); + + loader.extensions(); + + assert!(rx_a1.try_recv().is_err()); + assert!(rx_a2.try_recv().is_err()); + assert!(rx_a3.try_recv().is_ok()); + } + + /// Ensure that if multiple loaders have like types but differing extensions, they can be found + #[test] + fn extension_resolution() { + let mut loaders = AssetLoaders::default(); + + let (loader_a1, rx_a1) = Loader::::new(); + let (loader_b1, rx_b1) = Loader::::new(); + let (loader_c1, rx_c1) = Loader::::new(); + + loaders.push(loader_a1); + loaders.push(loader_b1); + loaders.push(loader_c1); + + assert!(rx_a1.try_recv().is_ok()); + assert!(rx_b1.try_recv().is_ok()); + assert!(rx_c1.try_recv().is_ok()); + + let loader = block_on(loaders.get_by_extension("a").unwrap().get()).unwrap(); + + loader.extensions(); + + assert!(rx_a1.try_recv().is_ok()); + assert!(rx_b1.try_recv().is_err()); + assert!(rx_c1.try_recv().is_err()); + + let loader = block_on(loaders.get_by_extension("b").unwrap().get()).unwrap(); + + loader.extensions(); + + assert!(rx_a1.try_recv().is_err()); + assert!(rx_b1.try_recv().is_ok()); + assert!(rx_c1.try_recv().is_err()); + + let loader = block_on(loaders.get_by_extension("c").unwrap().get()).unwrap(); + + loader.extensions(); + + assert!(rx_a1.try_recv().is_err()); + assert!(rx_b1.try_recv().is_err()); + assert!(rx_c1.try_recv().is_ok()); + } + + /// Ensure that if multiple loaders have like types but differing extensions, they can be found + #[test] + fn path_resolution() { + let mut loaders = AssetLoaders::default(); + + let (loader_a1, rx_a1) = Loader::::new(); + let (loader_b1, rx_b1) = Loader::::new(); + let (loader_c1, rx_c1) = Loader::::new(); + + loaders.push(loader_a1); + loaders.push(loader_b1); + loaders.push(loader_c1); + + assert!(rx_a1.try_recv().is_ok()); + assert!(rx_b1.try_recv().is_ok()); + assert!(rx_c1.try_recv().is_ok()); + + let path = AssetPath::from_path(Path::new("asset.a")); + + let loader = block_on(loaders.get_by_path(&path).unwrap().get()).unwrap(); + + loader.extensions(); + + assert!(rx_a1.try_recv().is_ok()); + assert!(rx_b1.try_recv().is_err()); + assert!(rx_c1.try_recv().is_err()); + + let path = AssetPath::from_path(Path::new("asset.b")); + + let loader = block_on(loaders.get_by_path(&path).unwrap().get()).unwrap(); + + loader.extensions(); + + assert!(rx_a1.try_recv().is_err()); + assert!(rx_b1.try_recv().is_ok()); + assert!(rx_c1.try_recv().is_err()); + + let path = AssetPath::from_path(Path::new("asset.c")); + + let loader = block_on(loaders.get_by_path(&path).unwrap().get()).unwrap(); + + loader.extensions(); + + assert!(rx_a1.try_recv().is_err()); + assert!(rx_b1.try_recv().is_err()); + assert!(rx_c1.try_recv().is_ok()); + } + + /// Full resolution algorithm + #[test] + fn total_resolution() { + let mut loaders = AssetLoaders::default(); + + let (loader_a1_a, rx_a1_a) = Loader::::new(); + + let (loader_b1_b, rx_b1_b) = Loader::::new(); + + let (loader_c1_a, rx_c1_a) = Loader::::new(); + let (loader_c1_b, rx_c1_b) = Loader::::new(); + let (loader_c1_c, rx_c1_c) = Loader::::new(); + + loaders.push(loader_a1_a); + loaders.push(loader_b1_b); + loaders.push(loader_c1_a); + loaders.push(loader_c1_b); + loaders.push(loader_c1_c); + + assert!(rx_a1_a.try_recv().is_ok()); + assert!(rx_b1_b.try_recv().is_ok()); + assert!(rx_c1_a.try_recv().is_ok()); + assert!(rx_c1_b.try_recv().is_ok()); + assert!(rx_c1_c.try_recv().is_ok()); + + // Type and Extension agree + + let loader = block_on( + loaders + .find( + None, + Some(TypeId::of::()), + None, + Some(&AssetPath::from_path(Path::new("asset.a"))), + ) + .unwrap() + .get(), + ) + .unwrap(); + + loader.extensions(); + + assert!(rx_a1_a.try_recv().is_ok()); + assert!(rx_b1_b.try_recv().is_err()); + assert!(rx_c1_a.try_recv().is_err()); + assert!(rx_c1_b.try_recv().is_err()); + assert!(rx_c1_c.try_recv().is_err()); + + let loader = block_on( + loaders + .find( + None, + Some(TypeId::of::()), + None, + Some(&AssetPath::from_path(Path::new("asset.b"))), + ) + .unwrap() + .get(), + ) + .unwrap(); + + loader.extensions(); + + assert!(rx_a1_a.try_recv().is_err()); + assert!(rx_b1_b.try_recv().is_ok()); + assert!(rx_c1_a.try_recv().is_err()); + assert!(rx_c1_b.try_recv().is_err()); + assert!(rx_c1_c.try_recv().is_err()); + + let loader = block_on( + loaders + .find( + None, + Some(TypeId::of::()), + None, + Some(&AssetPath::from_path(Path::new("asset.c"))), + ) + .unwrap() + .get(), + ) + .unwrap(); + + loader.extensions(); + + assert!(rx_a1_a.try_recv().is_err()); + assert!(rx_b1_b.try_recv().is_err()); + assert!(rx_c1_a.try_recv().is_err()); + assert!(rx_c1_b.try_recv().is_err()); + assert!(rx_c1_c.try_recv().is_ok()); + + // Type should override Extension + + let loader = block_on( + loaders + .find( + None, + Some(TypeId::of::()), + None, + Some(&AssetPath::from_path(Path::new("asset.a"))), + ) + .unwrap() + .get(), + ) + .unwrap(); + + loader.extensions(); + + assert!(rx_a1_a.try_recv().is_err()); + assert!(rx_b1_b.try_recv().is_err()); + assert!(rx_c1_a.try_recv().is_ok()); + assert!(rx_c1_b.try_recv().is_err()); + assert!(rx_c1_c.try_recv().is_err()); + + let loader = block_on( + loaders + .find( + None, + Some(TypeId::of::()), + None, + Some(&AssetPath::from_path(Path::new("asset.b"))), + ) + .unwrap() + .get(), + ) + .unwrap(); + + loader.extensions(); + + assert!(rx_a1_a.try_recv().is_err()); + assert!(rx_b1_b.try_recv().is_err()); + assert!(rx_c1_a.try_recv().is_err()); + assert!(rx_c1_b.try_recv().is_ok()); + assert!(rx_c1_c.try_recv().is_err()); + + // Type should override bad / missing extension + + let loader = block_on( + loaders + .find( + None, + Some(TypeId::of::()), + None, + Some(&AssetPath::from_path(Path::new("asset.x"))), + ) + .unwrap() + .get(), + ) + .unwrap(); + + loader.extensions(); + + assert!(rx_a1_a.try_recv().is_ok()); + assert!(rx_b1_b.try_recv().is_err()); + assert!(rx_c1_a.try_recv().is_err()); + assert!(rx_c1_b.try_recv().is_err()); + assert!(rx_c1_c.try_recv().is_err()); + + let loader = block_on( + loaders + .find( + None, + Some(TypeId::of::()), + None, + Some(&AssetPath::from_path(Path::new("asset"))), + ) + .unwrap() + .get(), + ) + .unwrap(); + + loader.extensions(); + + assert!(rx_a1_a.try_recv().is_ok()); + assert!(rx_b1_b.try_recv().is_err()); + assert!(rx_c1_a.try_recv().is_err()); + assert!(rx_c1_b.try_recv().is_err()); + assert!(rx_c1_c.try_recv().is_err()); + } + + /// Ensure that if there is a complete ambiguity in [`AssetLoader`] to use, prefer most recently registered by asset type. + #[test] + fn ambiguity_resolution() { + let mut loaders = AssetLoaders::default(); + + let (loader_a1_a, rx_a1_a) = Loader::::new(); + let (loader_a2_a, rx_a2_a) = Loader::::new(); + let (loader_a3_a, rx_a3_a) = Loader::::new(); + + loaders.push(loader_a1_a); + loaders.push(loader_a2_a); + loaders.push(loader_a3_a); + + assert!(rx_a1_a.try_recv().is_ok()); + assert!(rx_a2_a.try_recv().is_ok()); + assert!(rx_a3_a.try_recv().is_ok()); + + let loader = block_on( + loaders + .find( + None, + Some(TypeId::of::()), + None, + Some(&AssetPath::from_path(Path::new("asset.a"))), + ) + .unwrap() + .get(), + ) + .unwrap(); + + loader.extensions(); + + assert!(rx_a1_a.try_recv().is_err()); + assert!(rx_a2_a.try_recv().is_err()); + assert!(rx_a3_a.try_recv().is_ok()); + + let loader = block_on( + loaders + .find( + None, + Some(TypeId::of::()), + None, + Some(&AssetPath::from_path(Path::new("asset.x"))), + ) + .unwrap() + .get(), + ) + .unwrap(); + + loader.extensions(); + + assert!(rx_a1_a.try_recv().is_err()); + assert!(rx_a2_a.try_recv().is_err()); + assert!(rx_a3_a.try_recv().is_ok()); + + let loader = block_on( + loaders + .find( + None, + Some(TypeId::of::()), + None, + Some(&AssetPath::from_path(Path::new("asset"))), + ) + .unwrap() + .get(), + ) + .unwrap(); + + loader.extensions(); + + assert!(rx_a1_a.try_recv().is_err()); + assert!(rx_a2_a.try_recv().is_err()); + assert!(rx_a3_a.try_recv().is_ok()); + } +} diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index 298c209a98a4b..5fda4c55e5f3d 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -1,4 +1,5 @@ mod info; +mod loaders; use crate::{ folder::LoadedFolder, @@ -17,12 +18,13 @@ use crate::{ UntypedAssetLoadFailedEvent, UntypedHandle, }; use bevy_ecs::prelude::*; -use bevy_log::{error, info, warn}; +use bevy_log::{error, info}; use bevy_tasks::IoTaskPool; -use bevy_utils::{CowArc, HashMap, HashSet, TypeIdMap}; +use bevy_utils::{CowArc, HashSet}; use crossbeam_channel::{Receiver, Sender}; use futures_lite::StreamExt; use info::*; +use loaders::*; use parking_lot::RwLock; use std::path::PathBuf; use std::{any::TypeId, path::Path, sync::Arc}; @@ -135,42 +137,7 @@ impl AssetServer { /// Registers a new [`AssetLoader`]. [`AssetLoader`]s must be registered before they can be used. pub fn register_loader(&self, loader: L) { - let mut loaders = self.data.loaders.write(); - let type_name = std::any::type_name::(); - let loader = Arc::new(loader); - let (loader_index, is_new) = - if let Some(index) = loaders.preregistered_loaders.remove(type_name) { - (index, false) - } else { - (TypeId::of::(), true) - }; - for extension in loader.extensions() { - loaders - .extension_to_type_id - .insert(extension.to_string(), loader_index); - } - - if is_new { - loaders.type_name_to_type_id.insert(type_name, loader_index); - loaders - .type_id_to_loader - .insert(loader_index, MaybeAssetLoader::Ready(loader)); - } else { - let maybe_loader = std::mem::replace( - loaders.type_id_to_loader.get_mut(&loader_index).unwrap(), - MaybeAssetLoader::Ready(loader.clone()), - ); - match maybe_loader { - MaybeAssetLoader::Ready(_) => unreachable!(), - MaybeAssetLoader::Pending { sender, .. } => { - IoTaskPool::get() - .spawn(async move { - let _ = sender.broadcast(loader).await; - }) - .detach(); - } - } - } + self.data.loaders.write().push(loader); } /// Registers a new [`Asset`] type. [`Asset`] types must be registered before assets of that type can be loaded. @@ -219,20 +186,13 @@ impl AssetServer { &self, extension: &str, ) -> Result, MissingAssetLoaderForExtensionError> { - let loader = { - let loaders = self.data.loaders.read(); - let index = *loaders.extension_to_type_id.get(extension).ok_or_else(|| { - MissingAssetLoaderForExtensionError { - extensions: vec![extension.to_string()], - } - })?; - loaders.type_id_to_loader[&index].clone() + let error = || MissingAssetLoaderForExtensionError { + extensions: vec![extension.to_string()], }; - match loader { - MaybeAssetLoader::Ready(loader) => Ok(loader), - MaybeAssetLoader::Pending { mut receiver, .. } => Ok(receiver.recv().await.unwrap()), - } + let loader = { self.data.loaders.read().get_by_extension(extension) }; + + loader.ok_or_else(error)?.get().await.map_err(|_| error()) } /// Returns the registered [`AssetLoader`] associated with the given [`std::any::type_name`], if it exists. @@ -240,20 +200,13 @@ impl AssetServer { &self, type_name: &str, ) -> Result, MissingAssetLoaderForTypeNameError> { - let loader = { - let loaders = self.data.loaders.read(); - let index = *loaders.type_name_to_type_id.get(type_name).ok_or_else(|| { - MissingAssetLoaderForTypeNameError { - type_name: type_name.to_string(), - } - })?; - - loaders.type_id_to_loader[&index].clone() + let error = || MissingAssetLoaderForTypeNameError { + type_name: type_name.to_string(), }; - match loader { - MaybeAssetLoader::Ready(loader) => Ok(loader), - MaybeAssetLoader::Pending { mut receiver, .. } => Ok(receiver.recv().await.unwrap()), - } + + let loader = { self.data.loaders.read().get_by_name(type_name) }; + + loader.ok_or_else(error)?.get().await.map_err(|_| error()) } /// Retrieves the default [`AssetLoader`] for the given path, if one can be found. @@ -262,23 +215,25 @@ impl AssetServer { path: impl Into>, ) -> Result, MissingAssetLoaderForExtensionError> { let path = path.into(); - let full_extension = - path.get_full_extension() - .ok_or(MissingAssetLoaderForExtensionError { + + let error = || { + let Some(full_extension) = path.get_full_extension() else { + return MissingAssetLoaderForExtensionError { extensions: Vec::new(), - })?; - if let Ok(loader) = self.get_asset_loader_with_extension(&full_extension).await { - return Ok(loader); - } - for extension in AssetPath::iter_secondary_extensions(&full_extension) { - if let Ok(loader) = self.get_asset_loader_with_extension(extension).await { - return Ok(loader); - } - } - let mut extensions = vec![full_extension.clone()]; - extensions - .extend(AssetPath::iter_secondary_extensions(&full_extension).map(|e| e.to_string())); - Err(MissingAssetLoaderForExtensionError { extensions }) + }; + }; + + let mut extensions = vec![full_extension.clone()]; + extensions.extend( + AssetPath::iter_secondary_extensions(&full_extension).map(|e| e.to_string()), + ); + + MissingAssetLoaderForExtensionError { extensions } + }; + + let loader = { self.data.loaders.read().get_by_path(&path) }; + + loader.ok_or_else(error)?.get().await.map_err(|_| error()) } /// Retrieves the default [`AssetLoader`] for the given [`Asset`] [`TypeId`], if one can be found. @@ -286,19 +241,11 @@ impl AssetServer { &self, type_id: TypeId, ) -> Result, MissingAssetLoaderForTypeIdError> { - let loader = { - let loaders = self.data.loaders.read(); - loaders - .type_id_to_loader - .get(&type_id) - .ok_or(MissingAssetLoaderForTypeIdError { type_id })? - .clone() - }; + let error = || MissingAssetLoaderForTypeIdError { type_id }; - match loader { - MaybeAssetLoader::Ready(loader) => Ok(loader), - MaybeAssetLoader::Pending { mut receiver, .. } => Ok(receiver.recv().await.unwrap()), - } + let loader = { self.data.loaders.read().get_by_type(type_id) }; + + loader.ok_or_else(error)?.get().await.map_err(|_| error()) } /// Retrieves the default [`AssetLoader`] for the given [`Asset`] type, if one can be found. @@ -937,27 +884,7 @@ impl AssetServer { /// Assets loaded with matching extensions will be blocked until the /// real loader is added. pub fn preregister_loader(&self, extensions: &[&str]) { - let mut loaders = self.data.loaders.write(); - let loader_index = TypeId::of::(); - let type_name = std::any::type_name::(); - loaders - .preregistered_loaders - .insert(type_name, loader_index); - loaders.type_name_to_type_id.insert(type_name, loader_index); - for extension in extensions { - if loaders - .extension_to_type_id - .insert(extension.to_string(), loader_index) - .is_some() - { - warn!("duplicate preregistration for `{extension}`, any assets loaded with the previous loader will never complete."); - } - } - let (mut sender, receiver) = async_broadcast::broadcast(1); - sender.set_overflow(true); - loaders - .type_id_to_loader - .insert(loader_index, MaybeAssetLoader::Pending { sender, receiver }); + self.data.loaders.write().reserve::(extensions); } /// Retrieve a handle for the given path. This will create a handle (and [`AssetInfo`]) if it does not exist @@ -1039,7 +966,22 @@ impl AssetServer { Ok((meta, loader, reader)) } Err(AssetReaderError::NotFound(_)) => { - let loader = self.resolve_loader(asset_path, asset_type_id).await?; + // TODO: Handle error transformation + let loader = { + self.data + .loaders + .read() + .find(None, asset_type_id, None, Some(asset_path)) + }; + + let error = || AssetLoadError::MissingAssetLoader { + loader_name: None, + asset_type_id, + extension: None, + asset_path: Some(asset_path.to_string()), + }; + + let loader = loader.ok_or_else(error)?.get().await.map_err(|_| error())?; let meta = loader.default_meta(); Ok((meta, loader, reader)) @@ -1047,48 +989,25 @@ impl AssetServer { Err(err) => Err(err.into()), } } else { - let loader = self.resolve_loader(asset_path, asset_type_id).await?; - - let meta = loader.default_meta(); - Ok((meta, loader, reader)) - } - } - - /// Selects an [`AssetLoader`] for the provided path and (optional) [`Asset`] [`TypeId`]. - /// Prefers [`TypeId`], and falls back to reading the file extension in the provided [`AssetPath`] otherwise. - async fn resolve_loader<'a>( - &'a self, - asset_path: &'a AssetPath<'_>, - asset_type_id: Option, - ) -> Result, MissingAssetLoaderForExtensionError> { - let loader = 'type_resolution: { - let Some(type_id) = asset_type_id else { - // If not provided an asset_type_id, type inference is broken - break 'type_resolution None; - }; - - let None = asset_path.label() else { - // Labelled sub-assets could be any type, not just the one registered for the loader - break 'type_resolution None; + let loader = { + self.data + .loaders + .read() + .find(None, asset_type_id, None, Some(asset_path)) }; - let Ok(loader) = self.get_asset_loader_with_asset_type_id(type_id).await else { - bevy_log::warn!( - "Could not load asset via type_id: no asset loader registered for {:?}", - type_id - ); - break 'type_resolution None; + let error = || AssetLoadError::MissingAssetLoader { + loader_name: None, + asset_type_id, + extension: None, + asset_path: Some(asset_path.to_string()), }; - Some(loader) - }; - - let loader = match loader { - Some(loader) => loader, - None => self.get_path_asset_loader(asset_path).await?, - }; + let loader = loader.ok_or_else(error)?.get().await.map_err(|_| error())?; - Ok(loader) + let meta = loader.default_meta(); + Ok((meta, loader, reader)) + } } pub(crate) async fn load_with_meta_loader_and_reader( @@ -1236,23 +1155,6 @@ pub fn handle_internal_asset_events(world: &mut World) { }); } -#[derive(Default)] -pub(crate) struct AssetLoaders { - type_id_to_loader: TypeIdMap, - extension_to_type_id: HashMap, - type_name_to_type_id: HashMap<&'static str, TypeId>, - preregistered_loaders: HashMap<&'static str, TypeId>, -} - -#[derive(Clone)] -enum MaybeAssetLoader { - Ready(Arc), - Pending { - sender: async_broadcast::Sender>, - receiver: async_broadcast::Receiver>, - }, -} - /// Internal events for asset load results #[allow(clippy::large_enum_variant)] pub(crate) enum InternalAssetEvent { @@ -1319,6 +1221,13 @@ pub enum AssetLoadError { actual_asset_name: &'static str, loader_name: &'static str, }, + #[error("Could not find an asset loader matching: Loader Name: {loader_name:?}; Asset Type: {loader_name:?}; Extension: {extension:?}; Path: {asset_path:?};")] + MissingAssetLoader { + loader_name: Option, + asset_type_id: Option, + extension: Option, + asset_path: Option, + }, #[error(transparent)] MissingAssetLoaderForExtension(#[from] MissingAssetLoaderForExtensionError), #[error(transparent)]