diff --git a/crates/bevy_animation/src/graph.rs b/crates/bevy_animation/src/graph.rs index e101e11209a2b..5c06783b50bc3 100644 --- a/crates/bevy_animation/src/graph.rs +++ b/crates/bevy_animation/src/graph.rs @@ -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}, @@ -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. diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 81deeff1450f5..246303340987a 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -771,7 +771,7 @@ mod tests { sub_texts: Vec, } - #[derive(Default)] + #[derive(Default, TypePath)] pub struct CoolTextLoader; #[derive(Error, Debug)] @@ -1864,6 +1864,7 @@ mod tests { .init_asset::() .register_asset_loader(CoolTextLoader); + #[derive(TypePath)] struct NestedLoadOfSubassetLoader; impl AssetLoader for NestedLoadOfSubassetLoader { @@ -2301,6 +2302,7 @@ mod tests { unused, reason = "We only use this for asset processor tests, which are only compiled with the `multi_threaded` feature." )] + #[derive(TypePath)] struct CoolTextSaver; impl AssetSaver for CoolTextSaver { @@ -2342,12 +2344,13 @@ mod tests { unused, reason = "We only use this for asset processor tests, which are only compiled with the `multi_threaded` feature." )] + #[derive(TypePath)] // Note: while we allow any Fn, since closures are unnameable types, creating a processor with a // closure cannot be used (since we need to include the name of the transformer in the meta // file). struct RootAssetTransformer, A: Asset>(M, PhantomData); - trait MutateAsset: Send + Sync + 'static { + trait MutateAsset: TypePath + Send + Sync + 'static { fn mutate(&self, asset: &mut A); } @@ -2423,6 +2426,19 @@ mod tests { assert_eq!(processed_asset, source_asset); } + // The asset processor currently requires multi_threaded. + #[cfg(feature = "multi_threaded")] + #[derive(TypePath)] + struct AddText; + + // The asset processor currently requires multi_threaded. + #[cfg(feature = "multi_threaded")] + impl MutateAsset for AddText { + fn mutate(&self, text: &mut CoolText) { + text.text.push_str("_def"); + } + } + // The asset processor currently requires multi_threaded. #[cfg(feature = "multi_threaded")] #[test] @@ -2433,14 +2449,6 @@ mod tests { processed_dir, } = create_app_with_asset_processor(); - struct AddText; - - impl MutateAsset for AddText { - fn mutate(&self, text: &mut CoolText) { - text.text.push_str("_def"); - } - } - type CoolTextProcessor = LoadTransformAndSave< CoolTextLoader, RootAssetTransformer, @@ -2493,13 +2501,67 @@ mod tests { processed_dir, } = create_app_with_asset_processor(); - struct AddText; + type CoolTextProcessor = LoadTransformAndSave< + CoolTextLoader, + RootAssetTransformer, + CoolTextSaver, + >; + app.register_asset_loader(CoolTextLoader) + .register_asset_processor(CoolTextProcessor::new( + RootAssetTransformer::new(AddText), + CoolTextSaver, + )); - impl MutateAsset for AddText { - fn mutate(&self, text: &mut CoolText) { - text.text.push_str("_def"); - } - } + let path = Path::new("abc.cool.ron"); + source_dir.insert_asset_text( + path, + r#"( + text: "abc", + dependencies: [], + embedded_dependencies: [], + sub_texts: [], +)"#, + ); + source_dir.insert_meta_text(path, r#"( + meta_format_version: "1.0", + asset: Process( + processor: "bevy_asset::processor::process::LoadTransformAndSave, bevy_asset::tests::CoolTextSaver>", + settings: ( + loader_settings: (), + transformer_settings: (), + saver_settings: (), + ), + ), +)"#); + + // Start the app, which also starts the asset processor. + app.update(); + + // Wait for all processing to finish. + bevy_tasks::block_on( + app.world() + .resource::() + .data() + .wait_until_finished(), + ); + + let processed_asset = processed_dir.get_asset(path).unwrap(); + let processed_asset = str::from_utf8(processed_asset.value()).unwrap(); + assert_eq!( + processed_asset, + r#"(text:"abc_def",dependencies:[],embedded_dependencies:[],sub_texts:[])"# + ); + } + + // The asset processor currently requires multi_threaded. + #[cfg(feature = "multi_threaded")] + #[test] + fn asset_processor_transforms_asset_with_short_path_meta() { + let AppWithProcessor { + mut app, + source_dir, + processed_dir, + } = create_app_with_asset_processor(); type CoolTextProcessor = LoadTransformAndSave< CoolTextLoader, @@ -2525,7 +2587,7 @@ mod tests { source_dir.insert_meta_text(path, r#"( meta_format_version: "1.0", asset: Process( - processor: "bevy_asset::processor::process::LoadTransformAndSave, bevy_asset::tests::CoolTextSaver>", + processor: "LoadTransformAndSave, CoolTextSaver>", settings: ( loader_settings: (), transformer_settings: (), diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index ad4c6827461f9..08606327542ac 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -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}; @@ -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`]. @@ -66,8 +67,8 @@ pub trait ErasedAssetLoader: Send + Sync + 'static { 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 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`]. @@ -111,13 +112,13 @@ where fn default_meta(&self) -> Box { Box::new(AssetMeta::::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::() + fn type_path(&self) -> &'static str { + L::type_path() } fn type_id(&self) -> TypeId { diff --git a/crates/bevy_asset/src/loader_builders.rs b/crates/bevy_asset/src/loader_builders.rs index 8e94b742ec6c6..9fb3d13a5a773 100644 --- a/crates/bevy_asset/src/loader_builders.rs +++ b/crates/bevy_asset/src/loader_builders.rs @@ -467,7 +467,7 @@ impl NestedLoader<'_, '_, StaticTyped, Immediate<'_, '_>> { path, requested: TypeId::of::(), actual_asset_name: loader.asset_type_name(), - loader_name: loader.type_name(), + loader_name: loader.type_path(), }, }) }) diff --git a/crates/bevy_asset/src/processor/mod.rs b/crates/bevy_asset/src/processor/mod.rs index f5f071cfb9561..0b21397391b81 100644 --- a/crates/bevy_asset/src/processor/mod.rs +++ b/crates/bevy_asset/src/processor/mod.rs @@ -40,6 +40,8 @@ mod log; mod process; +#[cfg(feature = "trace")] +use bevy_reflect::TypePath; pub use log::*; pub use process::*; @@ -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; @@ -101,9 +105,8 @@ pub struct AssetProcessor { pub struct AssetProcessorData { pub(crate) asset_infos: async_lock::RwLock, log: async_lock::RwLock>, - processors: RwLock>>, - /// Default processors for file extensions - default_processors: RwLock, &'static str>>, + /// The processors that will be used to process assets. + processors: RwLock, state: async_lock::RwLock, sources: AssetSources, initialized_sender: async_broadcast::Sender<()>, @@ -112,6 +115,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>, + /// 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, &'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, + }, + /// 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 { @@ -583,50 +611,92 @@ impl AssetProcessor { /// Register a new asset processor. pub fn register_processor(&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::

