From c6604aab8d7fed84bf2c906da6e4be5b1442f4c3 Mon Sep 17 00:00:00 2001 From: PROMETHIA-27 Date: Mon, 9 May 2022 16:32:15 +0000 Subject: [PATCH] Add macro to implement reflect for struct types and migrate glam types (#4540) # Objective Relevant issue: #4474 Currently glam types implement Reflect as a value, which is problematic for reflection, making scripting/editor work much more difficult. This PR re-implements them as structs. ## Solution Added a new proc macro, `impl_reflect_struct`, which replaces `impl_reflect_value` and `impl_from_reflect_value` for glam types. This macro could also be used for other types, but I don't know of any that would require it. It's specifically useful for foreign types that cannot derive Reflect normally. --- ## Changelog ### Added - `impl_reflect_struct` proc macro ### Changed - Glam reflect impls have been replaced with `impl_reflect_struct` - from_reflect's `impl_struct` altered to take an optional custom constructor, allowing non-default non-constructible foreign types to use it - Calls to `impl_struct` altered to conform to new signature - Altered glam types (All vec/mat combinations) have a different serialization structure, as they are reflected differently now. ## Migration Guide This will break altered glam types serialized to RON scenes, as they will expect to be serialized/deserialized as structs rather than values now. A future PR to add custom serialization for non-value types is likely on the way to restore previous behavior. Additionally, calls to `impl_struct` must add a `None` parameter to the end of the call to restore previous behavior. Co-authored-by: PROMETHIA-27 <42193387+PROMETHIA-27@users.noreply.github.com> --- .../bevy_reflect_derive/src/from_reflect.rs | 34 ++- .../bevy_reflect_derive/src/lib.rs | 205 +++++++++++++++++- crates/bevy_reflect/src/impls/glam.rs | 192 ++++++++++++---- crates/bevy_reflect/src/lib.rs | 91 ++++++++ 4 files changed, 471 insertions(+), 51 deletions(-) diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs b/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs index 68d18d36a6f4cc..aabf75d904479d 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs @@ -8,6 +8,7 @@ pub fn impl_struct( bevy_reflect_path: &Path, active_fields: &[(&Field, usize)], ignored_fields: &[(&Field, usize)], + custom_constructor: Option, ) -> TokenStream { let field_names = active_fields .iter() @@ -60,20 +61,35 @@ pub fn impl_struct( #(#field_types: #bevy_reflect_path::FromReflect,)* }); + let constructor = if let Some(constructor) = custom_constructor { + quote!( + let mut value: Self = #constructor; + #( + value.#field_idents = { + <#field_types as #bevy_reflect_path::FromReflect>::from_reflect(#bevy_reflect_path::Struct::field(ref_struct, #field_names)?)? + }; + )* + Some(value) + ) + } else { + quote!( + Some( + Self { + #(#field_idents: { + <#field_types as #bevy_reflect_path::FromReflect>::from_reflect(#bevy_reflect_path::Struct::field(ref_struct, #field_names)?)? + },)* + #(#ignored_field_idents: Default::default(),)* + } + ) + ) + }; + TokenStream::from(quote! { impl #impl_generics #bevy_reflect_path::FromReflect for #struct_name #ty_generics #where_from_reflect_clause { fn from_reflect(reflect: &dyn #bevy_reflect_path::Reflect) -> Option { - use #bevy_reflect_path::Struct; if let #bevy_reflect_path::ReflectRef::Struct(ref_struct) = reflect.reflect_ref() { - Some( - Self{ - #(#field_idents: { - <#field_types as #bevy_reflect_path::FromReflect>::from_reflect(ref_struct.field(#field_names)?)? - },)* - #(#ignored_field_idents: Default::default(),)* - } - ) + #constructor } else { None } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs index 29cc4060993454..2b5f93a85edd43 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs @@ -287,8 +287,7 @@ fn impl_struct( #[inline] fn clone_value(&self) -> Box { - use #bevy_reflect_path::Struct; - Box::new(self.clone_dynamic()) + Box::new(#bevy_reflect_path::Struct::clone_dynamic(self)) } #[inline] fn set(&mut self, value: Box) -> Result<(), Box> { @@ -298,11 +297,10 @@ fn impl_struct( #[inline] fn apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) { - use #bevy_reflect_path::Struct; if let #bevy_reflect_path::ReflectRef::Struct(struct_value) = value.reflect_ref() { for (i, value) in struct_value.iter_fields().enumerate() { let name = struct_value.name_at(i).unwrap(); - self.field_mut(name).map(|v| v.apply(value)); + #bevy_reflect_path::Struct::field_mut(self, name).map(|v| v.apply(value)); } } else { panic!("Attempted to apply non-struct type to struct type."); @@ -607,6 +605,204 @@ pub fn impl_reflect_value(input: TokenStream) -> TokenStream { ) } +/// Represents the information needed to implement a type as a Reflect Struct. +/// +/// # Example +/// ```ignore +/// impl_reflect_struct!( +/// // attrs +/// // |----------------------------------------| +/// #[reflect(PartialEq, Serialize, Deserialize, Default)] +/// // type_name generics +/// // |-------------------||----------| +/// struct ThingThatImReflecting { +/// x: T1, // | +/// y: T2, // |- fields +/// z: T3 // | +/// } +/// ); +/// ``` +struct ReflectStructDef { + type_name: Ident, + generics: Generics, + attrs: ReflectAttrs, + fields: Fields, +} + +impl Parse for ReflectStructDef { + fn parse(input: ParseStream) -> syn::Result { + let ast = input.parse::()?; + + let type_name = ast.ident; + let generics = ast.generics; + let fields = match ast.data { + Data::Struct(data) => data.fields, + Data::Enum(data) => { + return Err(syn::Error::new_spanned( + data.enum_token, + "Enums are not currently supported for reflection", + )) + } + Data::Union(data) => { + return Err(syn::Error::new_spanned( + data.union_token, + "Unions are not supported for reflection", + )) + } + }; + + let mut attrs = ReflectAttrs::default(); + for attribute in ast.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) { + let meta_list = if let Meta::List(meta_list) = attribute { + meta_list + } else { + continue; + }; + + if let Some(ident) = meta_list.path.get_ident() { + if ident == REFLECT_ATTRIBUTE_NAME || ident == REFLECT_VALUE_ATTRIBUTE_NAME { + attrs = ReflectAttrs::from_nested_metas(&meta_list.nested); + } + } + } + + Ok(Self { + type_name, + generics, + attrs, + fields, + }) + } +} + +/// A replacement for `#[derive(Reflect)]` to be used with foreign types which +/// the definitions of cannot be altered. +/// +/// This macro is an alternative to [`impl_reflect_value!`] and [`impl_from_reflect_value!`] +/// which implement foreign types as Value types. Note that there is no `impl_from_reflect_struct`, +/// as this macro will do the job of both. This macro implements them as `Struct` types, +/// which have greater functionality. The type being reflected must be in scope, as you cannot +/// qualify it in the macro as e.g. `bevy::prelude::Vec3`. +/// +/// It may be necessary to add `#[reflect(Default)]` for some types, specifically non-constructible +/// foreign types. Without `Default` reflected for such types, you will usually get an arcane +/// error message and fail to compile. If the type does not implement `Default`, it may not +/// be possible to reflect without extending the macro. +/// +/// # Example +/// Implementing `Reflect` for `bevy::prelude::Vec3` as a struct type: +/// ```ignore +/// use bevy::prelude::Vec3; +/// +/// impl_reflect_struct!( +/// #[reflect(PartialEq, Serialize, Deserialize, Default)] +/// struct Vec3 { +/// x: f32, +/// y: f32, +/// z: f32 +/// } +/// ); +/// ``` +#[proc_macro] +pub fn impl_reflect_struct(input: TokenStream) -> TokenStream { + let ReflectStructDef { + type_name, + generics, + attrs, + fields, + } = parse_macro_input!(input as ReflectStructDef); + + let bevy_reflect_path = BevyManifest::default().get_path("bevy_reflect"); + + let fields_and_args = fields + .iter() + .enumerate() + .map(|(i, f)| { + ( + f, + f.attrs + .iter() + .find(|a| *a.path.get_ident().as_ref().unwrap() == REFLECT_ATTRIBUTE_NAME) + .map(|a| { + syn::custom_keyword!(ignore); + let mut attribute_args = PropAttributeArgs { ignore: None }; + a.parse_args_with(|input: ParseStream| { + if input.parse::>()?.is_some() { + attribute_args.ignore = Some(true); + return Ok(()); + } + Ok(()) + }) + .expect("Invalid 'property' attribute format."); + + attribute_args + }), + i, + ) + }) + .collect::, usize)>>(); + let active_fields = fields_and_args + .iter() + .filter(|(_field, attrs, _i)| { + attrs.is_none() + || match attrs.as_ref().unwrap().ignore { + Some(ignore) => !ignore, + None => true, + } + }) + .map(|(f, _attr, i)| (*f, *i)) + .collect::>(); + let ignored_fields = fields_and_args + .iter() + .filter(|(_field, attrs, _i)| { + attrs + .as_ref() + .map(|attrs| attrs.ignore.unwrap_or(false)) + .unwrap_or(false) + }) + .map(|(f, _attr, i)| (*f, *i)) + .collect::>(); + + let constructor = if attrs + .data + .contains(&Ident::new("ReflectDefault", Span::call_site())) + { + Some(quote! { Default::default() }) + } else { + None + }; + + let registration_data = &attrs.data; + let get_type_registration_impl = + impl_get_type_registration(&type_name, &bevy_reflect_path, registration_data, &generics); + + let impl_struct: proc_macro2::TokenStream = impl_struct( + &type_name, + &generics, + &get_type_registration_impl, + &bevy_reflect_path, + &attrs, + &active_fields, + ) + .into(); + + let impl_from_struct: proc_macro2::TokenStream = from_reflect::impl_struct( + &type_name, + &generics, + &bevy_reflect_path, + &active_fields, + &ignored_fields, + constructor, + ) + .into(); + + TokenStream::from(quote! { + #impl_struct + + #impl_from_struct + }) +} + #[derive(Default)] struct ReflectAttrs { reflect_hash: TraitImpl, @@ -862,6 +1058,7 @@ pub fn derive_from_reflect(input: TokenStream) -> TokenStream { &bevy_reflect_path, &active_fields, &ignored_fields, + None, ), DeriveType::TupleStruct => from_reflect::impl_tuple_struct( type_name, diff --git a/crates/bevy_reflect/src/impls/glam.rs b/crates/bevy_reflect/src/impls/glam.rs index 077a202fbb8721..93f803cc0cf94d 100644 --- a/crates/bevy_reflect/src/impls/glam.rs +++ b/crates/bevy_reflect/src/impls/glam.rs @@ -1,44 +1,160 @@ use crate as bevy_reflect; +use crate::prelude::ReflectDefault; +use crate::reflect::Reflect; use crate::ReflectDeserialize; -use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_value}; +use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_struct, impl_reflect_value}; use glam::*; -impl_reflect_value!(IVec2(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(IVec3(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(IVec4(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(UVec2(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(UVec3(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(UVec4(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(Vec2(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(Vec3(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(Vec3A(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(Vec4(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(DVec2(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(DVec3(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(DVec4(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(Mat3(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(Mat4(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(Quat(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(DMat3(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(DMat4(PartialEq, Serialize, Deserialize)); -impl_reflect_value!(DQuat(PartialEq, Serialize, Deserialize)); - -impl_from_reflect_value!(IVec2); -impl_from_reflect_value!(IVec3); -impl_from_reflect_value!(IVec4); -impl_from_reflect_value!(UVec2); -impl_from_reflect_value!(UVec3); -impl_from_reflect_value!(UVec4); -impl_from_reflect_value!(Vec2); -impl_from_reflect_value!(Vec3); -impl_from_reflect_value!(Vec4); -impl_from_reflect_value!(Vec3A); -impl_from_reflect_value!(DVec2); -impl_from_reflect_value!(DVec3); -impl_from_reflect_value!(DVec4); -impl_from_reflect_value!(Mat3); -impl_from_reflect_value!(Mat4); +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct IVec2 { + x: i32, + y: i32, + } +); +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct IVec3 { + x: i32, + y: i32, + z: i32, + } +); +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct IVec4 { + x: i32, + y: i32, + z: i32, + w: i32, + } +); + +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct UVec2 { + x: u32, + y: u32, + } +); +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct UVec3 { + x: u32, + y: u32, + z: u32, + } +); +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct UVec4 { + x: u32, + y: u32, + z: u32, + w: u32, + } +); + +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct Vec2 { + x: f32, + y: f32, + } +); +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct Vec3 { + x: f32, + y: f32, + z: f32, + } +); +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct Vec3A { + x: f32, + y: f32, + z: f32, + } +); +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct Vec4 { + x: f32, + y: f32, + z: f32, + w: f32, + } +); + +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct DVec2 { + x: f64, + y: f64, + } +); +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct DVec3 { + x: f64, + y: f64, + z: f64, + } +); +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct DVec4 { + x: f64, + y: f64, + z: f64, + w: f64, + } +); + +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct Mat3 { + x_axis: Vec3, + y_axis: Vec3, + z_axis: Vec3, + } +); +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct Mat4 { + x_axis: Vec4, + y_axis: Vec4, + z_axis: Vec4, + w_axis: Vec4, + } +); + +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct DMat3 { + x_axis: DVec3, + y_axis: DVec3, + z_axis: DVec3, + } +); +impl_reflect_struct!( + #[reflect(PartialEq, Serialize, Deserialize, Default)] + struct DMat4 { + x_axis: DVec4, + y_axis: DVec4, + z_axis: DVec4, + w_axis: DVec4, + } +); + +// Quat fields are read-only (as of now), and reflection is currently missing +// mechanisms for read-only fields. I doubt those mechanisms would be added, +// so for now quaternions will remain as values. They are represented identically +// to Vec4 and DVec4, so you may use those instead and convert between. +impl_reflect_value!(Quat(PartialEq, Serialize, Deserialize, Default)); +impl_reflect_value!(DQuat(PartialEq, Serialize, Deserialize, Default)); + impl_from_reflect_value!(Quat); -impl_from_reflect_value!(DMat3); -impl_from_reflect_value!(DMat4); impl_from_reflect_value!(DQuat); diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 544aa839819598..d8f7b42d8e80ac 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -82,7 +82,10 @@ pub mod __macro_exports { #[cfg(test)] #[allow(clippy::blacklisted_name, clippy::approx_constant)] mod tests { + #[cfg(feature = "glam")] + use ::glam::{vec3, Vec3}; use ::serde::de::DeserializeSeed; + use ::serde::Serialize; use bevy_utils::HashMap; use ron::{ ser::{to_string_pretty, PrettyConfig}, @@ -471,4 +474,92 @@ mod tests { // Should compile: let _ = trait_object.as_reflect(); } + + #[cfg(feature = "glam")] + mod glam { + use super::*; + + #[test] + fn vec3_serialization() { + let v = vec3(12.0, 3.0, -6.9); + + let mut registry = TypeRegistry::default(); + registry.add_registration(Vec3::get_type_registration()); + + let ser = ReflectSerializer::new(&v, ®istry); + + let mut dest = vec![]; + let mut serializer = ron::ser::Serializer::new(&mut dest, None, false) + .expect("Failed to acquire serializer"); + + ser.serialize(&mut serializer).expect("Failed to serialize"); + + let result = String::from_utf8(dest).expect("Failed to convert to string"); + + assert_eq!( + result, + r#"{"type":"glam::vec3::Vec3","struct":{"x":{"type":"f32","value":12},"y":{"type":"f32","value":3},"z":{"type":"f32","value":-6.9}}}"# + ); + } + + #[test] + fn vec3_deserialization() { + let data = r#"{"type":"glam::vec3::Vec3","struct":{"x":{"type":"f32","value":12},"y":{"type":"f32","value":3},"z":{"type":"f32","value":-6.9}}}"#; + + let mut registry = TypeRegistry::default(); + registry.add_registration(Vec3::get_type_registration()); + registry.add_registration(f32::get_type_registration()); + + let de = ReflectDeserializer::new(®istry); + + let mut deserializer = + ron::de::Deserializer::from_str(data).expect("Failed to acquire deserializer"); + + let dynamic_struct = de + .deserialize(&mut deserializer) + .expect("Failed to deserialize"); + + let mut result = Vec3::default(); + + result.apply(&*dynamic_struct); + + assert_eq!(result, vec3(12.0, 3.0, -6.9)); + } + + #[test] + fn vec3_field_access() { + let mut v = vec3(1.0, 2.0, 3.0); + + assert_eq!(*v.get_field::("x").unwrap(), 1.0); + + *v.get_field_mut::("y").unwrap() = 6.0; + + assert_eq!(v.y, 6.0); + } + + #[test] + fn vec3_path_access() { + let mut v = vec3(1.0, 2.0, 3.0); + + assert_eq!(*v.path("x").unwrap().downcast_ref::().unwrap(), 1.0); + + *v.path_mut("y").unwrap().downcast_mut::().unwrap() = 6.0; + + assert_eq!(v.y, 6.0); + } + + #[test] + fn vec3_apply_dynamic() { + let mut v = vec3(3.0, 3.0, 3.0); + + let mut d = DynamicStruct::default(); + d.insert("x", 4.0f32); + d.insert("y", 2.0f32); + d.insert("z", 1.0f32); + + v.apply(&d); + + assert_eq!(v, vec3(4.0, 2.0, 1.0)); + } + } }