diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 169c63f057f93..b78fbedd1a241 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -694,6 +694,59 @@ impl App { self } + /// Registers a fallible conversion from type T to U with the reflection + /// system. + /// + /// The supplied closure is expected to produce a value of type U, given an + /// instance of type T. If the conversion fails, the closure should return + /// the input value, wrapped in an `Err` variant. + /// + /// # Example + /// ``` + /// use bevy_app::App; + /// + /// App::new() + /// .register_type::() + /// .register_type::() + /// .register_type_conversion::(|n| Ok(n.to_string())); + /// ``` + /// + /// See [`bevy_reflect::TypeRegistry::register_type_conversion`]. + #[cfg(feature = "bevy_reflect")] + pub fn register_type_conversion(&mut self, function: F) -> &mut Self + where + T: Reflect + TypePath, + U: Reflect + TypePath, + F: Fn(T) -> Result + Clone + Send + Sync + 'static, + { + self.main_mut().register_type_conversion(function); + self + } + + /// Given types T and U, where `U: From`, registers that conversion with + /// the reflection system. + /// + /// # Example + /// ``` + /// use bevy_app::App; + /// + /// App::new() + /// .register_type::() + /// .register_type::() + /// .register_into_type_conversion::(); + /// ``` + /// + /// See [`bevy_reflect::TypeRegistry::register_into_type_conversion`]. + #[cfg(feature = "bevy_reflect")] + pub fn register_into_type_conversion(&mut self) -> &mut Self + where + T: Reflect + TypePath, + U: Reflect + TypePath + From, + { + self.main_mut().register_into_type_conversion::(); + self + } + /// Registers the given function into the [`AppFunctionRegistry`] resource. /// /// The given function will internally be stored as a [`DynamicFunction`] diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index 7790fbf9c7683..1b20aab2ff5d3 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -485,6 +485,33 @@ impl SubApp { self } + /// See [`App::register_type_conversion`]. + #[cfg(feature = "bevy_reflect")] + pub fn register_type_conversion(&mut self, function: F) -> &mut Self + where + T: bevy_reflect::Reflect + bevy_reflect::TypePath, + U: bevy_reflect::Reflect + bevy_reflect::TypePath, + F: Fn(T) -> Result + Clone + Send + Sync + 'static, + { + let registry = self.world.resource_mut::(); + registry + .write() + .register_type_conversion::(function); + self + } + + /// See [`App::register_into_type_conversion`]. + #[cfg(feature = "bevy_reflect")] + pub fn register_into_type_conversion(&mut self) -> &mut Self + where + T: bevy_reflect::Reflect + bevy_reflect::TypePath, + U: bevy_reflect::Reflect + bevy_reflect::TypePath + From, + { + let registry = self.world.resource_mut::(); + registry.write().register_into_type_conversion::(); + self + } + /// See [`App::register_function`]. #[cfg(feature = "reflect_functions")] pub fn register_function(&mut self, function: F) -> &mut Self diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index e908cda11d290..3b28389982aee 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -208,6 +208,7 @@ impl FromTemplate for Handle { type Template = HandleTemplate; } +#[derive(Reflect)] pub enum HandleTemplate { Path(AssetPath<'static>), Handle(Handle), diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index dd636d46a2e84..d5f531fc5cb22 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -682,8 +682,11 @@ impl AssetApp for App { type_registry.register::(); type_registry.register::>(); + type_registry.register::>(); type_registry.register_type_data::(); type_registry.register_type_data::, ReflectHandle>(); + type_registry + .register_type_conversion::, _>(|s| Ok(s.into())); } self diff --git a/crates/bevy_reflect/src/convert.rs b/crates/bevy_reflect/src/convert.rs new file mode 100644 index 0000000000000..5fd9bcfdaa1ca --- /dev/null +++ b/crates/bevy_reflect/src/convert.rs @@ -0,0 +1,277 @@ +//! The [`ReflectConvert`] type, which allows types to register conversions to +//! and from one another. + +use alloc::boxed::Box; +use core::{any::TypeId, marker::PhantomData}; + +use bevy_utils::TypeIdMap; + +use crate::{Reflect, TypePath}; + +/// Provides a mechanism for converting values of one type to another. +/// +/// This [`crate::type_registry::TypeData`] is associated with the type to be +/// converted *into*, not the type to be converted *from*. To convert a value, +/// use code like the following: +/// +/// ```rust +/// # use bevy_reflect::{convert::ReflectConvert, GetTypeRegistration, TypeRegistry}; +/// # use std::any::TypeId; +/// # +/// # let mut registry = TypeRegistry::default(); +/// # registry.add_registration(i32::get_type_registration()); +/// # registry.add_registration(String::get_type_registration()); +/// # registry.register_type_conversion(|x: i32| Ok(x.to_string())); +/// +/// let reflect_convert = registry +/// .get_type_data::(TypeId::of::()) +/// .unwrap(); +/// let converted: String = *reflect_convert +/// .try_convert_from(Box::new(12345i32)) +/// .unwrap() +/// .downcast::() +/// .unwrap(); +/// ``` +#[derive(Default)] +pub struct ReflectConvert { + /// A mapping from the type to be converted *from* to its associated + /// [`Converter`]. + conversions: TypeIdMap>, +} + +/// An internal trait that wraps a conversion function in an untyped interface. +trait Converter: Send + Sync { + /// Converts the value to the appropriate type. + /// + /// This returns the converted value if the conversion succeeds or the + /// original value if the conversion fails. + fn convert(&self, input: Box) -> Result, Box>; + + /// Returns a new boxed instance wrapping the same [`Converter`]. + fn clone_converter(&self) -> Box; +} + +/// A wrapper that contains a conversion function and implements [`Converter`]. +struct TypedConverter +where + T: Reflect + TypePath, + U: Reflect + TypePath, + F: Fn(T) -> Result + Clone + Send + Sync + 'static, +{ + function: F, + phantom: PhantomData<(T, U)>, +} + +impl ReflectConvert { + /// Attempts to construct an instance of this type from the provided + /// `input`. + /// + /// If the conversion fails, either because no conversion has been + /// registered from the type of `input` or because the conversion function + /// itself returned `Err`, the `input` value is returned as an error. + pub fn try_convert_from( + &self, + input: Box, + ) -> Result, Box> { + let type_id = (*input.as_any()).type_id(); + match self.conversions.get(&type_id) { + Some(converter) => converter.convert(input), + None => Err(input), + } + } + + /// Adds a conversion function from the type `T` to this type. + /// + /// If the conversion succeeds, the function should return the converted + /// value. If the conversion fails, the function should return the original + /// input value. + pub fn register_type_conversion(&mut self, function: F) + where + T: Reflect + TypePath, + U: Reflect + TypePath, + F: Fn(T) -> Result + Clone + Send + Sync + 'static, + { + self.conversions.insert( + TypeId::of::(), + Box::new(TypedConverter { + function, + phantom: PhantomData, + }), + ); + } +} + +impl Clone for ReflectConvert { + fn clone(&self) -> Self { + ReflectConvert { + conversions: self + .conversions + .iter() + .map(|(type_id, converter)| (*type_id, converter.clone_converter())) + .collect(), + } + } +} + +impl Clone for TypedConverter +where + T: Reflect + TypePath, + U: Reflect + TypePath, + F: Fn(T) -> Result + Clone + Send + Sync + 'static, +{ + fn clone(&self) -> Self { + TypedConverter { + function: self.function.clone(), + phantom: PhantomData, + } + } +} + +impl Converter for TypedConverter +where + T: Reflect + TypePath, + U: Reflect + TypePath, + F: Fn(T) -> Result + Clone + Send + Sync + 'static, +{ + fn convert(&self, input: Box) -> Result, Box> { + let mut input = input.downcast::()?; + match (self.function)(*input) { + Ok(value) => Ok(Box::new(value)), + Err(value) => { + *input = value; + Err(input) + } + } + } + + fn clone_converter(&self) -> Box { + Box::new(self.clone()) + } +} + +#[cfg(test)] +mod tests { + use alloc::{ + borrow::ToOwned as _, + boxed::Box, + string::{String, ToString}, + }; + use core::any::TypeId; + + use crate::{convert::ReflectConvert, type_registry::GetTypeRegistration, TypeRegistry}; + + /// Tests that `i32` can be converted to `String` if the appropriate + /// conversion is registered. + #[test] + fn convert_from_i32_to_string() { + // Register the types and the conversion. + let mut registry = TypeRegistry::default(); + registry.add_registration(i32::get_type_registration()); + registry.add_registration(String::get_type_registration()); + registry.register_type_conversion(|x: i32| Ok(x.to_string())); + + let reflect_convert = registry + .get_type_data::(TypeId::of::()) + .unwrap(); + + // Test that a successful conversion works. + let converted = reflect_convert + .try_convert_from(Box::new(12345i32)) + .unwrap() + .downcast::() + .unwrap(); + assert_eq!(&**converted, "12345"); + } + + /// Tests that `String` can be fallibly converted to `i32` if the + /// appropriate conversion is registered. + /// + /// This also tests that the behavior of returning the original string on + /// error is correct. + #[test] + fn convert_from_string_to_i32() { + // Register the types and the conversion. + let mut registry = TypeRegistry::default(); + registry.add_registration(i32::get_type_registration()); + registry.add_registration(String::get_type_registration()); + registry.register_type_conversion(|x: String| match x.parse::() { + Ok(value) => Ok(value), + Err(_) => Err(x), + }); + + let reflect_convert = registry + .get_type_data::(TypeId::of::()) + .unwrap(); + + // Test a successful conversion from string to integer. + let converted = reflect_convert + .try_convert_from(Box::new("12345".to_owned())) + .unwrap() + .downcast::() + .unwrap(); + assert_eq!(*converted, 12345); + + // Test an unsuccessful conversion from string to integer. + let error = reflect_convert + .try_convert_from(Box::new("qqqqq".to_owned())) + .unwrap_err() + .downcast::() + .unwrap(); + assert_eq!(&**error, "qqqqq"); + } + + /// Tests that we can register multiple conversions into the same type and + /// that they all work. + #[test] + fn convert_from_f32_and_u32_to_i32() { + let mut registry = TypeRegistry::default(); + registry.add_registration(i32::get_type_registration()); + registry.add_registration(f32::get_type_registration()); + registry.add_registration(u32::get_type_registration()); + registry.register_type_conversion::(|n: u32| n.try_into().map_err(|_| n)); + registry.register_type_conversion::(|n: f32| Ok(n as i32)); + + let reflect_convert = registry + .get_type_data::(TypeId::of::()) + .unwrap(); + + // Test that we can convert `u32` and `f32` into `i32`. + let a = reflect_convert + .try_convert_from(Box::new(99u32)) + .unwrap() + .downcast::() + .unwrap(); + assert_eq!(*a, 99i32); + let b = reflect_convert + .try_convert_from(Box::new(99.0f32)) + .unwrap() + .downcast::() + .unwrap(); + assert_eq!(*b, 99i32); + } + + /// Tests that the error-handling behavior is correct when attempting a + /// conversion that hasn't been registered. + #[test] + fn no_such_conversion() { + let mut registry = TypeRegistry::default(); + registry.add_registration(i32::get_type_registration()); + registry.add_registration(String::get_type_registration()); + registry + .get_mut(TypeId::of::()) + .unwrap() + .insert(ReflectConvert::default()); + + let reflect_convert = registry + .get_type_data::(TypeId::of::()) + .unwrap(); + + // Test that we get the original value back on error. + let error = reflect_convert + .try_convert_from(Box::new("12345".to_owned())) + .unwrap_err() + .downcast::() + .unwrap(); + assert_eq!(&**error, "12345"); + } +} diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index cf0ac38354351..56799242b5d3f 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -645,6 +645,7 @@ mod impls { } pub mod attributes; +pub mod convert; pub mod enums; mod generics; pub mod serde; diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs index 8ccd81ce710f7..55e137ae74768 100644 --- a/crates/bevy_reflect/src/type_registry.rs +++ b/crates/bevy_reflect/src/type_registry.rs @@ -1,4 +1,6 @@ -use crate::{serde::Serializable, FromReflect, Reflect, TypeInfo, TypePath, Typed}; +use crate::{ + convert::ReflectConvert, serde::Serializable, FromReflect, Reflect, TypeInfo, TypePath, Typed, +}; use alloc::{boxed::Box, string::String}; use bevy_platform::{ collections::{HashMap, HashSet}, @@ -353,6 +355,65 @@ impl TypeRegistry { data.insert(D::from_type()); } + /// Registers a fallible conversion from type T to U with the reflection + /// system. + /// + /// The supplied closure is expected to produce a value of type U, given an + /// instance of type T. If the conversion fails, the closure should return + /// the input value, wrapped in an `Err` variant. + /// + /// # Example + /// ``` + /// # use bevy_reflect::TypeRegistry; + /// + /// let mut type_registry = TypeRegistry::default(); + /// type_registry.register::(); + /// type_registry.register::(); + /// type_registry.register_type_conversion::(|n| Ok(n.to_string())); + /// ``` + pub fn register_type_conversion(&mut self, function: F) + where + T: Reflect + TypePath, + U: Reflect + TypePath, + F: Fn(T) -> Result + Clone + Send + Sync + 'static, + { + let data = self.get_mut(TypeId::of::()).unwrap_or_else(|| { + panic!( + "attempted to call `TypeRegistry::register_type_conversion` for type `{U}` without registering `{U}` first", + U = U::type_path(), + ) + }); + data.get_or_insert_data_with(ReflectConvert::default) + .register_type_conversion(function); + } + + /// Given types T and U, where `U: From`, registers that conversion with + /// the reflection system. + /// + /// # Example + /// ``` + /// # use bevy_reflect::TypeRegistry; + /// + /// let mut type_registry = TypeRegistry::default(); + /// type_registry.register::(); + /// type_registry.register::(); + /// type_registry.register_type_conversion::(|n| Ok(n.into())); + /// ``` + pub fn register_into_type_conversion(&mut self) + where + T: Reflect + TypePath, + U: Reflect + TypePath + From, + { + let data = self.get_mut(TypeId::of::()).unwrap_or_else(|| { + panic!( + "attempted to call `TypeRegistry::register_type_conversion` for type `{U}` without registering `{U}` first", + U = U::type_path(), + ) + }); + data.get_or_insert_data_with(ReflectConvert::default) + .register_type_conversion::(|input| Ok(input.into())); + } + /// Whether the type with given [`TypeId`] has been registered in this registry. pub fn contains(&self, type_id: TypeId) -> bool { self.registrations.contains_key(&type_id) @@ -588,6 +649,18 @@ impl TypeRegistration { self.data.insert(TypeId::of::(), Box::new(data)); } + /// Gets the instance of `T` into this registration's [type data], if it exists. If it does not + /// exist, it will insert a new instance using `get_data` and then return it. + /// + /// [type data]: TypeData + pub fn get_or_insert_data_with(&mut self, get_data: impl FnOnce() -> T) -> &mut T { + let boxed_data = self + .data + .entry(TypeId::of::()) + .or_insert_with(|| Box::new(get_data())); + boxed_data.downcast_mut::().unwrap() + } + /// Inserts the [`TypeData`] instance of `T` created for `V`, and inserts any /// [`TypeData`] dependencies for that combination of `T` and `V`. #[inline]