(), 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(&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::

()); + 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> { - 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> { + pub fn get_processor( + &self, + processor_type_name: &str, + ) -> Result, 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. @@ -850,9 +920,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)) } @@ -1101,7 +1169,6 @@ impl AssetProcessorData { log: Default::default(), processors: Default::default(), asset_infos: Default::default(), - default_processors: Default::default(), } } @@ -1161,6 +1228,7 @@ impl AssetProcessorData { } #[cfg(feature = "trace")] +#[derive(TypePath)] struct InstrumentedAssetProcessor(T); #[cfg(feature = "trace")] @@ -1184,7 +1252,7 @@ impl Process for InstrumentedAssetProcessor { }; let span = info_span!( "asset processing", - processor = core::any::type_name::(), + processor = T::type_path(), asset = context.path().to_string(), ); self.0.process(context, meta, writer).instrument(span) @@ -1497,3 +1565,166 @@ pub enum InitializeError { #[error("Failed to validate asset log: {0}")] ValidateLogError(#[from] ValidateLogError), } + +/// 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 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, + }, + } + } +} + +#[cfg(test)] +mod tests { + use bevy_reflect::TypePath; + use core::marker::PhantomData; + + use crate::io::memory::{Dir, MemoryAssetReader}; + + use super::*; + + #[derive(TypePath)] + struct MyProcessor(PhantomData T>); + + impl Process for MyProcessor { + type OutputLoader = (); + type Settings = (); + + async fn process( + &self, + _context: &mut ProcessContext<'_>, + _meta: AssetMeta<(), Self>, + _writer: &mut crate::io::Writer, + ) -> Result<(), ProcessError> { + Ok(()) + } + } + + #[derive(TypePath)] + struct Marker; + + fn create_empty_asset_processor() -> AssetProcessor { + let mut sources = AssetSourceBuilders::default(); + // Create an empty asset source so that AssetProcessor is happy. + let dir = Dir::default(); + let memory_reader = MemoryAssetReader { root: dir.clone() }; + sources.insert( + AssetSourceId::Default, + AssetSource::build().with_reader(move || Box::new(memory_reader.clone())), + ); + + AssetProcessor::new(&mut sources) + } + + #[test] + fn get_asset_processor_by_name() { + let asset_processor = create_empty_asset_processor(); + asset_processor.register_processor(MyProcessor::(PhantomData)); + + let long_processor = asset_processor + .get_processor( + "bevy_asset::processor::tests::MyProcessor", + ) + .expect("Processor was previously registered"); + let short_processor = asset_processor + .get_processor("MyProcessor") + .expect("Processor was previously registered"); + + // We can use either the long or short processor name and we will get the same processor + // out. + assert!(Arc::ptr_eq(&long_processor, &short_processor)); + } + + #[test] + fn missing_processor_returns_error() { + let asset_processor = create_empty_asset_processor(); + + let Err(long_processor_err) = asset_processor.get_processor( + "bevy_asset::processor::tests::MyProcessor", + ) else { + panic!("Processor was returned even though we never registered any."); + }; + let GetProcessorError::Missing(long_processor_err) = &long_processor_err else { + panic!("get_processor returned incorrect error: {long_processor_err}"); + }; + assert_eq!( + long_processor_err, + "bevy_asset::processor::tests::MyProcessor" + ); + + // Short paths should also return an error. + + let Err(long_processor_err) = asset_processor.get_processor("MyProcessor") else { + panic!("Processor was returned even though we never registered any."); + }; + let GetProcessorError::Missing(long_processor_err) = &long_processor_err else { + panic!("get_processor returned incorrect error: {long_processor_err}"); + }; + assert_eq!(long_processor_err, "MyProcessor"); + } + + // Create another marker type whose short name will overlap `Marker`. + mod sneaky { + use bevy_reflect::TypePath; + + #[derive(TypePath)] + pub struct Marker; + } + + #[test] + fn ambiguous_short_path_returns_error() { + let asset_processor = create_empty_asset_processor(); + asset_processor.register_processor(MyProcessor::(PhantomData)); + asset_processor.register_processor(MyProcessor::(PhantomData)); + + let Err(long_processor_err) = asset_processor.get_processor("MyProcessor") else { + panic!("Processor was returned even though the short path is ambiguous."); + }; + let GetProcessorError::Ambiguous { + processor_short_name, + ambiguous_processor_names, + } = &long_processor_err + else { + panic!("get_processor returned incorrect error: {long_processor_err}"); + }; + assert_eq!(processor_short_name, "MyProcessor"); + let expected_ambiguous_names = [ + "bevy_asset::processor::tests::MyProcessor", + "bevy_asset::processor::tests::MyProcessor", + ]; + assert_eq!(ambiguous_processor_names, &expected_ambiguous_names); + + let processor_1 = asset_processor + .get_processor( + "bevy_asset::processor::tests::MyProcessor", + ) + .expect("Processor was previously registered"); + let processor_2 = asset_processor + .get_processor( + "bevy_asset::processor::tests::MyProcessor", + ) + .expect("Processor was previously registered"); + + // If we fully specify the paths, we get the two different processors. + assert!(!Arc::ptr_eq(&processor_1, &processor_2)); + } +} diff --git a/crates/bevy_asset/src/processor/process.rs b/crates/bevy_asset/src/processor/process.rs index b37265d0fb660..905904566c84c 100644 --- a/crates/bevy_asset/src/processor/process.rs +++ b/crates/bevy_asset/src/processor/process.rs @@ -14,7 +14,9 @@ use alloc::{ borrow::ToOwned, boxed::Box, string::{String, ToString}, + vec::Vec, }; +use bevy_reflect::TypePath; use bevy_tasks::{BoxedFuture, ConditionalSendFuture}; use core::marker::PhantomData; use serde::{Deserialize, Serialize}; @@ -25,7 +27,7 @@ use thiserror::Error; /// /// This is a "low level", maximally flexible interface. Most use cases are better served by the [`LoadTransformAndSave`] implementation /// of [`Process`]. -pub trait Process: Send + Sync + Sized + 'static { +pub trait Process: TypePath + Send + Sync + Sized + 'static { /// The configuration / settings used to process the asset. This will be stored in the [`AssetMeta`] and is user-configurable per-asset. type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>; /// The [`AssetLoader`] that will be used to load the final processed asset. @@ -59,6 +61,7 @@ pub trait Process: Send + Sync + Sized + 'static { /// This uses [`LoadTransformAndSaveSettings`] to configure the processor. /// /// [`Asset`]: crate::Asset +#[derive(TypePath)] pub struct LoadTransformAndSave< L: AssetLoader, T: AssetTransformer, @@ -120,6 +123,11 @@ pub enum ProcessError { #[error("The processor '{0}' does not exist")] #[from(ignore)] MissingProcessor(String), + #[error("The processor '{processor_short_name}' is ambiguous between several processors: {ambiguous_processor_names:?}")] + AmbiguousProcessor { + processor_short_name: String, + ambiguous_processor_names: Vec<&'static str>, + }, #[error("Encountered an AssetReader error for '{path}': {err}")] #[from(ignore)] AssetReaderError { @@ -180,7 +188,7 @@ where return Err(ProcessError::WrongMetaType); }; let loader_meta = AssetMeta::::new(AssetAction::Load { - loader: core::any::type_name::().to_string(), + loader: Loader::type_path().to_string(), settings: settings.loader_settings, }); let pre_transformed_asset = TransformedAsset::::from_loaded( @@ -237,7 +245,7 @@ impl ErasedProcessor for P { let loader_settings =

