From 06d9384447e9934b56ee2033315f4f955824fda0 Mon Sep 17 00:00:00 2001 From: davier Date: Sun, 26 Dec 2021 18:49:01 +0000 Subject: [PATCH] Add FromReflect trait to convert dynamic types to concrete types (#1395) Dynamic types (`DynamicStruct`, `DynamicTupleStruct`, `DynamicTuple`, `DynamicList` and `DynamicMap`) are used when deserializing scenes, but currently they can only be applied to existing concrete types. This leads to issues when trying to spawn non trivial deserialized scene. For components, the issue is avoided by requiring that reflected components implement ~~`FromResources`~~ `FromWorld` (or `Default`). When spawning, a new concrete type is created that way, and the dynamic type is applied to it. Unfortunately, some components don't have any valid implementation of these traits. In addition, any `Vec` or `HashMap` inside a component will panic when a dynamic type is pushed into it (for instance, `Text` panics when adding a text section). To solve this issue, this PR adds the `FromReflect` trait that creates a concrete type from a dynamic type that represent it, derives the trait alongside the `Reflect` trait, drops the ~~`FromResources`~~ `FromWorld` requirement on reflected components, ~~and enables reflection for UI and Text bundles~~. It also adds the requirement that fields ignored with `#[reflect(ignore)]` implement `Default`, since we need to initialize them somehow. Co-authored-by: Carter Anderson --- crates/bevy_asset/src/handle.rs | 24 ++- crates/bevy_ecs/src/reflect.rs | 5 +- .../bevy_reflect_derive/src/from_reflect.rs | 151 ++++++++++++++++++ .../bevy_reflect_derive/src/lib.rs | 115 +++++++++++++ crates/bevy_reflect/src/impls/glam.rs | 15 +- crates/bevy_reflect/src/impls/smallvec.rs | 33 +++- crates/bevy_reflect/src/impls/std.rs | 92 +++++++++-- crates/bevy_reflect/src/lib.rs | 57 ++++++- crates/bevy_reflect/src/path.rs | 2 +- crates/bevy_reflect/src/reflect.rs | 5 + crates/bevy_reflect/src/tuple.rs | 19 ++- crates/bevy_render/src/color/mod.rs | 4 +- crates/bevy_text/src/lib.rs | 3 +- crates/bevy_text/src/text.rs | 6 +- 14 files changed, 495 insertions(+), 36 deletions(-) create mode 100644 crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index 5aeceda925..bdbba2a25f 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -10,14 +10,25 @@ use crate::{ Asset, Assets, }; use bevy_ecs::{component::Component, reflect::ReflectComponent}; -use bevy_reflect::{Reflect, ReflectDeserialize}; +use bevy_reflect::{FromReflect, Reflect, ReflectDeserialize}; use bevy_utils::Uuid; use crossbeam_channel::{Receiver, Sender}; use serde::{Deserialize, Serialize}; /// A unique, stable asset id #[derive( - Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect, + Debug, + Clone, + Copy, + Eq, + PartialEq, + Hash, + Ord, + PartialOrd, + Serialize, + Deserialize, + Reflect, + FromReflect, )] #[reflect_value(Serialize, Deserialize, PartialEq, Hash)] pub enum HandleId { @@ -58,7 +69,7 @@ impl HandleId { /// /// Handles contain a unique id that corresponds to a specific asset in the [Assets](crate::Assets) /// collection. -#[derive(Component, Reflect)] +#[derive(Component, Reflect, FromReflect)] #[reflect(Component)] pub struct Handle where @@ -77,6 +88,13 @@ enum HandleType { Strong(Sender), } +// FIXME: This only is needed because `Handle`'s field `handle_type` is currently ignored for reflection +impl Default for HandleType { + fn default() -> Self { + Self::Weak + } +} + impl Debug for HandleType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/crates/bevy_ecs/src/reflect.rs b/crates/bevy_ecs/src/reflect.rs index a967c60449..4881dd83f0 100644 --- a/crates/bevy_ecs/src/reflect.rs +++ b/crates/bevy_ecs/src/reflect.rs @@ -6,7 +6,9 @@ use crate::{ entity::{Entity, EntityMap, MapEntities, MapEntitiesError}, world::{FromWorld, World}, }; -use bevy_reflect::{impl_reflect_value, FromType, Reflect, ReflectDeserialize}; +use bevy_reflect::{ + impl_from_reflect_value, impl_reflect_value, FromType, Reflect, ReflectDeserialize, +}; #[derive(Clone)] pub struct ReflectComponent { @@ -121,6 +123,7 @@ impl FromType for ReflectComponent { } impl_reflect_value!(Entity(Hash, PartialEq, Serialize, Deserialize)); +impl_from_reflect_value!(Entity); #[derive(Clone)] pub struct ReflectMapEntities { diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs b/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs new file mode 100644 index 0000000000..68d18d36a6 --- /dev/null +++ b/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs @@ -0,0 +1,151 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{Field, Generics, Ident, Index, Member, Path}; + +pub fn impl_struct( + struct_name: &Ident, + generics: &Generics, + bevy_reflect_path: &Path, + active_fields: &[(&Field, usize)], + ignored_fields: &[(&Field, usize)], +) -> TokenStream { + let field_names = active_fields + .iter() + .map(|(field, index)| { + field + .ident + .as_ref() + .map(|i| i.to_string()) + .unwrap_or_else(|| index.to_string()) + }) + .collect::>(); + let field_idents = active_fields + .iter() + .map(|(field, index)| { + field + .ident + .as_ref() + .map(|ident| Member::Named(ident.clone())) + .unwrap_or_else(|| Member::Unnamed(Index::from(*index))) + }) + .collect::>(); + + let field_types = active_fields + .iter() + .map(|(field, _index)| field.ty.clone()) + .collect::>(); + let field_count = active_fields.len(); + let ignored_field_idents = ignored_fields + .iter() + .map(|(field, index)| { + field + .ident + .as_ref() + .map(|ident| Member::Named(ident.clone())) + .unwrap_or_else(|| Member::Unnamed(Index::from(*index))) + }) + .collect::>(); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + // Add FromReflect bound for each active field + let mut where_from_reflect_clause = if where_clause.is_some() { + quote! {#where_clause} + } else if field_count > 0 { + quote! {where} + } else { + quote! {} + }; + where_from_reflect_clause.extend(quote! { + #(#field_types: #bevy_reflect_path::FromReflect,)* + }); + + 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(),)* + } + ) + } else { + None + } + } + } + }) +} + +pub fn impl_tuple_struct( + struct_name: &Ident, + generics: &Generics, + bevy_reflect_path: &Path, + active_fields: &[(&Field, usize)], + ignored_fields: &[(&Field, usize)], +) -> TokenStream { + let field_idents = active_fields + .iter() + .map(|(_field, index)| Member::Unnamed(Index::from(*index))) + .collect::>(); + let field_types = active_fields + .iter() + .map(|(field, _index)| field.ty.clone()) + .collect::>(); + let field_count = active_fields.len(); + let field_indices = (0..field_count).collect::>(); + let ignored_field_idents = ignored_fields + .iter() + .map(|(_field, index)| Member::Unnamed(Index::from(*index))) + .collect::>(); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + // Add FromReflect bound for each active field + let mut where_from_reflect_clause = if where_clause.is_some() { + quote! {#where_clause} + } else if field_count > 0 { + quote! {where} + } else { + quote! {} + }; + where_from_reflect_clause.extend(quote! { + #(#field_types: #bevy_reflect_path::FromReflect,)* + }); + + 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::TupleStruct; + if let #bevy_reflect_path::ReflectRef::TupleStruct(ref_tuple_struct) = reflect.reflect_ref() { + Some( + Self{ + #(#field_idents: + <#field_types as #bevy_reflect_path::FromReflect>::from_reflect(ref_tuple_struct.field(#field_indices)?)? + ,)* + #(#ignored_field_idents: Default::default(),)* + } + ) + } else { + None + } + } + } + }) +} + +pub fn impl_value(type_name: &Ident, generics: &Generics, bevy_reflect_path: &Path) -> TokenStream { + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + TokenStream::from(quote! { + impl #impl_generics #bevy_reflect_path::FromReflect for #type_name #ty_generics #where_clause { + fn from_reflect(reflect: &dyn #bevy_reflect_path::Reflect) -> Option { + Some(reflect.any().downcast_ref::<#type_name #ty_generics>()?.clone()) + } + } + }) +} diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs index 45d40b0ff0..55201bff91 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs @@ -1,5 +1,6 @@ extern crate proc_macro; +mod from_reflect; mod reflect_trait; mod type_uuid; @@ -740,3 +741,117 @@ pub fn external_type_uuid(tokens: proc_macro::TokenStream) -> proc_macro::TokenS pub fn reflect_trait(args: TokenStream, input: TokenStream) -> TokenStream { reflect_trait::reflect_trait(args, input) } + +#[proc_macro_derive(FromReflect)] +pub fn derive_from_reflect(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let unit_struct_punctuated = Punctuated::new(); + let (fields, mut derive_type) = match &ast.data { + Data::Struct(DataStruct { + fields: Fields::Named(fields), + .. + }) => (&fields.named, DeriveType::Struct), + Data::Struct(DataStruct { + fields: Fields::Unnamed(fields), + .. + }) => (&fields.unnamed, DeriveType::TupleStruct), + Data::Struct(DataStruct { + fields: Fields::Unit, + .. + }) => (&unit_struct_punctuated, DeriveType::UnitStruct), + _ => (&unit_struct_punctuated, DeriveType::Value), + }; + + 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 bevy_reflect_path = BevyManifest::default().get_path("bevy_reflect"); + let type_name = &ast.ident; + + 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_VALUE_ATTRIBUTE_NAME { + derive_type = DeriveType::Value; + } + } + } + + match derive_type { + DeriveType::Struct | DeriveType::UnitStruct => from_reflect::impl_struct( + type_name, + &ast.generics, + &bevy_reflect_path, + &active_fields, + &ignored_fields, + ), + DeriveType::TupleStruct => from_reflect::impl_tuple_struct( + type_name, + &ast.generics, + &bevy_reflect_path, + &active_fields, + &ignored_fields, + ), + DeriveType::Value => from_reflect::impl_value(type_name, &ast.generics, &bevy_reflect_path), + } +} + +#[proc_macro] +pub fn impl_from_reflect_value(input: TokenStream) -> TokenStream { + let reflect_value_def = parse_macro_input!(input as ReflectDef); + + let bevy_reflect_path = BevyManifest::default().get_path("bevy_reflect"); + let ty = &reflect_value_def.type_name; + from_reflect::impl_value(ty, &reflect_value_def.generics, &bevy_reflect_path) +} diff --git a/crates/bevy_reflect/src/impls/glam.rs b/crates/bevy_reflect/src/impls/glam.rs index 93c749159e..de0c74c31c 100644 --- a/crates/bevy_reflect/src/impls/glam.rs +++ b/crates/bevy_reflect/src/impls/glam.rs @@ -1,6 +1,6 @@ use crate as bevy_reflect; use crate::ReflectDeserialize; -use bevy_reflect_derive::impl_reflect_value; +use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_value}; use glam::{IVec2, IVec3, IVec4, Mat3, Mat4, Quat, UVec2, UVec3, UVec4, Vec2, Vec3, Vec4}; impl_reflect_value!(IVec2(PartialEq, Serialize, Deserialize)); @@ -15,3 +15,16 @@ impl_reflect_value!(Vec4(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_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!(Mat3); +impl_from_reflect_value!(Mat4); +impl_from_reflect_value!(Quat); diff --git a/crates/bevy_reflect/src/impls/smallvec.rs b/crates/bevy_reflect/src/impls/smallvec.rs index 942f5d3abf..4d6417c6df 100644 --- a/crates/bevy_reflect/src/impls/smallvec.rs +++ b/crates/bevy_reflect/src/impls/smallvec.rs @@ -1,11 +1,11 @@ use smallvec::{Array, SmallVec}; use std::any::Any; -use crate::{serde::Serializable, List, ListIter, Reflect, ReflectMut, ReflectRef}; +use crate::{serde::Serializable, FromReflect, List, ListIter, Reflect, ReflectMut, ReflectRef}; impl List for SmallVec where - T::Item: Reflect + Clone, + T::Item: FromReflect + Clone, { fn get(&self, index: usize) -> Option<&dyn Reflect> { if index < SmallVec::len(self) { @@ -29,10 +29,12 @@ where fn push(&mut self, value: Box) { let value = value.take::().unwrap_or_else(|value| { - panic!( - "Attempted to push invalid value of type {}.", - value.type_name() - ) + ::Item::from_reflect(&*value).unwrap_or_else(|| { + panic!( + "Attempted to push invalid value of type {}.", + value.type_name() + ) + }) }); SmallVec::push(self, value); } @@ -48,7 +50,7 @@ where // SAFE: any and any_mut both return self unsafe impl Reflect for SmallVec where - T::Item: Reflect + Clone, + T::Item: FromReflect + Clone, { fn type_name(&self) -> &str { std::any::type_name::() @@ -95,3 +97,20 @@ where None } } + +impl FromReflect for SmallVec +where + T::Item: FromReflect + Clone, +{ + fn from_reflect(reflect: &dyn Reflect) -> Option { + if let ReflectRef::List(ref_list) = reflect.reflect_ref() { + let mut new_list = Self::with_capacity(ref_list.len()); + for field in ref_list.iter() { + new_list.push(::Item::from_reflect(field)?); + } + Some(new_list) + } else { + None + } + } +} diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index bb8067fee5..9f4d7708ee 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -1,11 +1,12 @@ use crate as bevy_reflect; use crate::{ - map_partial_eq, serde::Serializable, DynamicMap, FromType, GetTypeRegistration, List, ListIter, - Map, MapIter, Reflect, ReflectDeserialize, ReflectMut, ReflectRef, TypeRegistration, + map_partial_eq, serde::Serializable, DynamicMap, FromReflect, FromType, GetTypeRegistration, + List, ListIter, Map, MapIter, Reflect, ReflectDeserialize, ReflectMut, ReflectRef, + TypeRegistration, }; -use bevy_reflect_derive::impl_reflect_value; -use bevy_utils::{Duration, HashMap, HashSet}; +use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_value}; +use bevy_utils::{AHashExt, Duration, HashMap, HashSet}; use serde::{Deserialize, Serialize}; use std::{ any::Any, @@ -35,7 +36,34 @@ impl_reflect_value!(HashSet Deserial impl_reflect_value!(Range Deserialize<'de> + Send + Sync + 'static>(Serialize, Deserialize)); impl_reflect_value!(Duration); -impl List for Vec { +impl_from_reflect_value!(bool); +impl_from_reflect_value!(u8); +impl_from_reflect_value!(u16); +impl_from_reflect_value!(u32); +impl_from_reflect_value!(u64); +impl_from_reflect_value!(u128); +impl_from_reflect_value!(usize); +impl_from_reflect_value!(i8); +impl_from_reflect_value!(i16); +impl_from_reflect_value!(i32); +impl_from_reflect_value!(i64); +impl_from_reflect_value!(i128); +impl_from_reflect_value!(isize); +impl_from_reflect_value!(f32); +impl_from_reflect_value!(f64); +impl_from_reflect_value!(String); +impl_from_reflect_value!( + Option Deserialize<'de> + Reflect + 'static> +); +impl_from_reflect_value!( + HashSet Deserialize<'de> + Send + Sync + 'static> +); +impl_from_reflect_value!( + Range Deserialize<'de> + Send + Sync + 'static> +); +impl_from_reflect_value!(Duration); + +impl List for Vec { fn get(&self, index: usize) -> Option<&dyn Reflect> { <[T]>::get(self, index).map(|value| value as &dyn Reflect) } @@ -57,17 +85,19 @@ impl List for Vec { fn push(&mut self, value: Box) { let value = value.take::().unwrap_or_else(|value| { - panic!( - "Attempted to push invalid value of type {}.", - value.type_name() - ) + T::from_reflect(&*value).unwrap_or_else(|| { + panic!( + "Attempted to push invalid value of type {}.", + value.type_name() + ) + }) }); Vec::push(self, value); } } // SAFE: any and any_mut both return self -unsafe impl Reflect for Vec { +unsafe impl Reflect for Vec { fn type_name(&self) -> &str { std::any::type_name::() } @@ -114,7 +144,7 @@ unsafe impl Reflect for Vec { } } -impl Deserialize<'de>> GetTypeRegistration for Vec { +impl Deserialize<'de>> GetTypeRegistration for Vec { fn get_type_registration() -> TypeRegistration { let mut registration = TypeRegistration::of::>(); registration.insert::(FromType::>::from_type()); @@ -122,7 +152,21 @@ impl Deserialize<'de>> GetTypeRegistration for Vec { } } -impl Map for HashMap { +impl FromReflect for Vec { + fn from_reflect(reflect: &dyn Reflect) -> Option { + if let ReflectRef::List(ref_list) = reflect.reflect_ref() { + let mut new_list = Self::with_capacity(ref_list.len()); + for field in ref_list.iter() { + new_list.push(T::from_reflect(field)?); + } + Some(new_list) + } else { + None + } + } +} + +impl Map for HashMap { fn get(&self, key: &dyn Reflect) -> Option<&dyn Reflect> { key.downcast_ref::() .and_then(|key| HashMap::get(self, key)) @@ -163,7 +207,7 @@ impl Map for HashMap { } // SAFE: any and any_mut both return self -unsafe impl Reflect for HashMap { +unsafe impl Reflect for HashMap { fn type_name(&self) -> &str { std::any::type_name::() } @@ -230,6 +274,22 @@ where } } +impl FromReflect for HashMap { + fn from_reflect(reflect: &dyn Reflect) -> Option { + if let ReflectRef::Map(ref_map) = reflect.reflect_ref() { + let mut new_map = Self::with_capacity(ref_map.len()); + for (key, value) in ref_map.iter() { + let new_key = K::from_reflect(key)?; + let new_value = V::from_reflect(value)?; + new_map.insert(new_key, new_value); + } + Some(new_map) + } else { + None + } + } +} + // SAFE: any and any_mut both return self unsafe impl Reflect for Cow<'static, str> { fn type_name(&self) -> &str { @@ -298,3 +358,9 @@ impl GetTypeRegistration for Cow<'static, str> { registration } } + +impl FromReflect for Cow<'static, str> { + fn from_reflect(reflect: &dyn crate::Reflect) -> Option { + Some(reflect.any().downcast_ref::>()?.clone()) + } +} diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 29929df9f8..cd479d1e07 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -196,7 +196,7 @@ mod tests { #[test] fn reflect_complex_patch() { - #[derive(Reflect, Eq, PartialEq, Debug)] + #[derive(Reflect, Eq, PartialEq, Debug, FromReflect)] #[reflect(PartialEq)] struct Foo { a: u32, @@ -206,17 +206,25 @@ mod tests { d: HashMap, e: Bar, f: (i32, Vec, Bar), + g: Vec<(Baz, HashMap)>, } - #[derive(Reflect, Eq, PartialEq, Debug)] + #[derive(Reflect, Eq, PartialEq, Clone, Debug, FromReflect)] #[reflect(PartialEq)] struct Bar { x: u32, } + #[derive(Reflect, Eq, PartialEq, Debug, FromReflect)] + struct Baz(String); + let mut hash_map = HashMap::default(); hash_map.insert(1, 1); hash_map.insert(2, 2); + + let mut hash_map_baz = HashMap::default(); + hash_map_baz.insert(1, Bar { x: 0 }); + let mut foo = Foo { a: 1, _b: 1, @@ -224,6 +232,7 @@ mod tests { d: hash_map, e: Bar { x: 1 }, f: (1, vec![1, 2], Bar { x: 1 }), + g: vec![(Baz("string".to_string()), hash_map_baz)], }; let mut foo_patch = DynamicStruct::default(); @@ -250,11 +259,36 @@ mod tests { tuple.insert(bar_patch); foo_patch.insert("f", tuple); + let mut composite = DynamicList::default(); + composite.push({ + let mut tuple = DynamicTuple::default(); + tuple.insert({ + let mut tuple_struct = DynamicTupleStruct::default(); + tuple_struct.insert("new_string".to_string()); + tuple_struct + }); + tuple.insert({ + let mut map = DynamicMap::default(); + map.insert(1usize, { + let mut struct_ = DynamicStruct::default(); + struct_.insert("x", 7u32); + struct_ + }); + map + }); + tuple + }); + foo_patch.insert("g", composite); + foo.apply(&foo_patch); let mut hash_map = HashMap::default(); hash_map.insert(1, 1); hash_map.insert(2, 3); + + let mut hash_map_baz = HashMap::default(); + hash_map_baz.insert(1, Bar { x: 7 }); + let expected_foo = Foo { a: 2, _b: 1, @@ -262,9 +296,28 @@ mod tests { d: hash_map, e: Bar { x: 2 }, f: (2, vec![3, 4, 5], Bar { x: 2 }), + g: vec![(Baz("new_string".to_string()), hash_map_baz.clone())], }; assert_eq!(foo, expected_foo); + + let new_foo = Foo::from_reflect(&foo_patch) + .expect("error while creating a concrete type from a dynamic type"); + + let mut hash_map = HashMap::default(); + hash_map.insert(2, 3); + + let expected_new_foo = Foo { + a: 2, + _b: 0, + c: vec![3, 4, 5], + d: hash_map, + e: Bar { x: 2 }, + f: (2, vec![3, 4, 5], Bar { x: 2 }), + g: vec![(Baz("new_string".to_string()), hash_map_baz)], + }; + + assert_eq!(new_foo, expected_new_foo); } #[test] diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs index 12f36e5dd7..44b4450c0d 100644 --- a/crates/bevy_reflect/src/path.rs +++ b/crates/bevy_reflect/src/path.rs @@ -326,7 +326,7 @@ mod tests { bar: C, } - #[derive(Reflect)] + #[derive(Reflect, FromReflect)] struct C { baz: f32, } diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 9b0dea4b09..7ad42390bf 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -47,6 +47,11 @@ pub unsafe trait Reflect: Any + Send + Sync { fn serializable(&self) -> Option; } +pub trait FromReflect: Reflect + Sized { + /// Creates a clone of a reflected value, converting it to a concrete type if it was a dynamic types (e.g. [`DynamicStruct`](crate::DynamicStruct)) + fn from_reflect(reflect: &dyn Reflect) -> Option; +} + impl Debug for dyn Reflect { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Reflect({})", self.type_name()) diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index 75366d2a6b..ed921aaf9e 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -1,6 +1,6 @@ use std::any::Any; -use crate::{serde::Serializable, Reflect, ReflectMut, ReflectRef}; +use crate::{serde::Serializable, FromReflect, Reflect, ReflectMut, ReflectRef}; pub trait Tuple: Reflect { fn field(&self, index: usize) -> Option<&dyn Reflect>; @@ -329,6 +329,23 @@ macro_rules! impl_reflect_tuple { None } } + + impl<$($name: FromReflect),*> FromReflect for ($($name,)*) + { + fn from_reflect(reflect: &dyn Reflect) -> Option { + if let ReflectRef::Tuple(_ref_tuple) = reflect.reflect_ref() { + Some( + ( + $( + <$name as FromReflect>::from_reflect(_ref_tuple.field($index)?)?, + )* + ) + ) + } else { + None + } + } + } } } diff --git a/crates/bevy_render/src/color/mod.rs b/crates/bevy_render/src/color/mod.rs index 5a3816e205..ea17e9bde1 100644 --- a/crates/bevy_render/src/color/mod.rs +++ b/crates/bevy_render/src/color/mod.rs @@ -5,11 +5,11 @@ pub use colorspace::*; use crate::color::{HslRepresentation, SrgbColorSpace}; use bevy_core::Bytes; use bevy_math::{Vec3, Vec4}; -use bevy_reflect::{Reflect, ReflectDeserialize}; +use bevy_reflect::{FromReflect, Reflect, ReflectDeserialize}; use serde::{Deserialize, Serialize}; use std::ops::{Add, AddAssign, Mul, MulAssign}; -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)] +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect, FromReflect)] #[reflect(PartialEq, Serialize, Deserialize)] pub enum Color { /// sRGBA color diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index f245330e68..fcf6c1e604 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -41,8 +41,7 @@ impl Plugin for TextPlugin { fn build(&self, app: &mut App) { app.add_asset::() .add_asset::() - // TODO: uncomment when #2215 is fixed - // .register_type::() + .register_type::() .register_type::() .register_type::() .init_asset_loader::() diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index 56d3631c40..2f2b37ab9a 100644 --- a/crates/bevy_text/src/text.rs +++ b/crates/bevy_text/src/text.rs @@ -1,7 +1,7 @@ use bevy_asset::Handle; use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; use bevy_math::Size; -use bevy_reflect::{Reflect, ReflectDeserialize}; +use bevy_reflect::{FromReflect, Reflect, ReflectDeserialize}; use bevy_render::color::Color; use serde::{Deserialize, Serialize}; @@ -65,7 +65,7 @@ impl Text { } } -#[derive(Debug, Default, Clone, Reflect)] +#[derive(Debug, Default, Clone, FromReflect, Reflect)] pub struct TextSection { pub value: String, pub style: TextStyle, @@ -134,7 +134,7 @@ impl From for glyph_brush_layout::VerticalAlign { } } -#[derive(Clone, Debug, Reflect)] +#[derive(Clone, Debug, Reflect, FromReflect)] pub struct TextStyle { pub font: Handle, pub font_size: f32,