mirror of
https://github.com/bevyengine/bevy
synced 2024-09-20 06:22:01 +00:00
Use Asset Path Extension for AssetLoader
Disambiguation (#11644)
# Objective - Fixes #11638 - See [here](https://github.com/bevyengine/bevy/issues/11638#issuecomment-1920508465) for details on the cause of this issue. ## Solution - Modified `AssetLoaders` to capture possibility of multiple `AssetLoader` registrations operating on the same `Asset` type, but different extensions. - Added an algorithm which will attempt to resolve via `AssetLoader` name, then `Asset` type, then by extension. If at any point multiple loaders fit a particular criteria, the next criteria is used as a tie breaker.
This commit is contained in:
parent
87add5660f
commit
7b5a4ec4ed
3 changed files with 885 additions and 168 deletions
|
@ -38,6 +38,7 @@ pub trait AssetLoader: Send + Sync + 'static {
|
|||
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>>;
|
||||
|
||||
/// 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] {
|
||||
&[]
|
||||
}
|
||||
|
|
807
crates/bevy_asset/src/server/loaders.rs
Normal file
807
crates/bevy_asset/src/server/loaders.rs
Normal file
|
@ -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<MaybeAssetLoader>,
|
||||
type_id_to_loaders: TypeIdMap<Vec<usize>>,
|
||||
extension_to_loaders: HashMap<String, Vec<usize>>,
|
||||
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<MaybeAssetLoader> {
|
||||
self.loaders.get(index).cloned()
|
||||
}
|
||||
|
||||
/// Registers a new [`AssetLoader`]. [`AssetLoader`]s must be registered before they can be used.
|
||||
pub(crate) fn push<L: AssetLoader>(&mut self, loader: L) {
|
||||
let type_name = std::any::type_name::<L>();
|
||||
let loader_asset_type = TypeId::of::<L::Asset>();
|
||||
let loader_asset_type_name = std::any::type_name::<L::Asset>();
|
||||
|
||||
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<L: AssetLoader>(&mut self, extensions: &[&str]) {
|
||||
let loader_asset_type = TypeId::of::<L::Asset>();
|
||||
let loader_asset_type_name = std::any::type_name::<L::Asset>();
|
||||
let type_name = std::any::type_name::<L>();
|
||||
|
||||
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<MaybeAssetLoader> {
|
||||
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<TypeId>,
|
||||
extension: Option<&str>,
|
||||
asset_path: Option<&AssetPath<'_>>,
|
||||
) -> Option<MaybeAssetLoader> {
|
||||
// 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<MaybeAssetLoader> {
|
||||
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<MaybeAssetLoader> {
|
||||
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<MaybeAssetLoader> {
|
||||
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<dyn ErasedAssetLoader>),
|
||||
Pending {
|
||||
sender: async_broadcast::Sender<Arc<dyn ErasedAssetLoader>>,
|
||||
receiver: async_broadcast::Receiver<Arc<dyn ErasedAssetLoader>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl MaybeAssetLoader {
|
||||
pub(crate) async fn get(self) -> Result<Arc<dyn ErasedAssetLoader>, 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<A: Asset, const N: usize, const E: usize> {
|
||||
sender: Sender<()>,
|
||||
_phantom: PhantomData<A>,
|
||||
}
|
||||
|
||||
impl<T: Asset, const N: usize, const E: usize> Loader<T, N, E> {
|
||||
fn new() -> (Self, Receiver<()>) {
|
||||
let (tx, rx) = channel();
|
||||
|
||||
let loader = Self {
|
||||
sender: tx,
|
||||
_phantom: PhantomData,
|
||||
};
|
||||
|
||||
(loader, rx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Asset, const N: usize, const E: usize> AssetLoader for Loader<T, N, E> {
|
||||
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::Asset, Self::Error>> {
|
||||
self.sender.send(()).unwrap();
|
||||
|
||||
Box::pin(async move {
|
||||
Err(format!(
|
||||
"Loaded {}:{}",
|
||||
std::any::type_name::<Self::Asset>(),
|
||||
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::<A, 1, 0>::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::<Loader<A, 1, 0>>())
|
||||
.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::<A, 1, 0>::new();
|
||||
let (loader_b1, rx_b1) = Loader::<B, 1, 0>::new();
|
||||
let (loader_c1, rx_c1) = Loader::<C, 1, 0>::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::<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_type(TypeId::of::<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_type(TypeId::of::<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 the last loader added is selected
|
||||
#[test]
|
||||
fn type_resolution_shadow() {
|
||||
let mut loaders = AssetLoaders::default();
|
||||
|
||||
let (loader_a1, rx_a1) = Loader::<A, 1, 0>::new();
|
||||
let (loader_a2, rx_a2) = Loader::<A, 2, 0>::new();
|
||||
let (loader_a3, rx_a3) = Loader::<A, 3, 0>::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::<A>()).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::<A, 1, 1>::new();
|
||||
let (loader_b1, rx_b1) = Loader::<A, 1, 2>::new();
|
||||
let (loader_c1, rx_c1) = Loader::<A, 1, 3>::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::<A, 1, 1>::new();
|
||||
let (loader_b1, rx_b1) = Loader::<A, 1, 2>::new();
|
||||
let (loader_c1, rx_c1) = Loader::<A, 1, 3>::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::<A, 1, 1>::new();
|
||||
|
||||
let (loader_b1_b, rx_b1_b) = Loader::<B, 1, 2>::new();
|
||||
|
||||
let (loader_c1_a, rx_c1_a) = Loader::<C, 1, 1>::new();
|
||||
let (loader_c1_b, rx_c1_b) = Loader::<C, 1, 2>::new();
|
||||
let (loader_c1_c, rx_c1_c) = Loader::<C, 1, 3>::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::<A>()),
|
||||
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::<B>()),
|
||||
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::<C>()),
|
||||
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::<C>()),
|
||||
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::<C>()),
|
||||
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::<A>()),
|
||||
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::<A>()),
|
||||
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::<A, 1, 1>::new();
|
||||
let (loader_a2_a, rx_a2_a) = Loader::<A, 2, 1>::new();
|
||||
let (loader_a3_a, rx_a3_a) = Loader::<A, 3, 1>::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::<A>()),
|
||||
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::<A>()),
|
||||
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::<A>()),
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -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<L: AssetLoader>(&self, loader: L) {
|
||||
let mut loaders = self.data.loaders.write();
|
||||
let type_name = std::any::type_name::<L>();
|
||||
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::<L::Asset>(), 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<Arc<dyn ErasedAssetLoader>, 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<Arc<dyn ErasedAssetLoader>, 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<AssetPath<'a>>,
|
||||
) -> Result<Arc<dyn ErasedAssetLoader>, 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<Arc<dyn ErasedAssetLoader>, 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<L: AssetLoader>(&self, extensions: &[&str]) {
|
||||
let mut loaders = self.data.loaders.write();
|
||||
let loader_index = TypeId::of::<L::Asset>();
|
||||
let type_name = std::any::type_name::<L>();
|
||||
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::<L>(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,50 +989,27 @@ impl AssetServer {
|
|||
Err(err) => Err(err.into()),
|
||||
}
|
||||
} else {
|
||||
let loader = self.resolve_loader(asset_path, asset_type_id).await?;
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<TypeId>,
|
||||
) -> Result<Arc<dyn ErasedAssetLoader>, 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 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;
|
||||
};
|
||||
|
||||
Some(loader)
|
||||
};
|
||||
|
||||
let loader = match loader {
|
||||
Some(loader) => loader,
|
||||
None => self.get_path_asset_loader(asset_path).await?,
|
||||
};
|
||||
|
||||
Ok(loader)
|
||||
}
|
||||
|
||||
pub(crate) async fn load_with_meta_loader_and_reader(
|
||||
&self,
|
||||
asset_path: &AssetPath<'_>,
|
||||
|
@ -1236,23 +1155,6 @@ pub fn handle_internal_asset_events(world: &mut World) {
|
|||
});
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct AssetLoaders {
|
||||
type_id_to_loader: TypeIdMap<MaybeAssetLoader>,
|
||||
extension_to_type_id: HashMap<String, TypeId>,
|
||||
type_name_to_type_id: HashMap<&'static str, TypeId>,
|
||||
preregistered_loaders: HashMap<&'static str, TypeId>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum MaybeAssetLoader {
|
||||
Ready(Arc<dyn ErasedAssetLoader>),
|
||||
Pending {
|
||||
sender: async_broadcast::Sender<Arc<dyn ErasedAssetLoader>>,
|
||||
receiver: async_broadcast::Receiver<Arc<dyn ErasedAssetLoader>>,
|
||||
},
|
||||
}
|
||||
|
||||
/// 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<String>,
|
||||
asset_type_id: Option<TypeId>,
|
||||
extension: Option<String>,
|
||||
asset_path: Option<String>,
|
||||
},
|
||||
#[error(transparent)]
|
||||
MissingAssetLoaderForExtension(#[from] MissingAssetLoaderForExtensionError),
|
||||
#[error(transparent)]
|
||||
|
|
Loading…
Reference in a new issue