::process(self, context, *meta, writer).await?; let output_meta: Box = Box::new(AssetMeta::::new(AssetAction::Load { - loader: core::any::type_name::().to_string(), + loader: P::OutputLoader::type_path().to_string(), settings: loader_settings, })); Ok(output_meta) @@ -251,7 +259,7 @@ impl ErasedProcessor for P { fn default_meta(&self) -> Box { Box::new(AssetMeta::<(), P>::new(AssetAction::Process { - processor: core::any::type_name::

().to_string(), + processor: P::type_path().to_string(), settings: P::Settings::default(), })) } @@ -307,7 +315,7 @@ impl<'a> ProcessContext<'a> { meta: AssetMeta, ) -> Result { let server = &self.processor.server; - let loader_name = core::any::type_name::(); + let loader_name = L::type_path(); let loader = server.get_asset_loader_with_type_name(loader_name).await?; let mut reader = SliceReader::new(self.asset_bytes); let loaded_asset = server diff --git a/crates/bevy_asset/src/saver.rs b/crates/bevy_asset/src/saver.rs index 0f3e3987f264a..86d170db02497 100644 --- a/crates/bevy_asset/src/saver.rs +++ b/crates/bevy_asset/src/saver.rs @@ -5,6 +5,7 @@ use crate::{ use alloc::boxed::Box; use atomicow::CowArc; use bevy_platform::collections::HashMap; +use bevy_reflect::TypePath; use bevy_tasks::{BoxedFuture, ConditionalSendFuture}; use core::{borrow::Borrow, hash::Hash, ops::Deref}; use serde::{Deserialize, Serialize}; @@ -15,7 +16,7 @@ use serde::{Deserialize, Serialize}; /// This trait is generally used in concert with [`AssetWriter`](crate::io::AssetWriter) to write assets as bytes. /// /// For a complementary version of this trait that can load assets, see [`AssetLoader`]. -pub trait AssetSaver: Send + Sync + 'static { +pub trait AssetSaver: TypePath + Send + Sync + 'static { /// The top level [`Asset`] saved by this [`AssetSaver`]. type Asset: Asset; /// The settings type used by this [`AssetSaver`]. diff --git a/crates/bevy_asset/src/server/loaders.rs b/crates/bevy_asset/src/server/loaders.rs index 9c13c861bd986..e414502a0c89d 100644 --- a/crates/bevy_asset/src/server/loaders.rs +++ b/crates/bevy_asset/src/server/loaders.rs @@ -5,6 +5,8 @@ use crate::{ use alloc::{boxed::Box, sync::Arc, vec::Vec}; use async_broadcast::RecvError; use bevy_platform::collections::HashMap; +#[cfg(feature = "trace")] +use bevy_reflect::TypePath; use bevy_tasks::IoTaskPool; use bevy_utils::TypeIdMap; use core::any::TypeId; @@ -23,8 +25,8 @@ pub(crate) struct AssetLoaders { loaders: Vec, type_id_to_loaders: TypeIdMap>, extension_to_loaders: HashMap, Vec>, - type_name_to_loader: HashMap<&'static str, usize>, - preregistered_loaders: HashMap<&'static str, usize>, + type_path_to_loader: HashMap<&'static str, usize>, + type_path_to_preregistered_loader: HashMap<&'static str, usize>, } impl AssetLoaders { @@ -35,7 +37,8 @@ impl AssetLoaders { /// 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 = core::any::type_name::(); + let type_path = L::type_path(); + // TODO: Allow using the short path of loaders. let loader_asset_type = TypeId::of::(); let loader_asset_type_name = core::any::type_name::(); @@ -44,7 +47,7 @@ impl AssetLoaders { let loader = Arc::new(loader); let (loader_index, is_new) = - if let Some(index) = self.preregistered_loaders.remove(type_name) { + if let Some(index) = self.type_path_to_preregistered_loader.remove(type_path) { (index, false) } else { (self.loaders.len(), true) @@ -75,7 +78,7 @@ impl AssetLoaders { Loader must be specified in a .meta file in order to load assets of this type with these extensions."); } - self.type_name_to_loader.insert(type_name, loader_index); + self.type_path_to_loader.insert(type_path, loader_index); self.type_id_to_loaders .entry(loader_asset_type) @@ -108,12 +111,14 @@ impl AssetLoaders { pub(crate) fn reserve(&mut self, extensions: &[&str]) { let loader_asset_type = TypeId::of::(); let loader_asset_type_name = core::any::type_name::(); - let type_name = core::any::type_name::(); + let type_path = L::type_path(); + // TODO: Allow using the short path of loaders. let loader_index = self.loaders.len(); - self.preregistered_loaders.insert(type_name, loader_index); - self.type_name_to_loader.insert(type_name, loader_index); + self.type_path_to_preregistered_loader + .insert(type_path, loader_index); + self.type_path_to_loader.insert(type_path, loader_index); let existing_loaders_for_type_id = self.type_id_to_loaders.get(&loader_asset_type); let mut duplicate_extensions = Vec::new(); @@ -152,7 +157,7 @@ impl AssetLoaders { /// 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()?; + let index = self.type_path_to_loader.get(name).copied()?; self.get_by_index(index) } @@ -309,6 +314,7 @@ impl MaybeAssetLoader { } #[cfg(feature = "trace")] +#[derive(TypePath)] struct InstrumentedAssetLoader(T); #[cfg(feature = "trace")] @@ -325,7 +331,7 @@ impl AssetLoader for InstrumentedAssetLoader { ) -> impl ConditionalSendFuture> { let span = info_span!( "asset loading", - loader = core::any::type_name::(), + loader = T::type_path(), asset = load_context.asset_path().to_string(), ); self.0.load(reader, settings, load_context).instrument(span) @@ -361,6 +367,7 @@ mod tests { #[derive(Asset, TypePath, Debug)] struct C; + #[derive(TypePath)] struct Loader { sender: Sender<()>, _phantom: PhantomData, @@ -430,7 +437,7 @@ mod tests { let loader = block_on( loaders - .get_by_name(core::any::type_name::>()) + .get_by_name( as TypePath>::type_path()) .unwrap() .get(), ) diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index cd046aa8a238c..6c0b38ef17f8a 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -247,7 +247,7 @@ impl AssetServer { loader.get().await.map_err(|_| error()) } - /// Returns the registered [`AssetLoader`] associated with the given [`core::any::type_name`], if it exists. + /// Returns the registered [`AssetLoader`] associated with the given type name, if it exists. pub async fn get_asset_loader_with_type_name( &self, type_name: &str, @@ -768,7 +768,7 @@ impl AssetServer { path: path.into_owned(), requested: asset_type_id, actual_asset_name: loader.asset_type_name(), - loader_name: loader.type_name(), + loader_name: loader.type_path(), }); } } @@ -1474,12 +1474,12 @@ impl AssetServer { .await .map_err(|_| AssetLoadError::AssetLoaderPanic { path: asset_path.clone_owned(), - loader_name: loader.type_name(), + loader_name: loader.type_path(), })? .map_err(|e| { AssetLoadError::AssetLoaderError(AssetLoaderError { path: asset_path.clone_owned(), - loader_name: loader.type_name(), + loader_name: loader.type_path(), error: e.into(), }) }) diff --git a/crates/bevy_asset/src/transformer.rs b/crates/bevy_asset/src/transformer.rs index f80f44511ab4c..575efbfbaa8eb 100644 --- a/crates/bevy_asset/src/transformer.rs +++ b/crates/bevy_asset/src/transformer.rs @@ -2,6 +2,7 @@ use crate::{meta::Settings, Asset, ErasedLoadedAsset, Handle, LabeledAsset, Unty use alloc::boxed::Box; use atomicow::CowArc; use bevy_platform::collections::HashMap; +use bevy_reflect::TypePath; use bevy_tasks::ConditionalSendFuture; use core::{ borrow::Borrow, @@ -15,7 +16,7 @@ use serde::{Deserialize, Serialize}; /// Transforms an [`Asset`] of a given [`AssetTransformer::AssetInput`] type to an [`Asset`] of [`AssetTransformer::AssetOutput`] type. /// /// This trait is commonly used in association with [`LoadTransformAndSave`](crate::processor::LoadTransformAndSave) to accomplish common asset pipeline workflows. -pub trait AssetTransformer: Send + Sync + 'static { +pub trait AssetTransformer: TypePath + Send + Sync + 'static { /// The [`Asset`] type which this [`AssetTransformer`] takes as and input. type AssetInput: Asset; /// The [`Asset`] type which this [`AssetTransformer`] outputs. @@ -249,6 +250,7 @@ impl<'a, A: Asset> TransformedSubAsset<'a, A> { } /// An identity [`AssetTransformer`] which infallibly returns the input [`Asset`] on transformation.] +#[derive(TypePath)] pub struct IdentityAssetTransformer { _phantom: PhantomData A>, } diff --git a/crates/bevy_audio/src/audio_source.rs b/crates/bevy_audio/src/audio_source.rs index 9cf3ca988b0b1..be54c89ea78ee 100644 --- a/crates/bevy_audio/src/audio_source.rs +++ b/crates/bevy_audio/src/audio_source.rs @@ -34,7 +34,7 @@ impl AsRef<[u8]> for AudioSource { /// `.mp3` with `bevy/mp3` /// `.flac` with `bevy/flac` /// `.wav` with `bevy/wav` -#[derive(Default)] +#[derive(Default, TypePath)] pub struct AudioLoader; impl AssetLoader for AudioLoader { diff --git a/crates/bevy_gltf/src/loader/mod.rs b/crates/bevy_gltf/src/loader/mod.rs index 4c651b0dc9ac7..5c9db689f38d2 100644 --- a/crates/bevy_gltf/src/loader/mod.rs +++ b/crates/bevy_gltf/src/loader/mod.rs @@ -2,6 +2,7 @@ mod extensions; mod gltf_ext; use alloc::sync::Arc; +use bevy_reflect::TypePath; use std::{ io::Error, path::{Path, PathBuf}, @@ -136,6 +137,7 @@ pub enum GltfError { } /// Loads glTF files with all of their data as their corresponding bevy representations. +#[derive(TypePath)] pub struct GltfLoader { /// List of compressed image formats handled by the loader. pub supported_compressed_formats: CompressedImageFormats, diff --git a/crates/bevy_image/src/compressed_image_saver.rs b/crates/bevy_image/src/compressed_image_saver.rs index bcae2f238060d..3675dfc39baa3 100644 --- a/crates/bevy_image/src/compressed_image_saver.rs +++ b/crates/bevy_image/src/compressed_image_saver.rs @@ -1,13 +1,15 @@ use crate::{Image, ImageFormat, ImageFormatSetting, ImageLoader, ImageLoaderSettings}; use bevy_asset::saver::{AssetSaver, SavedAsset}; +use bevy_reflect::TypePath; use futures_lite::AsyncWriteExt; use thiserror::Error; +#[derive(TypePath)] pub struct CompressedImageSaver; #[non_exhaustive] -#[derive(Debug, Error)] +#[derive(Debug, Error, TypePath)] pub enum CompressedImageSaverError { #[error(transparent)] Io(#[from] std::io::Error), diff --git a/crates/bevy_image/src/exr_texture_loader.rs b/crates/bevy_image/src/exr_texture_loader.rs index 9cbf315bb4f88..e40c9735dfb49 100644 --- a/crates/bevy_image/src/exr_texture_loader.rs +++ b/crates/bevy_image/src/exr_texture_loader.rs @@ -1,12 +1,13 @@ use crate::{Image, TextureAccessError, TextureFormatPixelInfo}; use bevy_asset::{io::Reader, AssetLoader, LoadContext, RenderAssetUsages}; +use bevy_reflect::TypePath; use image::ImageDecoder; use serde::{Deserialize, Serialize}; use thiserror::Error; use wgpu_types::{Extent3d, TextureDimension, TextureFormat}; /// Loads EXR textures as Texture assets -#[derive(Clone, Default)] +#[derive(Clone, Default, TypePath)] #[cfg(feature = "exr")] pub struct ExrTextureLoader; @@ -18,7 +19,7 @@ pub struct ExrTextureLoaderSettings { /// Possible errors that can be produced by [`ExrTextureLoader`] #[non_exhaustive] -#[derive(Debug, Error)] +#[derive(Debug, Error, TypePath)] #[cfg(feature = "exr")] pub enum ExrTextureLoaderError { #[error(transparent)] diff --git a/crates/bevy_image/src/hdr_texture_loader.rs b/crates/bevy_image/src/hdr_texture_loader.rs index 83e9df3b3d807..6210c91dccf87 100644 --- a/crates/bevy_image/src/hdr_texture_loader.rs +++ b/crates/bevy_image/src/hdr_texture_loader.rs @@ -1,13 +1,14 @@ use crate::{Image, TextureAccessError, TextureFormatPixelInfo}; use bevy_asset::RenderAssetUsages; use bevy_asset::{io::Reader, AssetLoader, LoadContext}; +use bevy_reflect::TypePath; use image::DynamicImage; use serde::{Deserialize, Serialize}; use thiserror::Error; use wgpu_types::{Extent3d, TextureDimension, TextureFormat}; /// Loads HDR textures as Texture assets -#[derive(Clone, Default)] +#[derive(Clone, Default, TypePath)] pub struct HdrTextureLoader; #[derive(Serialize, Deserialize, Default, Debug)] diff --git a/crates/bevy_image/src/image_loader.rs b/crates/bevy_image/src/image_loader.rs index 91d03cb9d1116..2fa51e62a5c40 100644 --- a/crates/bevy_image/src/image_loader.rs +++ b/crates/bevy_image/src/image_loader.rs @@ -1,12 +1,13 @@ use crate::image::{Image, ImageFormat, ImageType, TextureError}; use bevy_asset::{io::Reader, AssetLoader, LoadContext, RenderAssetUsages}; +use bevy_reflect::TypePath; use thiserror::Error; use super::{CompressedImageFormats, ImageSampler}; use serde::{Deserialize, Serialize}; /// Loader for images that can be read by the `image` crate. -#[derive(Clone)] +#[derive(Clone, TypePath)] pub struct ImageLoader { supported_compressed_formats: CompressedImageFormats, } diff --git a/crates/bevy_pbr/src/meshlet/asset.rs b/crates/bevy_pbr/src/meshlet/asset.rs index 49a993ffa4388..b965165db9727 100644 --- a/crates/bevy_pbr/src/meshlet/asset.rs +++ b/crates/bevy_pbr/src/meshlet/asset.rs @@ -145,6 +145,7 @@ pub struct MeshletBoundingSphere { } /// An [`AssetSaver`] for `.meshlet_mesh` [`MeshletMesh`] assets. +#[derive(TypePath)] pub struct MeshletMeshSaver; impl AssetSaver for MeshletMeshSaver { @@ -193,6 +194,7 @@ impl AssetSaver for MeshletMeshSaver { } /// An [`AssetLoader`] for `.meshlet_mesh` [`MeshletMesh`] assets. +#[derive(TypePath)] pub struct MeshletMeshLoader; impl AssetLoader for MeshletMeshLoader { diff --git a/crates/bevy_scene/src/scene_loader.rs b/crates/bevy_scene/src/scene_loader.rs index d74dff84f5a6d..b1f17af7f4d14 100644 --- a/crates/bevy_scene/src/scene_loader.rs +++ b/crates/bevy_scene/src/scene_loader.rs @@ -3,7 +3,7 @@ use bevy_ecs::{ reflect::AppTypeRegistry, world::{FromWorld, World}, }; -use bevy_reflect::TypeRegistryArc; +use bevy_reflect::{TypePath, TypeRegistryArc}; use thiserror::Error; #[cfg(feature = "serialize")] @@ -16,7 +16,7 @@ use { /// Asset loader for a Bevy dynamic scene (`.scn` / `.scn.ron`). /// /// The loader handles assets serialized with [`DynamicScene::serialize`]. -#[derive(Debug)] +#[derive(Debug, TypePath)] pub struct SceneLoader { #[cfg_attr( not(feature = "serialize"), diff --git a/crates/bevy_shader/src/shader.rs b/crates/bevy_shader/src/shader.rs index 932de7b98ceff..4de9b8e913a40 100644 --- a/crates/bevy_shader/src/shader.rs +++ b/crates/bevy_shader/src/shader.rs @@ -338,7 +338,7 @@ impl From<&Source> for naga_oil::compose::ShaderType { } } -#[derive(Default)] +#[derive(Default, TypePath)] pub struct ShaderLoader; #[non_exhaustive] diff --git a/crates/bevy_text/src/font_loader.rs b/crates/bevy_text/src/font_loader.rs index 9e0f2185a234e..22bacf208af13 100644 --- a/crates/bevy_text/src/font_loader.rs +++ b/crates/bevy_text/src/font_loader.rs @@ -1,8 +1,9 @@ use crate::Font; use bevy_asset::{io::Reader, AssetLoader, LoadContext}; +use bevy_reflect::TypePath; use thiserror::Error; -#[derive(Default)] +#[derive(Default, TypePath)] /// An [`AssetLoader`] for [`Font`]s, for use by the [`AssetServer`](bevy_asset::AssetServer) pub struct FontLoader; diff --git a/examples/asset/asset_decompression.rs b/examples/asset/asset_decompression.rs index e514924ca9d0a..0663a398a072a 100644 --- a/examples/asset/asset_decompression.rs +++ b/examples/asset/asset_decompression.rs @@ -17,7 +17,7 @@ struct GzAsset { uncompressed: ErasedLoadedAsset, } -#[derive(Default)] +#[derive(Default, TypePath)] struct GzAssetLoader; /// Possible errors that can be produced by [`GzAssetLoader`] diff --git a/examples/asset/custom_asset.rs b/examples/asset/custom_asset.rs index 8d4ac958ecdd1..60982e53fa313 100644 --- a/examples/asset/custom_asset.rs +++ b/examples/asset/custom_asset.rs @@ -17,7 +17,7 @@ struct CustomAsset { value: i32, } -#[derive(Default)] +#[derive(Default, TypePath)] struct CustomAssetLoader; /// Possible errors that can be produced by [`CustomAssetLoader`] @@ -58,7 +58,7 @@ struct Blob { bytes: Vec, } -#[derive(Default)] +#[derive(Default, TypePath)] struct BlobAssetLoader; /// Possible errors that can be produced by [`BlobAssetLoader`] diff --git a/examples/asset/processing/asset_processing.rs b/examples/asset/processing/asset_processing.rs index d5da644c27190..651dbe2148a76 100644 --- a/examples/asset/processing/asset_processing.rs +++ b/examples/asset/processing/asset_processing.rs @@ -69,7 +69,7 @@ impl Plugin for TextPlugin { #[derive(Asset, TypePath, Debug)] struct Text(String); -#[derive(Default)] +#[derive(Default, TypePath)] struct TextLoader; #[derive(Clone, Default, Serialize, Deserialize)] @@ -120,7 +120,7 @@ struct CoolText { dependencies: Vec>, } -#[derive(Default)] +#[derive(Default, TypePath)] struct CoolTextLoader; #[derive(Debug, Error)] @@ -182,7 +182,7 @@ impl AssetLoader for CoolTextLoader { } } -#[derive(Default)] +#[derive(Default, TypePath)] struct CoolTextTransformer; #[derive(Default, Serialize, Deserialize)] @@ -206,6 +206,7 @@ impl AssetTransformer for CoolTextTransformer { } } +#[derive(TypePath)] struct CoolTextSaver; impl AssetSaver for CoolTextSaver { diff --git a/examples/asset/processing/assets/a.cool.ron.meta b/examples/asset/processing/assets/a.cool.ron.meta index d87c629bac33d..bfe78b3128f5d 100644 --- a/examples/asset/processing/assets/a.cool.ron.meta +++ b/examples/asset/processing/assets/a.cool.ron.meta @@ -1,7 +1,7 @@ ( meta_format_version: "1.0", asset: Process( - processor: "bevy_asset::processor::process::LoadTransformAndSave", + processor: "LoadTransformAndSave", settings: ( loader_settings: (), transformer_settings: ( diff --git a/release-content/migration-guides/type_path_for_asset_traits.md b/release-content/migration-guides/type_path_for_asset_traits.md new file mode 100644 index 0000000000000..eda0fd901735f --- /dev/null +++ b/release-content/migration-guides/type_path_for_asset_traits.md @@ -0,0 +1,25 @@ +--- +title: Traits `AssetLoader`, `AssetTransformer`, `AssetSaver`, and `Process` all now require `TypePath` +pull_requests: [21339] +--- + +The `AssetLoader`, `AssetTransformer`, `AssetSaver`, and `Process` traits now include a super trait +of `TypePath`. This means if you previously had a loader like: + +```rust +struct MyFunkyLoader { + add_funk: u32, +} +``` + +You will need to add the following derive: + +```rust +#[derive(TypePath)] +struct MyFunkyLoader { + add_funk: u32, +} +``` + +`TypePath` comes from `bevy_reflect`, so libraries may also need to add a dependency on +`bevy_reflect`. diff --git a/release-content/release-notes/short_type_path_asset_processors.md b/release-content/release-notes/short_type_path_asset_processors.md new file mode 100644 index 0000000000000..ab74ae7febb98 --- /dev/null +++ b/release-content/release-notes/short_type_path_asset_processors.md @@ -0,0 +1,44 @@ +--- +title: Short-type-path asset processors +authors: ["@andriyDev"] +pull_requests: [21339] +--- + +Asset processors allow manipulating assets at "publish-time" to convert them into a more optimal +form when loading the data at runtime. This can either be done using a default processor, which +processes all assets with a particular file extension, or by specifying the processor in the asset's +meta file. + +In previous versions of Bevy, the processor had to be **fully** specified in the asset's meta file. +For example: + +```ron +( + meta_format_version: "1.0", + asset: Process( + processor: "bevy_asset::processor::process::LoadTransformAndSave", + settings: ( + loader_settings: (), + transformer_settings: (), + saver_settings: (), + ), + ), +) +``` + +As you can see, processor types can be very verbose! In order to make these meta files easier to +manipulate, we now also support using the "short type path" of the asset. This would look like: + +```ron +( + meta_format_version: "1.0", + asset: Process( + processor: "LoadTransformAndSave", + settings: ( + loader_settings: (), + transformer_settings: (), + saver_settings: (), + ), + ), +) +```