Skip to content

Commit 02aef14

Browse files
pcwaltoncart
andauthored
Introduce ReflectConvert, a generic reflection mechanism for type conversions, intended for asset BSN. (#23742)
One of the important features of BSN is that supplying a value of type T where a value of type U is expected is allowed if a `From` conversion exists from T to U. This is what allows `HandleTemplate`s to be automatically created from string literals. We need this feature for BSN like this to be valid: ```rust SceneRoot("models/FlightHelmet/FlightHelmet.gltf#Scene0") ``` And for this to be valid: ```rust EnvironmentMapLight { diffuse_map: "environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2", specular_map: "environment_maps/pisa_specular_rgb9e5_zstd.ktx2", intensity: 250.0, } ``` Unfortunately, the `From` trait isn't reflectable, and this is a problem as reflection drives asset BSN. It's not immediately clear how to make `From` reflectable either, as a syntax like `#[reflect(From)]` wouldn't work as it's a generic trait. Instead of making `From` directly reflectable, this patch introduces `ReflectConvert`, a new `TypeData` that encodes a generic way to convert a value of type T to type U. You register a type conversion like this: ```rust App::new() .register_type::<i32>() .register_type::<String>() .register_type_conversion::<i32, String>(|n| Ok(n.into())); ``` And you invoke the conversion like this: ```rust let reflect_convert = registry .get_type_data::<ReflectConvert>(TypeId::of::<String>()) .unwrap(); let converted = reflect_convert .try_convert_from(Box::new(12345i32)) .unwrap() .downcast::<String>() .unwrap(); ``` I tested `ReflectConvert` with asset BSN (PR #23576). The `ReflectConvert` trait as implemented in this patch successfully enables `HandleTemplate`s to be created from strings, without hard-coding `HandleTemplate` anywhere in the patch generation logic. --------- Co-authored-by: Carter Anderson <mcanders1@gmail.com>
1 parent 082c44e commit 02aef14

7 files changed

Lines changed: 436 additions & 1 deletion

File tree

crates/bevy_app/src/app.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,59 @@ impl App {
694694
self
695695
}
696696

697+
/// Registers a fallible conversion from type T to U with the reflection
698+
/// system.
699+
///
700+
/// The supplied closure is expected to produce a value of type U, given an
701+
/// instance of type T. If the conversion fails, the closure should return
702+
/// the input value, wrapped in an `Err` variant.
703+
///
704+
/// # Example
705+
/// ```
706+
/// use bevy_app::App;
707+
///
708+
/// App::new()
709+
/// .register_type::<i32>()
710+
/// .register_type::<String>()
711+
/// .register_type_conversion::<i32, String, _>(|n| Ok(n.to_string()));
712+
/// ```
713+
///
714+
/// See [`bevy_reflect::TypeRegistry::register_type_conversion`].
715+
#[cfg(feature = "bevy_reflect")]
716+
pub fn register_type_conversion<T, U, F>(&mut self, function: F) -> &mut Self
717+
where
718+
T: Reflect + TypePath,
719+
U: Reflect + TypePath,
720+
F: Fn(T) -> Result<U, T> + Clone + Send + Sync + 'static,
721+
{
722+
self.main_mut().register_type_conversion(function);
723+
self
724+
}
725+
726+
/// Given types T and U, where `U: From<T>`, registers that conversion with
727+
/// the reflection system.
728+
///
729+
/// # Example
730+
/// ```
731+
/// use bevy_app::App;
732+
///
733+
/// App::new()
734+
/// .register_type::<u8>()
735+
/// .register_type::<u32>()
736+
/// .register_into_type_conversion::<u8, u32>();
737+
/// ```
738+
///
739+
/// See [`bevy_reflect::TypeRegistry::register_into_type_conversion`].
740+
#[cfg(feature = "bevy_reflect")]
741+
pub fn register_into_type_conversion<T, U>(&mut self) -> &mut Self
742+
where
743+
T: Reflect + TypePath,
744+
U: Reflect + TypePath + From<T>,
745+
{
746+
self.main_mut().register_into_type_conversion::<T, U>();
747+
self
748+
}
749+
697750
/// Registers the given function into the [`AppFunctionRegistry`] resource.
698751
///
699752
/// The given function will internally be stored as a [`DynamicFunction`]

crates/bevy_app/src/sub_app.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,33 @@ impl SubApp {
485485
self
486486
}
487487

488+
/// See [`App::register_type_conversion`].
489+
#[cfg(feature = "bevy_reflect")]
490+
pub fn register_type_conversion<T, U, F>(&mut self, function: F) -> &mut Self
491+
where
492+
T: bevy_reflect::Reflect + bevy_reflect::TypePath,
493+
U: bevy_reflect::Reflect + bevy_reflect::TypePath,
494+
F: Fn(T) -> Result<U, T> + Clone + Send + Sync + 'static,
495+
{
496+
let registry = self.world.resource_mut::<AppTypeRegistry>();
497+
registry
498+
.write()
499+
.register_type_conversion::<T, U, _>(function);
500+
self
501+
}
502+
503+
/// See [`App::register_into_type_conversion`].
504+
#[cfg(feature = "bevy_reflect")]
505+
pub fn register_into_type_conversion<T, U>(&mut self) -> &mut Self
506+
where
507+
T: bevy_reflect::Reflect + bevy_reflect::TypePath,
508+
U: bevy_reflect::Reflect + bevy_reflect::TypePath + From<T>,
509+
{
510+
let registry = self.world.resource_mut::<AppTypeRegistry>();
511+
registry.write().register_into_type_conversion::<T, U>();
512+
self
513+
}
514+
488515
/// See [`App::register_function`].
489516
#[cfg(feature = "reflect_functions")]
490517
pub fn register_function<F, Marker>(&mut self, function: F) -> &mut Self

crates/bevy_asset/src/handle.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ impl<T: Asset> FromTemplate for Handle<T> {
208208
type Template = HandleTemplate<T>;
209209
}
210210

211+
#[derive(Reflect)]
211212
pub enum HandleTemplate<T: Asset> {
212213
Path(AssetPath<'static>),
213214
Handle(Handle<T>),

crates/bevy_asset/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,8 +682,11 @@ impl AssetApp for App {
682682

683683
type_registry.register::<A>();
684684
type_registry.register::<Handle<A>>();
685+
type_registry.register::<HandleTemplate<A>>();
685686
type_registry.register_type_data::<A, ReflectAsset>();
686687
type_registry.register_type_data::<Handle<A>, ReflectHandle>();
688+
type_registry
689+
.register_type_conversion::<String, HandleTemplate<A>, _>(|s| Ok(s.into()));
687690
}
688691

689692
self

crates/bevy_reflect/src/convert.rs

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
//! The [`ReflectConvert`] type, which allows types to register conversions to
2+
//! and from one another.
3+
4+
use alloc::boxed::Box;
5+
use core::{any::TypeId, marker::PhantomData};
6+
7+
use bevy_utils::TypeIdMap;
8+
9+
use crate::{Reflect, TypePath};
10+
11+
/// Provides a mechanism for converting values of one type to another.
12+
///
13+
/// This [`crate::type_registry::TypeData`] is associated with the type to be
14+
/// converted *into*, not the type to be converted *from*. To convert a value,
15+
/// use code like the following:
16+
///
17+
/// ```rust
18+
/// # use bevy_reflect::{convert::ReflectConvert, GetTypeRegistration, TypeRegistry};
19+
/// # use std::any::TypeId;
20+
/// #
21+
/// # let mut registry = TypeRegistry::default();
22+
/// # registry.add_registration(i32::get_type_registration());
23+
/// # registry.add_registration(String::get_type_registration());
24+
/// # registry.register_type_conversion(|x: i32| Ok(x.to_string()));
25+
///
26+
/// let reflect_convert = registry
27+
/// .get_type_data::<ReflectConvert>(TypeId::of::<String>())
28+
/// .unwrap();
29+
/// let converted: String = *reflect_convert
30+
/// .try_convert_from(Box::new(12345i32))
31+
/// .unwrap()
32+
/// .downcast::<String>()
33+
/// .unwrap();
34+
/// ```
35+
#[derive(Default)]
36+
pub struct ReflectConvert {
37+
/// A mapping from the type to be converted *from* to its associated
38+
/// [`Converter`].
39+
conversions: TypeIdMap<Box<dyn Converter>>,
40+
}
41+
42+
/// An internal trait that wraps a conversion function in an untyped interface.
43+
trait Converter: Send + Sync {
44+
/// Converts the value to the appropriate type.
45+
///
46+
/// This returns the converted value if the conversion succeeds or the
47+
/// original value if the conversion fails.
48+
fn convert(&self, input: Box<dyn Reflect>) -> Result<Box<dyn Reflect>, Box<dyn Reflect>>;
49+
50+
/// Returns a new boxed instance wrapping the same [`Converter`].
51+
fn clone_converter(&self) -> Box<dyn Converter>;
52+
}
53+
54+
/// A wrapper that contains a conversion function and implements [`Converter`].
55+
struct TypedConverter<T, U, F>
56+
where
57+
T: Reflect + TypePath,
58+
U: Reflect + TypePath,
59+
F: Fn(T) -> Result<U, T> + Clone + Send + Sync + 'static,
60+
{
61+
function: F,
62+
phantom: PhantomData<(T, U)>,
63+
}
64+
65+
impl ReflectConvert {
66+
/// Attempts to construct an instance of this type from the provided
67+
/// `input`.
68+
///
69+
/// If the conversion fails, either because no conversion has been
70+
/// registered from the type of `input` or because the conversion function
71+
/// itself returned `Err`, the `input` value is returned as an error.
72+
pub fn try_convert_from(
73+
&self,
74+
input: Box<dyn Reflect>,
75+
) -> Result<Box<dyn Reflect>, Box<dyn Reflect>> {
76+
let type_id = (*input.as_any()).type_id();
77+
match self.conversions.get(&type_id) {
78+
Some(converter) => converter.convert(input),
79+
None => Err(input),
80+
}
81+
}
82+
83+
/// Adds a conversion function from the type `T` to this type.
84+
///
85+
/// If the conversion succeeds, the function should return the converted
86+
/// value. If the conversion fails, the function should return the original
87+
/// input value.
88+
pub fn register_type_conversion<T, U, F>(&mut self, function: F)
89+
where
90+
T: Reflect + TypePath,
91+
U: Reflect + TypePath,
92+
F: Fn(T) -> Result<U, T> + Clone + Send + Sync + 'static,
93+
{
94+
self.conversions.insert(
95+
TypeId::of::<T>(),
96+
Box::new(TypedConverter {
97+
function,
98+
phantom: PhantomData,
99+
}),
100+
);
101+
}
102+
}
103+
104+
impl Clone for ReflectConvert {
105+
fn clone(&self) -> Self {
106+
ReflectConvert {
107+
conversions: self
108+
.conversions
109+
.iter()
110+
.map(|(type_id, converter)| (*type_id, converter.clone_converter()))
111+
.collect(),
112+
}
113+
}
114+
}
115+
116+
impl<T, U, F> Clone for TypedConverter<T, U, F>
117+
where
118+
T: Reflect + TypePath,
119+
U: Reflect + TypePath,
120+
F: Fn(T) -> Result<U, T> + Clone + Send + Sync + 'static,
121+
{
122+
fn clone(&self) -> Self {
123+
TypedConverter {
124+
function: self.function.clone(),
125+
phantom: PhantomData,
126+
}
127+
}
128+
}
129+
130+
impl<T, U, F> Converter for TypedConverter<T, U, F>
131+
where
132+
T: Reflect + TypePath,
133+
U: Reflect + TypePath,
134+
F: Fn(T) -> Result<U, T> + Clone + Send + Sync + 'static,
135+
{
136+
fn convert(&self, input: Box<dyn Reflect>) -> Result<Box<dyn Reflect>, Box<dyn Reflect>> {
137+
let mut input = input.downcast::<T>()?;
138+
match (self.function)(*input) {
139+
Ok(value) => Ok(Box::new(value)),
140+
Err(value) => {
141+
*input = value;
142+
Err(input)
143+
}
144+
}
145+
}
146+
147+
fn clone_converter(&self) -> Box<dyn Converter> {
148+
Box::new(self.clone())
149+
}
150+
}
151+
152+
#[cfg(test)]
153+
mod tests {
154+
use alloc::{
155+
borrow::ToOwned as _,
156+
boxed::Box,
157+
string::{String, ToString},
158+
};
159+
use core::any::TypeId;
160+
161+
use crate::{convert::ReflectConvert, type_registry::GetTypeRegistration, TypeRegistry};
162+
163+
/// Tests that `i32` can be converted to `String` if the appropriate
164+
/// conversion is registered.
165+
#[test]
166+
fn convert_from_i32_to_string() {
167+
// Register the types and the conversion.
168+
let mut registry = TypeRegistry::default();
169+
registry.add_registration(i32::get_type_registration());
170+
registry.add_registration(String::get_type_registration());
171+
registry.register_type_conversion(|x: i32| Ok(x.to_string()));
172+
173+
let reflect_convert = registry
174+
.get_type_data::<ReflectConvert>(TypeId::of::<String>())
175+
.unwrap();
176+
177+
// Test that a successful conversion works.
178+
let converted = reflect_convert
179+
.try_convert_from(Box::new(12345i32))
180+
.unwrap()
181+
.downcast::<String>()
182+
.unwrap();
183+
assert_eq!(&**converted, "12345");
184+
}
185+
186+
/// Tests that `String` can be fallibly converted to `i32` if the
187+
/// appropriate conversion is registered.
188+
///
189+
/// This also tests that the behavior of returning the original string on
190+
/// error is correct.
191+
#[test]
192+
fn convert_from_string_to_i32() {
193+
// Register the types and the conversion.
194+
let mut registry = TypeRegistry::default();
195+
registry.add_registration(i32::get_type_registration());
196+
registry.add_registration(String::get_type_registration());
197+
registry.register_type_conversion(|x: String| match x.parse::<i32>() {
198+
Ok(value) => Ok(value),
199+
Err(_) => Err(x),
200+
});
201+
202+
let reflect_convert = registry
203+
.get_type_data::<ReflectConvert>(TypeId::of::<i32>())
204+
.unwrap();
205+
206+
// Test a successful conversion from string to integer.
207+
let converted = reflect_convert
208+
.try_convert_from(Box::new("12345".to_owned()))
209+
.unwrap()
210+
.downcast::<i32>()
211+
.unwrap();
212+
assert_eq!(*converted, 12345);
213+
214+
// Test an unsuccessful conversion from string to integer.
215+
let error = reflect_convert
216+
.try_convert_from(Box::new("qqqqq".to_owned()))
217+
.unwrap_err()
218+
.downcast::<String>()
219+
.unwrap();
220+
assert_eq!(&**error, "qqqqq");
221+
}
222+
223+
/// Tests that we can register multiple conversions into the same type and
224+
/// that they all work.
225+
#[test]
226+
fn convert_from_f32_and_u32_to_i32() {
227+
let mut registry = TypeRegistry::default();
228+
registry.add_registration(i32::get_type_registration());
229+
registry.add_registration(f32::get_type_registration());
230+
registry.add_registration(u32::get_type_registration());
231+
registry.register_type_conversion::<u32, i32, _>(|n: u32| n.try_into().map_err(|_| n));
232+
registry.register_type_conversion::<f32, i32, _>(|n: f32| Ok(n as i32));
233+
234+
let reflect_convert = registry
235+
.get_type_data::<ReflectConvert>(TypeId::of::<i32>())
236+
.unwrap();
237+
238+
// Test that we can convert `u32` and `f32` into `i32`.
239+
let a = reflect_convert
240+
.try_convert_from(Box::new(99u32))
241+
.unwrap()
242+
.downcast::<i32>()
243+
.unwrap();
244+
assert_eq!(*a, 99i32);
245+
let b = reflect_convert
246+
.try_convert_from(Box::new(99.0f32))
247+
.unwrap()
248+
.downcast::<i32>()
249+
.unwrap();
250+
assert_eq!(*b, 99i32);
251+
}
252+
253+
/// Tests that the error-handling behavior is correct when attempting a
254+
/// conversion that hasn't been registered.
255+
#[test]
256+
fn no_such_conversion() {
257+
let mut registry = TypeRegistry::default();
258+
registry.add_registration(i32::get_type_registration());
259+
registry.add_registration(String::get_type_registration());
260+
registry
261+
.get_mut(TypeId::of::<i32>())
262+
.unwrap()
263+
.insert(ReflectConvert::default());
264+
265+
let reflect_convert = registry
266+
.get_type_data::<ReflectConvert>(TypeId::of::<i32>())
267+
.unwrap();
268+
269+
// Test that we get the original value back on error.
270+
let error = reflect_convert
271+
.try_convert_from(Box::new("12345".to_owned()))
272+
.unwrap_err()
273+
.downcast::<String>()
274+
.unwrap();
275+
assert_eq!(&**error, "12345");
276+
}
277+
}

crates/bevy_reflect/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ mod impls {
645645
}
646646

647647
pub mod attributes;
648+
pub mod convert;
648649
pub mod enums;
649650
mod generics;
650651
pub mod serde;

0 commit comments

Comments
 (0)