bevy_reflect: Add ReflectFromReflect (v2) (#6245)
# Objective Resolves #4597 (based on the work from #6056 and a refresh of #4147) When using reflection, we may often end up in a scenario where we have a Dynamic representing a certain type. Unfortunately, we can't just call `MyType::from_reflect` as we do not have knowledge of the concrete type (`MyType`) at runtime. Such scenarios happen when we call `Reflect::clone_value`, use the reflection deserializers, or create the Dynamic type ourselves. ## Solution Add a `ReflectFromReflect` type data struct. This struct allows us to easily convert Dynamic representations of our types into their respective concrete instances. ```rust #[derive(Reflect, FromReflect)] #[reflect(FromReflect)] // <- Register `ReflectFromReflect` struct MyStruct(String); let type_id = TypeId::of::<MyStruct>(); // Register our type let mut registry = TypeRegistry::default(); registry.register::<MyStruct>(); // Create a concrete instance let my_struct = MyStruct("Hello world".to_string()); // `Reflect::clone_value` will generate a `DynamicTupleStruct` for tuple struct types let dynamic_value: Box<dyn Reflect> = my_struct.clone_value(); assert!(!dynamic_value.is::<MyStruct>()); // Get the `ReflectFromReflect` type data from the registry let rfr: &ReflectFromReflect = registry .get_type_data::<ReflectFromReflect>(type_id) .unwrap(); // Call `FromReflect::from_reflect` on our Dynamic value let concrete_value: Box<dyn Reflect> = rfr.from_reflect(&dynamic_value); assert!(concrete_value.is::<MyStruct>()); ``` ### Why this PR? ###### Why now? The three main reasons I closed #4147 were that: 1. Registering `ReflectFromReflect` is clunky (deriving `FromReflect` *and* registering `ReflectFromReflect`) 2. The ecosystem and Bevy itself didn't seem to pay much attention to deriving `FromReflect` 3. I didn't see a lot of desire from the community for such a feature However, as time has passed it seems 2 and 3 are not really true anymore. Bevy is internally adding lots more `FromReflect` derives, which should make this feature all the more useful. Additionally, I have seen a growing number of people look for something like `ReflectFromReflect`. I think 1 is still an issue, but not a horrible one. Plus it could be made much, much better using #6056. And I think splitting this feature out of #6056 could lead to #6056 being adopted sooner (or at least make the need more clear to users). ###### Why not just re-open #4147? The main reason is so that this PR can garner more attention than simply re-opening the old one. This helps bring fresh eyes to the PR for potentially more perspectives/reviews. --- ## Changelog * Added `ReflectFromReflect` Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
This commit is contained in:
parent
6308041772
commit
63f1a9dec8
92
crates/bevy_reflect/src/from_reflect.rs
Normal file
92
crates/bevy_reflect/src/from_reflect.rs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
use crate::{FromType, Reflect};
|
||||||
|
|
||||||
|
/// A trait for types which can be constructed from a reflected type.
|
||||||
|
///
|
||||||
|
/// This trait can be derived on types which implement [`Reflect`]. Some complex
|
||||||
|
/// types (such as `Vec<T>`) may only be reflected if their element types
|
||||||
|
/// implement this trait.
|
||||||
|
///
|
||||||
|
/// For structs and tuple structs, fields marked with the `#[reflect(ignore)]`
|
||||||
|
/// attribute will be constructed using the `Default` implementation of the
|
||||||
|
/// field type, rather than the corresponding field value (if any) of the
|
||||||
|
/// reflected value.
|
||||||
|
pub trait FromReflect: Reflect + Sized {
|
||||||
|
/// Constructs a concrete instance of `Self` from a reflected value.
|
||||||
|
fn from_reflect(reflect: &dyn Reflect) -> Option<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type data that represents the [`FromReflect`] trait and allows it to be used dynamically.
|
||||||
|
///
|
||||||
|
/// `FromReflect` allows dynamic types (e.g. [`DynamicStruct`], [`DynamicEnum`], etc.) to be converted
|
||||||
|
/// to their full, concrete types. This is most important when it comes to deserialization where it isn't
|
||||||
|
/// guaranteed that every field exists when trying to construct the final output.
|
||||||
|
///
|
||||||
|
/// However, to do this, you normally need to specify the exact concrete type:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_reflect::{DynamicTupleStruct, FromReflect, Reflect};
|
||||||
|
/// #[derive(Reflect, FromReflect, PartialEq, Eq, Debug)]
|
||||||
|
/// struct Foo(#[reflect(default = "default_value")] usize);
|
||||||
|
///
|
||||||
|
/// fn default_value() -> usize { 123 }
|
||||||
|
///
|
||||||
|
/// let reflected = DynamicTupleStruct::default();
|
||||||
|
///
|
||||||
|
/// let concrete: Foo = <Foo as FromReflect>::from_reflect(&reflected).unwrap();
|
||||||
|
///
|
||||||
|
/// assert_eq!(Foo(123), concrete);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// In a dynamic context where the type might not be known at compile-time, this is nearly impossible to do.
|
||||||
|
/// That is why this type data struct exists— it allows us to construct the full type without knowing
|
||||||
|
/// what the actual type is.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_reflect::{DynamicTupleStruct, FromReflect, Reflect, ReflectFromReflect, TypeRegistry};
|
||||||
|
/// # #[derive(Reflect, FromReflect, PartialEq, Eq, Debug)]
|
||||||
|
/// # #[reflect(FromReflect)]
|
||||||
|
/// # struct Foo(#[reflect(default = "default_value")] usize);
|
||||||
|
/// # fn default_value() -> usize { 123 }
|
||||||
|
/// # let mut registry = TypeRegistry::new();
|
||||||
|
/// # registry.register::<Foo>();
|
||||||
|
///
|
||||||
|
/// let mut reflected = DynamicTupleStruct::default();
|
||||||
|
/// reflected.set_name(std::any::type_name::<Foo>().to_string());
|
||||||
|
///
|
||||||
|
/// let registration = registry.get_with_name(reflected.type_name()).unwrap();
|
||||||
|
/// let rfr = registration.data::<ReflectFromReflect>().unwrap();
|
||||||
|
///
|
||||||
|
/// let concrete: Box<dyn Reflect> = rfr.from_reflect(&reflected).unwrap();
|
||||||
|
///
|
||||||
|
/// assert_eq!(Foo(123), concrete.take::<Foo>().unwrap());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`DynamicStruct`]: crate::DynamicStruct
|
||||||
|
/// [`DynamicEnum`]: crate::DynamicEnum
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ReflectFromReflect {
|
||||||
|
from_reflect: fn(&dyn Reflect) -> Option<Box<dyn Reflect>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReflectFromReflect {
|
||||||
|
/// Perform a [`FromReflect::from_reflect`] conversion on the given reflection object.
|
||||||
|
///
|
||||||
|
/// This will convert the object to a concrete type if it wasn't already, and return
|
||||||
|
/// the value as `Box<dyn Reflect>`.
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
|
pub fn from_reflect(&self, reflect_value: &dyn Reflect) -> Option<Box<dyn Reflect>> {
|
||||||
|
(self.from_reflect)(reflect_value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: FromReflect> FromType<T> for ReflectFromReflect {
|
||||||
|
fn from_type() -> Self {
|
||||||
|
Self {
|
||||||
|
from_reflect: |reflect_value| {
|
||||||
|
T::from_reflect(reflect_value).map(|value| Box::new(value) as Box<dyn Reflect>)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
mod array;
|
mod array;
|
||||||
mod fields;
|
mod fields;
|
||||||
|
mod from_reflect;
|
||||||
mod list;
|
mod list;
|
||||||
mod map;
|
mod map;
|
||||||
mod path;
|
mod path;
|
||||||
@ -47,6 +48,7 @@ pub mod prelude {
|
|||||||
pub use array::*;
|
pub use array::*;
|
||||||
pub use enums::*;
|
pub use enums::*;
|
||||||
pub use fields::*;
|
pub use fields::*;
|
||||||
|
pub use from_reflect::*;
|
||||||
pub use impls::*;
|
pub use impls::*;
|
||||||
pub use list::*;
|
pub use list::*;
|
||||||
pub use map::*;
|
pub use map::*;
|
||||||
@ -103,6 +105,7 @@ mod tests {
|
|||||||
ser::{to_string_pretty, PrettyConfig},
|
ser::{to_string_pretty, PrettyConfig},
|
||||||
Deserializer,
|
Deserializer,
|
||||||
};
|
};
|
||||||
|
use std::any::TypeId;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
@ -244,6 +247,42 @@ mod tests {
|
|||||||
assert_eq!(values, vec![1]);
|
assert_eq!(values, vec![1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_call_from_reflect_dynamically() {
|
||||||
|
#[derive(Reflect, FromReflect)]
|
||||||
|
#[reflect(FromReflect)]
|
||||||
|
struct MyStruct {
|
||||||
|
foo: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register
|
||||||
|
let mut registry = TypeRegistry::default();
|
||||||
|
registry.register::<MyStruct>();
|
||||||
|
|
||||||
|
// Get type data
|
||||||
|
let type_id = TypeId::of::<MyStruct>();
|
||||||
|
let rfr = registry
|
||||||
|
.get_type_data::<ReflectFromReflect>(type_id)
|
||||||
|
.expect("the FromReflect trait should be registered");
|
||||||
|
|
||||||
|
// Call from_reflect
|
||||||
|
let mut dynamic_struct = DynamicStruct::default();
|
||||||
|
dynamic_struct.insert("foo", 123usize);
|
||||||
|
let reflected = rfr
|
||||||
|
.from_reflect(&dynamic_struct)
|
||||||
|
.expect("the type should be properly reflected");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let expected = MyStruct { foo: 123 };
|
||||||
|
assert!(expected
|
||||||
|
.reflect_partial_eq(reflected.as_ref())
|
||||||
|
.unwrap_or_default());
|
||||||
|
let not_expected = MyStruct { foo: 321 };
|
||||||
|
assert!(!not_expected
|
||||||
|
.reflect_partial_eq(reflected.as_ref())
|
||||||
|
.unwrap_or_default());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn from_reflect_should_use_default_field_attributes() {
|
fn from_reflect_should_use_default_field_attributes() {
|
||||||
#[derive(Reflect, FromReflect, Eq, PartialEq, Debug)]
|
#[derive(Reflect, FromReflect, Eq, PartialEq, Debug)]
|
||||||
|
|||||||
@ -215,21 +215,6 @@ pub trait Reflect: Any + Send + Sync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for types which can be constructed from a reflected type.
|
|
||||||
///
|
|
||||||
/// This trait can be derived on types which implement [`Reflect`]. Some complex
|
|
||||||
/// types (such as `Vec<T>`) may only be reflected if their element types
|
|
||||||
/// implement this trait.
|
|
||||||
///
|
|
||||||
/// For structs and tuple structs, fields marked with the `#[reflect(ignore)]`
|
|
||||||
/// attribute will be constructed using the `Default` implementation of the
|
|
||||||
/// field type, rather than the corresponding field value (if any) of the
|
|
||||||
/// reflected value.
|
|
||||||
pub trait FromReflect: Reflect + Sized {
|
|
||||||
/// Constructs a concrete instance of `Self` from a reflected value.
|
|
||||||
fn from_reflect(reflect: &dyn Reflect) -> Option<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for dyn Reflect {
|
impl Debug for dyn Reflect {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
self.debug(f)
|
self.debug(f)
|
||||||
@ -280,6 +265,8 @@ impl dyn Reflect {
|
|||||||
/// a different type, like the Dynamic\*\*\* types do, you can call `represents`
|
/// a different type, like the Dynamic\*\*\* types do, you can call `represents`
|
||||||
/// to determine what type they represent. Represented types cannot be downcasted
|
/// to determine what type they represent. Represented types cannot be downcasted
|
||||||
/// to, but you can use [`FromReflect`] to create a value of the represented type from them.
|
/// to, but you can use [`FromReflect`] to create a value of the represented type from them.
|
||||||
|
///
|
||||||
|
/// [`FromReflect`]: crate::FromReflect
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is<T: Reflect>(&self) -> bool {
|
pub fn is<T: Reflect>(&self) -> bool {
|
||||||
self.type_id() == TypeId::of::<T>()
|
self.type_id() == TypeId::of::<T>()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user