Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/bevy_animation/src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use bevy_ecs::{
system::{Res, ResMut},
};
use bevy_platform::collections::HashMap;
use bevy_reflect::{prelude::ReflectDefault, Reflect};
use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath};
use derive_more::derive::From;
use petgraph::{
graph::{DiGraph, NodeIndex},
Expand Down Expand Up @@ -238,7 +238,7 @@ pub enum AnimationNodeType {
///
/// The canonical extension for [`AnimationGraph`]s is `.animgraph.ron`. Plain
/// `.animgraph` is supported as well.
#[derive(Default)]
#[derive(Default, TypePath)]
pub struct AnimationGraphAssetLoader;

/// Errors that can occur when serializing animation graphs to RON.
Expand Down
7 changes: 6 additions & 1 deletion crates/bevy_asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ mod tests {
pub sub_texts: Vec<String>,
}

#[derive(Default)]
#[derive(Default, TypePath)]
pub struct CoolTextLoader;

#[derive(Error, Debug)]
Expand Down Expand Up @@ -1932,6 +1932,7 @@ mod tests {
.init_asset::<SubText>()
.register_asset_loader(CoolTextLoader);

#[derive(TypePath)]
struct NestedLoadOfSubassetLoader;

impl AssetLoader for NestedLoadOfSubassetLoader {
Expand Down Expand Up @@ -2152,6 +2153,7 @@ mod tests {
// Note: we can't just use the GatedReader, since currently we hold the handle until after
// we've selected the reader. The GatedReader blocks this process, so we need to wait until
// we gate in the loader instead.
#[derive(TypePath)]
struct GatedLoader {
in_loader_sender: async_channel::Sender<()>,
gate_receiver: async_channel::Receiver<()>,
Expand Down Expand Up @@ -2469,6 +2471,7 @@ mod tests {
#[derive(Serialize, Deserialize, Default)]
struct U8LoaderSettings(u8);

#[derive(TypePath)]
struct U8Loader;

impl AssetLoader for U8Loader {
Expand Down Expand Up @@ -2566,6 +2569,7 @@ mod tests {
.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default()))
.init_asset::<TestAsset>();

#[derive(TypePath)]
struct TwoSubassetLoader;

impl AssetLoader for TwoSubassetLoader {
Expand Down Expand Up @@ -2618,6 +2622,7 @@ mod tests {
.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default()))
.init_asset::<TestAsset>();

#[derive(TypePath)]
struct TrivialLoader;

impl AssetLoader for TrivialLoader {
Expand Down
13 changes: 7 additions & 6 deletions crates/bevy_asset/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use alloc::{
use atomicow::CowArc;
use bevy_ecs::{error::BevyError, world::World};
use bevy_platform::collections::{HashMap, HashSet};
use bevy_reflect::TypePath;
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
use core::any::{Any, TypeId};
use downcast_rs::{impl_downcast, Downcast};
Expand All @@ -28,7 +29,7 @@ use thiserror::Error;
/// This trait is generally used in concert with [`AssetReader`](crate::io::AssetReader) to load assets from a byte source.
///
/// For a complementary version of this trait that can save assets, see [`AssetSaver`](crate::saver::AssetSaver).
pub trait AssetLoader: Send + Sync + 'static {
pub trait AssetLoader: TypePath + Send + Sync + 'static {
/// The top level [`Asset`] loaded by this [`AssetLoader`].
type Asset: Asset;
/// The settings type used by this [`AssetLoader`].
Expand Down Expand Up @@ -66,8 +67,8 @@ pub trait ErasedAssetLoader: Send + Sync + 'static {
fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
/// Returns the default meta value for the [`AssetLoader`] (erased as [`Box<dyn AssetMetaDyn>`]).
fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
/// Returns the type name of the [`AssetLoader`].
fn type_name(&self) -> &'static str;
/// Returns the type path of the [`AssetLoader`].
fn type_path(&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`].
Expand Down Expand Up @@ -111,13 +112,13 @@ where

fn default_meta(&self) -> Box<dyn AssetMetaDyn> {
Box::new(AssetMeta::<L, ()>::new(crate::meta::AssetAction::Load {
loader: self.type_name().to_string(),
loader: self.type_path().to_string(),
settings: L::Settings::default(),
}))
}

fn type_name(&self) -> &'static str {
core::any::type_name::<L>()
fn type_path(&self) -> &'static str {
L::type_path()
}

fn type_id(&self) -> TypeId {
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_asset/src/loader_builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ impl NestedLoader<'_, '_, StaticTyped, Immediate<'_, '_>> {
path,
requested: TypeId::of::<A>(),
actual_asset_name: loader.asset_type_name(),
loader_name: loader.type_name(),
loader_name: loader.type_path(),
},
})
})
Expand Down
147 changes: 121 additions & 26 deletions crates/bevy_asset/src/processor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
mod log;
mod process;

#[cfg(feature = "trace")]
use bevy_reflect::TypePath;
pub use log::*;
pub use process::*;

Expand All @@ -56,10 +58,12 @@ use crate::{
AssetLoadError, AssetMetaCheck, AssetPath, AssetServer, AssetServerMode, DeserializeMetaError,
MissingAssetLoaderForExtensionError, UnapprovedPathMode, WriteDefaultMetaError,
};
use alloc::{borrow::ToOwned, boxed::Box, collections::VecDeque, sync::Arc, vec, vec::Vec};
use alloc::{
borrow::ToOwned, boxed::Box, collections::VecDeque, string::String, sync::Arc, vec, vec::Vec,
};
use bevy_ecs::prelude::*;
use bevy_platform::{
collections::{HashMap, HashSet},
collections::{hash_map::Entry, HashMap, HashSet},
sync::{PoisonError, RwLock},
};
use bevy_tasks::IoTaskPool;
Expand Down Expand Up @@ -110,9 +114,8 @@ pub struct AssetProcessorData {
/// avoids needing to use [`block_on`](bevy_tasks::block_on) to set the factory).
log_factory: Mutex<Option<Box<dyn ProcessorTransactionLogFactory>>>,
log: async_lock::RwLock<Option<Box<dyn ProcessorTransactionLog>>>,
processors: RwLock<HashMap<&'static str, Arc<dyn ErasedProcessor>>>,
/// Default processors for file extensions
default_processors: RwLock<HashMap<Box<str>, &'static str>>,
/// The processors that will be used to process assets.
processors: RwLock<Processors>,
state: async_lock::RwLock<ProcessorState>,
sources: AssetSources,
initialized_sender: async_broadcast::Sender<()>,
Expand All @@ -121,6 +124,31 @@ pub struct AssetProcessorData {
finished_receiver: async_broadcast::Receiver<()>,
}

#[derive(Default)]
struct Processors {
/// Maps the type path of the processor to its instance.
type_path_to_processor: HashMap<&'static str, Arc<dyn ErasedProcessor>>,
/// Maps the short type path of the processor to its instance.
short_type_path_to_processor: HashMap<&'static str, ShortTypeProcessorEntry>,
/// Maps the file extension of an asset to the type path of the processor we should use to
/// process it by default.
file_extension_to_default_processor: HashMap<Box<str>, &'static str>,
}

enum ShortTypeProcessorEntry {
/// There is a unique processor with the given short type path.
Unique {
/// The full type path of the processor.
type_path: &'static str,
/// The processor itself.
processor: Arc<dyn ErasedProcessor>,
},
/// There are (at least) two processors with the same short type path (storing the full type
/// paths of all conflicting processors). Users must fully specify the type path in order to
/// disambiguate.
Ambiguous(Vec<&'static str>),
}

impl AssetProcessor {
/// Creates a new [`AssetProcessor`] instance.
pub fn new(source: &mut AssetSourceBuilders) -> Self {
Expand Down Expand Up @@ -610,50 +638,92 @@ impl AssetProcessor {

/// Register a new asset processor.
pub fn register_processor<P: Process>(&self, processor: P) {
let mut process_plans = self
let mut processors = self
.data
.processors
.write()
.unwrap_or_else(PoisonError::into_inner);
#[cfg(feature = "trace")]
let processor = InstrumentedAssetProcessor(processor);
process_plans.insert(core::any::type_name::<P>(), Arc::new(processor));
let processor = Arc::new(processor);
processors
.type_path_to_processor
.insert(P::type_path(), processor.clone());
match processors
.short_type_path_to_processor
.entry(P::short_type_path())
{
Entry::Vacant(entry) => {
entry.insert(ShortTypeProcessorEntry::Unique {
type_path: P::type_path(),
processor,
});
}
Entry::Occupied(mut entry) => match entry.get_mut() {
ShortTypeProcessorEntry::Unique { type_path, .. } => {
let type_path = *type_path;
*entry.get_mut() =
ShortTypeProcessorEntry::Ambiguous(vec![type_path, P::type_path()]);
}
ShortTypeProcessorEntry::Ambiguous(type_paths) => {
type_paths.push(P::type_path());
}
},
}
}

/// Set the default processor for the given `extension`. Make sure `P` is registered with [`AssetProcessor::register_processor`].
pub fn set_default_processor<P: Process>(&self, extension: &str) {
let mut default_processors = self
let mut processors = self
.data
.default_processors
.processors
.write()
.unwrap_or_else(PoisonError::into_inner);
default_processors.insert(extension.into(), core::any::type_name::<P>());
processors
.file_extension_to_default_processor
.insert(extension.into(), P::type_path());
}

/// Returns the default processor for the given `extension`, if it exists.
pub fn get_default_processor(&self, extension: &str) -> Option<Arc<dyn ErasedProcessor>> {
let default_processors = self
let processors = self
.data
.default_processors
.read()
.unwrap_or_else(PoisonError::into_inner);
let key = default_processors.get(extension)?;
self.data
.processors
.read()
.unwrap_or_else(PoisonError::into_inner)
.get(key)
.cloned()
.unwrap_or_else(PoisonError::into_inner);
let key = processors
.file_extension_to_default_processor
.get(extension)?;
processors.type_path_to_processor.get(key).cloned()
}

/// Returns the processor with the given `processor_type_name`, if it exists.
pub fn get_processor(&self, processor_type_name: &str) -> Option<Arc<dyn ErasedProcessor>> {
pub fn get_processor(
&self,
processor_type_name: &str,
) -> Result<Arc<dyn ErasedProcessor>, GetProcessorError> {
let processors = self
.data
.processors
.read()
.unwrap_or_else(PoisonError::into_inner);
processors.get(processor_type_name).cloned()
if let Some(short_type_processor) = processors
.short_type_path_to_processor
.get(processor_type_name)
{
return match short_type_processor {
ShortTypeProcessorEntry::Unique { processor, .. } => Ok(processor.clone()),
ShortTypeProcessorEntry::Ambiguous(examples) => Err(GetProcessorError::Ambiguous {
processor_short_name: processor_type_name.to_owned(),
ambiguous_processor_names: examples.clone(),
}),
};
}
processors
.type_path_to_processor
.get(processor_type_name)
.cloned()
.ok_or_else(|| GetProcessorError::Missing(processor_type_name.to_owned()))
}

/// Populates the initial view of each asset by scanning the unprocessed and processed asset folders.
Expand Down Expand Up @@ -877,9 +947,7 @@ impl AssetProcessor {
(meta, None)
}
AssetActionMinimal::Process { processor } => {
let processor = self
.get_processor(&processor)
.ok_or_else(|| ProcessError::MissingProcessor(processor))?;
let processor = self.get_processor(&processor)?;
let meta = processor.deserialize_meta(&meta_bytes)?;
(meta, Some(processor))
}
Expand Down Expand Up @@ -1138,7 +1206,6 @@ impl AssetProcessorData {
log: Default::default(),
processors: Default::default(),
asset_infos: Default::default(),
default_processors: Default::default(),
}
}

Expand Down Expand Up @@ -1220,6 +1287,7 @@ impl AssetProcessorData {
}

#[cfg(feature = "trace")]
#[derive(TypePath)]
struct InstrumentedAssetProcessor<T>(T);

#[cfg(feature = "trace")]
Expand All @@ -1243,7 +1311,7 @@ impl<T: Process> Process for InstrumentedAssetProcessor<T> {
};
let span = info_span!(
"asset processing",
processor = core::any::type_name::<T>(),
processor = T::type_path(),
asset = context.path().to_string(),
);
self.0.process(context, meta, writer).instrument(span)
Expand Down Expand Up @@ -1564,6 +1632,33 @@ pub enum SetTransactionLogFactoryError {
AlreadyInUse,
}

/// An error when retrieving an asset processor.
#[derive(Error, Debug, PartialEq, Eq)]
pub enum GetProcessorError {
#[error("The processor '{0}' does not exist")]
Missing(String),
#[error("The processor '{processor_short_name}' is ambiguous between several processors: {ambiguous_processor_names:?}")]
Ambiguous {
processor_short_name: String,
ambiguous_processor_names: Vec<&'static str>,
},
}

impl From<GetProcessorError> for ProcessError {
fn from(err: GetProcessorError) -> Self {
match err {
GetProcessorError::Missing(name) => Self::MissingProcessor(name),
GetProcessorError::Ambiguous {
processor_short_name,
ambiguous_processor_names,
} => Self::AmbiguousProcessor {
processor_short_name,
ambiguous_processor_names,
},
}
}
}

// The asset processor currently requires multi_threaded.
#[cfg(feature = "multi_threaded")]
#[cfg(test)]
Expand Down
Loading