Skip to content
Merged
53 changes: 53 additions & 0 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<i32>()
/// .register_type::<String>()
/// .register_type_conversion::<i32, String, _>(|n| Ok(n.to_string()));
/// ```
///
/// See [`bevy_reflect::TypeRegistry::register_type_conversion`].
#[cfg(feature = "bevy_reflect")]
pub fn register_type_conversion<T, U, F>(&mut self, function: F) -> &mut Self
where
T: Reflect + TypePath,
U: Reflect + TypePath,
F: Fn(T) -> Result<U, T> + Clone + Send + Sync + 'static,
{
self.main_mut().register_type_conversion(function);
self
}

/// Given types T and U, where `U: From<T>`, registers that conversion with
/// the reflection system.
///
/// # Example
/// ```
/// use bevy_app::App;
///
/// App::new()
/// .register_type::<u8>()
/// .register_type::<u32>()
/// .register_into_type_conversion::<u8, u32>();
/// ```
///
/// See [`bevy_reflect::TypeRegistry::register_into_type_conversion`].
#[cfg(feature = "bevy_reflect")]
pub fn register_into_type_conversion<T, U>(&mut self) -> &mut Self
where
T: Reflect + TypePath,
U: Reflect + TypePath + From<T>,
{
self.main_mut().register_into_type_conversion::<T, U>();
self
}

/// Registers the given function into the [`AppFunctionRegistry`] resource.
///
/// The given function will internally be stored as a [`DynamicFunction`]
Expand Down
27 changes: 27 additions & 0 deletions crates/bevy_app/src/sub_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,33 @@ impl SubApp {
self
}

/// See [`App::register_type_conversion`].
#[cfg(feature = "bevy_reflect")]
pub fn register_type_conversion<T, U, F>(&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<U, T> + Clone + Send + Sync + 'static,
{
let registry = self.world.resource_mut::<AppTypeRegistry>();
registry
.write()
.register_type_conversion::<T, U, _>(function);
self
}

/// See [`App::register_into_type_conversion`].
#[cfg(feature = "bevy_reflect")]
pub fn register_into_type_conversion<T, U>(&mut self) -> &mut Self
where
T: bevy_reflect::Reflect + bevy_reflect::TypePath,
U: bevy_reflect::Reflect + bevy_reflect::TypePath + From<T>,
{
let registry = self.world.resource_mut::<AppTypeRegistry>();
registry.write().register_into_type_conversion::<T, U>();
self
}

/// See [`App::register_function`].
#[cfg(feature = "reflect_functions")]
pub fn register_function<F, Marker>(&mut self, function: F) -> &mut Self
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_asset/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ impl<T: Asset> FromTemplate for Handle<T> {
type Template = HandleTemplate<T>;
}

#[derive(Reflect)]
pub enum HandleTemplate<T: Asset> {
Path(AssetPath<'static>),
Handle(Handle<T>),
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,8 +682,11 @@ impl AssetApp for App {

type_registry.register::<A>();
type_registry.register::<Handle<A>>();
type_registry.register::<HandleTemplate<A>>();
type_registry.register_type_data::<A, ReflectAsset>();
type_registry.register_type_data::<Handle<A>, ReflectHandle>();
type_registry
.register_type_conversion::<String, HandleTemplate<A>, _>(|s| Ok(s.into()));
}

self
Expand Down
277 changes: 277 additions & 0 deletions crates/bevy_reflect/src/convert.rs
Original file line number Diff line number Diff line change
@@ -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::<ReflectConvert>(TypeId::of::<String>())
/// .unwrap();
/// let converted: String = *reflect_convert
/// .try_convert_from(Box::new(12345i32))
/// .unwrap()
/// .downcast::<String>()
/// .unwrap();
/// ```
#[derive(Default)]
pub struct ReflectConvert {
/// A mapping from the type to be converted *from* to its associated
/// [`Converter`].
conversions: TypeIdMap<Box<dyn Converter>>,
}

/// 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<dyn Reflect>) -> Result<Box<dyn Reflect>, Box<dyn Reflect>>;

/// Returns a new boxed instance wrapping the same [`Converter`].
fn clone_converter(&self) -> Box<dyn Converter>;
}

/// A wrapper that contains a conversion function and implements [`Converter`].
struct TypedConverter<T, U, F>
where
T: Reflect + TypePath,
U: Reflect + TypePath,
F: Fn(T) -> Result<U, T> + 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<dyn Reflect>,
) -> Result<Box<dyn Reflect>, Box<dyn Reflect>> {
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<T, U, F>(&mut self, function: F)
where
T: Reflect + TypePath,
U: Reflect + TypePath,
F: Fn(T) -> Result<U, T> + Clone + Send + Sync + 'static,
{
self.conversions.insert(
TypeId::of::<T>(),
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<T, U, F> Clone for TypedConverter<T, U, F>
where
T: Reflect + TypePath,
U: Reflect + TypePath,
F: Fn(T) -> Result<U, T> + Clone + Send + Sync + 'static,
{
fn clone(&self) -> Self {
TypedConverter {
function: self.function.clone(),
phantom: PhantomData,
}
}
}

impl<T, U, F> Converter for TypedConverter<T, U, F>
where
T: Reflect + TypePath,
U: Reflect + TypePath,
F: Fn(T) -> Result<U, T> + Clone + Send + Sync + 'static,
{
fn convert(&self, input: Box<dyn Reflect>) -> Result<Box<dyn Reflect>, Box<dyn Reflect>> {
let mut input = input.downcast::<T>()?;
match (self.function)(*input) {
Ok(value) => Ok(Box::new(value)),
Err(value) => {
*input = value;
Err(input)
}
}
}

fn clone_converter(&self) -> Box<dyn Converter> {
Box::new(self.clone())
}
}

#[cfg(test)]
mod tests {
Comment thread
alice-i-cecile marked this conversation as resolved.
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::<ReflectConvert>(TypeId::of::<String>())
.unwrap();

// Test that a successful conversion works.
let converted = reflect_convert
.try_convert_from(Box::new(12345i32))
.unwrap()
.downcast::<String>()
.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::<i32>() {
Ok(value) => Ok(value),
Err(_) => Err(x),
});

let reflect_convert = registry
.get_type_data::<ReflectConvert>(TypeId::of::<i32>())
.unwrap();

// Test a successful conversion from string to integer.
let converted = reflect_convert
.try_convert_from(Box::new("12345".to_owned()))
.unwrap()
.downcast::<i32>()
.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::<String>()
.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::<u32, i32, _>(|n: u32| n.try_into().map_err(|_| n));
registry.register_type_conversion::<f32, i32, _>(|n: f32| Ok(n as i32));

let reflect_convert = registry
.get_type_data::<ReflectConvert>(TypeId::of::<i32>())
.unwrap();

// Test that we can convert `u32` and `f32` into `i32`.
let a = reflect_convert
.try_convert_from(Box::new(99u32))
.unwrap()
.downcast::<i32>()
.unwrap();
assert_eq!(*a, 99i32);
let b = reflect_convert
.try_convert_from(Box::new(99.0f32))
.unwrap()
.downcast::<i32>()
.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::<i32>())
.unwrap()
.insert(ReflectConvert::default());

let reflect_convert = registry
.get_type_data::<ReflectConvert>(TypeId::of::<i32>())
.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::<String>()
.unwrap();
assert_eq!(&**error, "12345");
}
}
1 change: 1 addition & 0 deletions crates/bevy_reflect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,7 @@ mod impls {
}

pub mod attributes;
pub mod convert;
pub mod enums;
mod generics;
pub mod serde;
Expand Down
Loading