diff --git a/crates/bevy_reflect/bevy_reflect_derive/Cargo.toml b/crates/bevy_reflect/bevy_reflect_derive/Cargo.toml index ef6ba3a7733c1..c7c947537fec7 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/Cargo.toml +++ b/crates/bevy_reflect/bevy_reflect_derive/Cargo.toml @@ -18,3 +18,4 @@ syn = { version = "1.0", features = ["full"] } proc-macro2 = "1.0" quote = "1.0" uuid = { version = "1.1", features = ["v4"] } +bit-set = "0.5.2" \ No newline at end of file diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs b/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs index 236c0e627ae3f..9fb4295ea7e70 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs @@ -1,11 +1,13 @@ use crate::container_attributes::ReflectTraits; use crate::field_attributes::{parse_field_attrs, ReflectFieldAttr}; +use crate::utility::members_to_serialization_denylist; +use bit_set::BitSet; use quote::quote; use crate::{utility, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME}; use syn::punctuated::Punctuated; use syn::spanned::Spanned; -use syn::{Data, DeriveInput, Field, Fields, Generics, Ident, Meta, Path, Token, Type, Variant}; +use syn::{Data, DeriveInput, Field, Fields, Generics, Ident, Meta, Path, Token, Variant}; pub(crate) enum ReflectDerive<'a> { Struct(ReflectStruct<'a>), @@ -54,6 +56,7 @@ pub(crate) struct ReflectMeta<'a> { /// ``` pub(crate) struct ReflectStruct<'a> { meta: ReflectMeta<'a>, + serialization_denylist: BitSet, fields: Vec>, } @@ -92,6 +95,7 @@ pub(crate) struct EnumVariant<'a> { /// The fields within this variant. pub fields: EnumVariantFields<'a>, /// The reflection-based attributes on the variant. + #[allow(dead_code)] pub attrs: ReflectFieldAttr, /// The index of this variant within the enum. #[allow(dead_code)] @@ -125,18 +129,24 @@ impl<'a> ReflectDerive<'a> { _ => continue, } } - - let meta = ReflectMeta::new(&input.ident, &input.generics, traits); - if force_reflect_value { - return Ok(Self::Value(meta)); + return Ok(Self::Value(ReflectMeta::new( + &input.ident, + &input.generics, + traits, + ))); } return match &input.data { Data::Struct(data) => { + let fields = Self::collect_struct_fields(&data.fields)?; + let meta = ReflectMeta::new(&input.ident, &input.generics, traits); let reflect_struct = ReflectStruct { meta, - fields: Self::collect_struct_fields(&data.fields)?, + serialization_denylist: members_to_serialization_denylist( + fields.iter().map(|v| v.attrs.ignore), + ), + fields, }; match data.fields { @@ -146,10 +156,10 @@ impl<'a> ReflectDerive<'a> { } } Data::Enum(data) => { - let reflect_enum = ReflectEnum { - meta, - variants: Self::collect_enum_variants(&data.variants)?, - }; + let variants = Self::collect_enum_variants(&data.variants)?; + let meta = ReflectMeta::new(&input.ident, &input.generics, traits); + + let reflect_enum = ReflectEnum { meta, variants }; Ok(Self::Enum(reflect_enum)) } Data::Union(..) => Err(syn::Error::new( @@ -246,6 +256,7 @@ impl<'a> ReflectMeta<'a> { &self.bevy_reflect_path, self.traits.idents(), self.generics, + None, ) } } @@ -256,19 +267,50 @@ impl<'a> ReflectStruct<'a> { &self.meta } - /// Get an iterator over the active fields. - pub fn active_fields(&self) -> impl Iterator> { - self.fields.iter().filter(|field| !field.attrs.ignore) + /// Access the data about which fields should be ignored during serialization. + /// + /// The returned bitset is a collection of indices obtained from the [`members_to_serialization_denylist`](crate::utility::members_to_serialization_denylist) function. + #[allow(dead_code)] + pub fn serialization_denylist(&self) -> &BitSet { + &self.serialization_denylist } - /// Get an iterator over the ignored fields. - pub fn ignored_fields(&self) -> impl Iterator> { - self.fields.iter().filter(|field| field.attrs.ignore) + /// Returns the `GetTypeRegistration` impl as a `TokenStream`. + /// + /// Returns a specific implementation for structs and this method should be preffered over the generic [`get_type_registration`](crate::ReflectMeta) method + pub fn get_type_registration(&self) -> proc_macro2::TokenStream { + let reflect_path = self.meta.bevy_reflect_path(); + + crate::registration::impl_get_type_registration( + self.meta.type_name(), + reflect_path, + self.meta.traits().idents(), + self.meta.generics(), + Some(&self.serialization_denylist), + ) } - /// Get a collection of all active types. - pub fn active_types(&self) -> impl Iterator { - self.active_fields().map(|field| &field.data.ty) + /// Get a collection of types which are exposed to the reflection API + pub fn active_types(&self) -> Vec { + self.fields + .iter() + .filter(move |field| field.attrs.ignore.is_active()) + .map(|field| field.data.ty.clone()) + .collect::>() + } + + /// Get an iterator of fields which are exposed to the reflection API + pub fn active_fields(&self) -> impl Iterator> { + self.fields + .iter() + .filter(move |field| field.attrs.ignore.is_active()) + } + + /// Get an iterator of fields which are ignored by the reflection API + pub fn ignored_fields(&self) -> impl Iterator> { + self.fields + .iter() + .filter(move |field| field.attrs.ignore.is_ignored()) } /// The complete set of fields in this struct. @@ -284,17 +326,6 @@ impl<'a> ReflectEnum<'a> { &self.meta } - /// Get an iterator over the active variants. - pub fn active_variants(&self) -> impl Iterator> { - self.variants.iter().filter(|variant| !variant.attrs.ignore) - } - - /// Get an iterator over the ignored variants. - #[allow(dead_code)] - pub fn ignored_variants(&self) -> impl Iterator> { - self.variants.iter().filter(|variant| variant.attrs.ignore) - } - /// Returns the given ident as a qualified unit variant of this enum. pub fn get_unit(&self, variant: &Ident) -> proc_macro2::TokenStream { let name = self.meta.type_name; @@ -304,7 +335,6 @@ impl<'a> ReflectEnum<'a> { } /// The complete set of variants in this enum. - #[allow(dead_code)] pub fn variants(&self) -> &[EnumVariant<'a>] { &self.variants } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs b/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs index 427db8a377e57..8b1140bb6ac71 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs @@ -24,7 +24,7 @@ pub(crate) fn get_variant_constructors( let mut variant_names = Vec::with_capacity(variant_count); let mut variant_constructors = Vec::with_capacity(variant_count); - for variant in reflect_enum.active_variants() { + for variant in reflect_enum.variants() { let ident = &variant.data.ident; let name = ident.to_string(); let variant_constructor = reflect_enum.get_unit(ident); @@ -38,7 +38,7 @@ pub(crate) fn get_variant_constructors( let mut reflect_index: usize = 0; let constructor_fields = fields.iter().enumerate().map(|(declar_index, field)| { let field_ident = ident_or_index(field.data.ident.as_ref(), declar_index); - let field_value = if field.attrs.ignore { + let field_value = if field.attrs.ignore.is_ignored() { quote! { Default::default() } } else { let error_repr = field.data.ident.as_ref().map_or_else( diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/field_attributes.rs b/crates/bevy_reflect/bevy_reflect_derive/src/field_attributes.rs index 362fd93f54912..0e19b8429e581 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/field_attributes.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/field_attributes.rs @@ -9,14 +9,47 @@ use quote::ToTokens; use syn::spanned::Spanned; use syn::{Attribute, Lit, Meta, NestedMeta}; -pub(crate) static IGNORE_ATTR: &str = "ignore"; +pub(crate) static IGNORE_SERIALIZATION_ATTR: &str = "skip_serializing"; +pub(crate) static IGNORE_ALL_ATTR: &str = "ignore"; + pub(crate) static DEFAULT_ATTR: &str = "default"; +/// Stores data about if the field should be visible via the Reflect and serialization interfaces +/// +/// Note the relationship between serialization and reflection is such that a member must be reflected in order to be serialized. +/// In boolean logic this is described as: `is_serialized -> is_reflected`, this means we can reflect something without serializing it but not the other way round. +/// The `is_reflected` predicate is provided as `self.is_active()` +#[derive(Default, Clone, Copy, PartialEq, Eq)] +pub(crate) enum ReflectIgnoreBehavior { + /// Don't ignore, appear to all systems + #[default] + None, + /// Ignore when serializing but not when reflecting + IgnoreSerialization, + /// Ignore both when serializing and reflecting + IgnoreAlways, +} + +impl ReflectIgnoreBehavior { + /// Returns `true` if the ignoring behaviour implies member is included in the reflection API, and false otherwise. + pub fn is_active(self) -> bool { + match self { + ReflectIgnoreBehavior::None | ReflectIgnoreBehavior::IgnoreSerialization => true, + ReflectIgnoreBehavior::IgnoreAlways => false, + } + } + + /// The exact logical opposite of `self.is_active()` returns true iff this member is not part of the reflection API whatsover (neither serialized nor reflected) + pub fn is_ignored(self) -> bool { + !self.is_active() + } +} + /// A container for attributes defined on a reflected type's field. #[derive(Default)] pub(crate) struct ReflectFieldAttr { - /// Determines if this field should be ignored. - pub ignore: bool, + /// Determines how this field should be ignored if at all. + pub ignore: ReflectIgnoreBehavior, /// Sets the default behavior of this field. pub default: DefaultBehavior, } @@ -65,9 +98,15 @@ pub(crate) fn parse_field_attrs(attrs: &[Attribute]) -> Result Result<(), syn::Error> { match meta { - Meta::Path(path) if path.is_ident(IGNORE_ATTR) => { - args.ignore = true; - Ok(()) + Meta::Path(path) if path.is_ident(IGNORE_SERIALIZATION_ATTR) => { + (args.ignore == ReflectIgnoreBehavior::None) + .then(|| args.ignore = ReflectIgnoreBehavior::IgnoreSerialization) + .ok_or_else(|| syn::Error::new_spanned(path, format!("Only one of ['{IGNORE_SERIALIZATION_ATTR}','{IGNORE_ALL_ATTR}'] is allowed"))) + } + Meta::Path(path) if path.is_ident(IGNORE_ALL_ATTR) => { + (args.ignore == ReflectIgnoreBehavior::None) + .then(|| args.ignore = ReflectIgnoreBehavior::IgnoreAlways) + .ok_or_else(|| syn::Error::new_spanned(path, format!("Only one of ['{IGNORE_SERIALIZATION_ATTR}','{IGNORE_ALL_ATTR}'] is allowed"))) } Meta::Path(path) if path.is_ident(DEFAULT_ATTR) => { args.default = DefaultBehavior::Default; diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs index 6140f853eee69..83fceda4e7404 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs @@ -269,7 +269,7 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden let mut enum_variant_name = Vec::new(); let mut enum_variant_type = Vec::new(); - for variant in reflect_enum.active_variants() { + for variant in reflect_enum.variants() { let ident = &variant.data.ident; let name = ident.to_string(); let unit = reflect_enum.get_unit(ident); @@ -281,7 +281,7 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden let mut constructor_argument = Vec::new(); let mut reflect_idx = 0; for field in fields.iter() { - if field.attrs.ignore { + if field.attrs.ignore.is_ignored() { // Ignored field continue; } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs index 4dca3a4feea16..f47dddf6536b0 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs @@ -64,7 +64,7 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream { bevy_reflect_path, ); - let get_type_registration_impl = reflect_struct.meta().get_type_registration(); + let get_type_registration_impl = reflect_struct.get_type_registration(); let (impl_generics, ty_generics, where_clause) = reflect_struct.meta().generics().split_for_impl(); diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs index 0ad33ba4bb8ce..ecd829f94f034 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs @@ -8,7 +8,7 @@ use syn::{Index, Member}; pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream { let bevy_reflect_path = reflect_struct.meta().bevy_reflect_path(); let struct_name = reflect_struct.meta().type_name(); - let get_type_registration_impl = reflect_struct.meta().get_type_registration(); + let get_type_registration_impl = reflect_struct.get_type_registration(); let field_idents = reflect_struct .active_fields() diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs b/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs index 7b13f7d352ef8..0f234105452fe 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs @@ -1,5 +1,6 @@ //! Contains code related specifically to Bevy's type registration. +use bit_set::BitSet; use proc_macro2::Ident; use quote::quote; use syn::{Generics, Path}; @@ -10,14 +11,24 @@ pub(crate) fn impl_get_type_registration( bevy_reflect_path: &Path, registration_data: &[Ident], generics: &Generics, + serialization_denylist: Option<&BitSet>, ) -> proc_macro2::TokenStream { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let serialization_data = serialization_denylist.map(|denylist| { + let denylist = denylist.into_iter().map(|v| v as usize); + quote! { + let ignored_indices = [#(#denylist),*].into_iter(); + registration.insert::<#bevy_reflect_path::serde::SerializationData>(#bevy_reflect_path::serde::SerializationData::new(ignored_indices)); + } + }); + quote! { #[allow(unused_mut)] impl #impl_generics #bevy_reflect_path::GetTypeRegistration for #type_name #ty_generics #where_clause { fn get_type_registration() -> #bevy_reflect_path::TypeRegistration { let mut registration = #bevy_reflect_path::TypeRegistration::of::<#type_name #ty_generics>(); registration.insert::<#bevy_reflect_path::ReflectFromPtr>(#bevy_reflect_path::FromType::<#type_name #ty_generics>::from_type()); + #serialization_data #(registration.insert::<#registration_data>(#bevy_reflect_path::FromType::<#type_name #ty_generics>::from_type());)* registration } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs b/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs index f8894689a3858..4f3668bd0cdb2 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs @@ -1,6 +1,8 @@ //! General-purpose utility functions for internal usage within this crate. +use crate::field_attributes::ReflectIgnoreBehavior; use bevy_macro_utils::BevyManifest; +use bit_set::BitSet; use proc_macro2::{Ident, Span}; use syn::{Member, Path}; @@ -96,3 +98,42 @@ impl ResultSifter { } } } + +/// Converts an iterator over ignore behaviour of members to a bitset of ignored members. +/// +/// Takes into account the fact that always ignored (non-reflected) members are skipped. +/// +/// # Example +/// ```rust,ignore +/// pub struct HelloWorld { +/// reflected_field: u32 // index: 0 +/// +/// #[reflect(ignore)] +/// non_reflected_field: u32 // index: N/A (not 1!) +/// +/// #[reflect(skip_serializing)] +/// non_serialized_field: u32 // index: 1 +/// } +/// ``` +/// Would convert to the `0b01` bitset (i.e second field is NOT serialized) +/// +pub(crate) fn members_to_serialization_denylist(member_iter: T) -> BitSet +where + T: Iterator, +{ + let mut bitset = BitSet::default(); + + member_iter.fold(0, |next_idx, member| match member { + ReflectIgnoreBehavior::IgnoreAlways => { + bitset.insert(next_idx); + next_idx + } + ReflectIgnoreBehavior::IgnoreSerialization => { + bitset.insert(next_idx); + next_idx + 1 + } + ReflectIgnoreBehavior::None => next_idx + 1, + }); + + bitset +} diff --git a/crates/bevy_reflect/src/enums/dynamic_enum.rs b/crates/bevy_reflect/src/enums/dynamic_enum.rs index d0f097e977b1e..cc07b5c5282d9 100644 --- a/crates/bevy_reflect/src/enums/dynamic_enum.rs +++ b/crates/bevy_reflect/src/enums/dynamic_enum.rs @@ -7,6 +7,7 @@ use std::any::Any; use std::fmt::Formatter; /// A dynamic representation of an enum variant. +#[derive(Debug)] pub enum DynamicVariant { Unit, Tuple(DynamicTuple), @@ -72,7 +73,7 @@ impl From<()> for DynamicVariant { /// // Tada! /// assert_eq!(None, value); /// ``` -#[derive(Default)] +#[derive(Default, Debug)] pub struct DynamicEnum { name: String, variant_name: String, diff --git a/crates/bevy_reflect/src/enums/mod.rs b/crates/bevy_reflect/src/enums/mod.rs index 6a1f80fd38cf3..e8c32e73ef9c5 100644 --- a/crates/bevy_reflect/src/enums/mod.rs +++ b/crates/bevy_reflect/src/enums/mod.rs @@ -283,30 +283,6 @@ mod tests { value.apply(&dyn_tuple); } - #[test] - #[allow(dead_code)] - fn should_skip_ignored_variants() { - #[derive(Reflect, Debug, PartialEq)] - enum TestEnum { - A, - #[reflect(ignore)] - B, - C, - } - - if let TypeInfo::Enum(info) = TestEnum::type_info() { - assert_eq!( - 2, - info.variant_len(), - "expected one of the variants to be ignored" - ); - assert_eq!("A", info.variant_at(0).unwrap().name()); - assert_eq!("C", info.variant_at(1).unwrap().name()); - } else { - panic!("expected `TypeInfo::Enum`"); - } - } - #[test] fn should_skip_ignored_fields() { #[derive(Reflect, Debug, PartialEq)] diff --git a/crates/bevy_reflect/src/serde/mod.rs b/crates/bevy_reflect/src/serde/mod.rs index 7c7c18f4ea954..1ff61a4e70b4e 100644 --- a/crates/bevy_reflect/src/serde/mod.rs +++ b/crates/bevy_reflect/src/serde/mod.rs @@ -1,8 +1,10 @@ mod de; mod ser; +mod type_data; pub use de::*; pub use ser::*; +pub use type_data::*; pub(crate) mod type_fields { pub const TYPE: &str = "type"; @@ -16,3 +18,91 @@ pub(crate) mod type_fields { pub const ARRAY: &str = "array"; pub const VALUE: &str = "value"; } + +#[cfg(test)] +mod tests { + use crate::{self as bevy_reflect, DynamicTupleStruct}; + use crate::{ + serde::{ReflectDeserializer, ReflectSerializer}, + type_registry::TypeRegistry, + DynamicStruct, Reflect, + }; + use serde::de::DeserializeSeed; + + #[test] + fn test_serialization_struct() { + #[derive(Debug, Reflect, PartialEq)] + #[reflect(PartialEq)] + struct TestStruct { + a: i32, + #[reflect(ignore)] + b: i32, + #[reflect(skip_serializing)] + c: i32, + d: i32, + } + + let mut registry = TypeRegistry::default(); + registry.register::(); + + let test_struct = TestStruct { + a: 3, + b: 4, + c: 5, + d: 6, + }; + + let serializer = ReflectSerializer::new(&test_struct, ®istry); + let serialized = + ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap(); + + let mut expected = DynamicStruct::default(); + expected.insert("a", 3); + expected.insert("d", 6); + + let mut deserializer = ron::de::Deserializer::from_str(&serialized).unwrap(); + let reflect_deserializer = ReflectDeserializer::new(®istry); + let value = reflect_deserializer.deserialize(&mut deserializer).unwrap(); + let deserialized = value.take::().unwrap(); + + assert!( + expected.reflect_partial_eq(&deserialized).unwrap(), + "Expected {expected:?} found {deserialized:?}" + ); + } + + #[test] + fn test_serialization_tuple_struct() { + #[derive(Debug, Reflect, PartialEq)] + #[reflect(PartialEq)] + struct TestStruct( + i32, + #[reflect(ignore)] i32, + #[reflect(skip_serializing)] i32, + i32, + ); + + let mut registry = TypeRegistry::default(); + registry.register::(); + + let test_struct = TestStruct(3, 4, 5, 6); + + let serializer = ReflectSerializer::new(&test_struct, ®istry); + let serialized = + ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap(); + + let mut expected = DynamicTupleStruct::default(); + expected.insert(3); + expected.insert(6); + + let mut deserializer = ron::de::Deserializer::from_str(&serialized).unwrap(); + let reflect_deserializer = ReflectDeserializer::new(®istry); + let value = reflect_deserializer.deserialize(&mut deserializer).unwrap(); + let deserialized = value.take::().unwrap(); + + assert!( + expected.reflect_partial_eq(&deserialized).unwrap(), + "Expected {expected:?} found {deserialized:?}" + ); + } +} diff --git a/crates/bevy_reflect/src/serde/ser.rs b/crates/bevy_reflect/src/serde/ser.rs index 0dd239d6305a7..b086a6a942085 100644 --- a/crates/bevy_reflect/src/serde/ser.rs +++ b/crates/bevy_reflect/src/serde/ser.rs @@ -8,6 +8,8 @@ use serde::{ Serialize, Serializer, }; +use super::SerializationData; + pub enum Serializable<'a> { Owned(Box), Borrowed(&'a dyn erased_serde::Serialize), @@ -154,7 +156,18 @@ impl<'a> Serialize for StructValueSerializer<'a> { S: serde::Serializer, { let mut state = serializer.serialize_map(Some(self.struct_value.field_len()))?; + let serialization_data = self + .registry + .get_with_name(self.struct_value.type_name()) + .and_then(|registration| registration.data::()); + for (index, value) in self.struct_value.iter_fields().enumerate() { + if serialization_data + .map(|data| data.is_ignored_field(index)) + .unwrap_or(false) + { + continue; + } let key = self.struct_value.name_at(index).unwrap(); state.serialize_entry(key, &ReflectSerializer::new(value, self.registry))?; } @@ -197,7 +210,18 @@ impl<'a> Serialize for TupleStructValueSerializer<'a> { S: serde::Serializer, { let mut state = serializer.serialize_seq(Some(self.tuple_struct.field_len()))?; - for value in self.tuple_struct.iter_fields() { + let serialization_data = self + .registry + .get_with_name(self.tuple_struct.type_name()) + .and_then(|registration| registration.data::()); + + for (index, value) in self.tuple_struct.iter_fields().enumerate() { + if serialization_data + .map(|data| data.is_ignored_field(index)) + .unwrap_or(false) + { + continue; + } state.serialize_element(&ReflectSerializer::new(value, self.registry))?; } state.end() @@ -350,6 +374,7 @@ impl<'a> Serialize for TupleValueSerializer<'a> { S: serde::Serializer, { let mut state = serializer.serialize_seq(Some(self.tuple.field_len()))?; + for value in self.tuple.iter_fields() { state.serialize_element(&ReflectSerializer::new(value, self.registry))?; } diff --git a/crates/bevy_reflect/src/serde/type_data.rs b/crates/bevy_reflect/src/serde/type_data.rs new file mode 100644 index 0000000000000..2e3cd6bbf7951 --- /dev/null +++ b/crates/bevy_reflect/src/serde/type_data.rs @@ -0,0 +1,34 @@ +use std::collections::HashSet; + +/// Contains data relevant to the automatic reflect powered serialization of a type +#[derive(Debug, Clone)] +pub struct SerializationData { + ignored_field_indices: HashSet, +} + +impl SerializationData { + /// Creates a new `SerializationData` instance given: + /// + /// - `ignored_iter`: the iterator of member indices to be ignored during serialization. Indices are assigned only to reflected members, those which are not reflected are skipped. + pub fn new>(ignored_iter: I) -> Self { + Self { + ignored_field_indices: ignored_iter.collect(), + } + } + /// Returns true if the given index corresponds to a field meant to be ignored in serialization. + /// + /// Indices start from 0 and ignored fields are skipped. + /// + /// # Example + /// + /// ```rust,ignore + /// for (idx, field) in my_struct.iter_fields().enumerate(){ + /// if serialization_data.is_ignored_field(idx){ + /// // serialize ... + /// } + /// } + /// ``` + pub fn is_ignored_field(&self, index: usize) -> bool { + self.ignored_field_indices.contains(&index) + } +} diff --git a/crates/bevy_reflect/src/struct_trait.rs b/crates/bevy_reflect/src/struct_trait.rs index 4f0b7819f258d..a1dc934b9efaf 100644 --- a/crates/bevy_reflect/src/struct_trait.rs +++ b/crates/bevy_reflect/src/struct_trait.rs @@ -59,7 +59,7 @@ pub trait Struct: Reflect { /// Returns the number of fields in the struct. fn field_len(&self) -> usize; - /// Returns an iterator over the values of the struct's fields. + /// Returns an iterator over the values of the reflectable fields for this struct. fn iter_fields(&self) -> FieldIter; /// Clones the struct into a [`DynamicStruct`]. diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index 13469c0be3fcb..b31b5f78e8da7 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -185,7 +185,7 @@ impl TupleInfo { } /// A tuple which allows fields to be added at runtime. -#[derive(Default)] +#[derive(Default, Debug)] pub struct DynamicTuple { name: String, fields: Vec>, diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs index 74865abeb0c91..fe3302774b761 100644 --- a/crates/bevy_reflect/src/type_registry.rs +++ b/crates/bevy_reflect/src/type_registry.rs @@ -272,6 +272,15 @@ pub struct TypeRegistration { type_info: &'static TypeInfo, } +impl Debug for TypeRegistration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TypeRegistration") + .field("short_name", &self.short_name) + .field("type_info", &self.type_info) + .finish() + } +} + impl TypeRegistration { /// Returns the [`TypeId`] of the type. /// @@ -352,7 +361,6 @@ impl Clone for TypeRegistration { } } } - /// A trait for types generated by the [`#[reflect_trait]`][0] attribute macro. /// /// [0]: crate::reflect_trait diff --git a/examples/scene/scene.rs b/examples/scene/scene.rs index ffbb93fb5873c..ecf7a04acf268 100644 --- a/examples/scene/scene.rs +++ b/examples/scene/scene.rs @@ -30,14 +30,14 @@ struct ComponentA { } // Some components have fields that cannot (or should not) be written to scene files. These can be -// ignored with the #[reflect(ignore)] attribute. This is also generally where the `FromWorld` +// ignored with the #[reflect(skip_serializing)] attribute. This is also generally where the `FromWorld` // trait comes into play. `FromWorld` gives you access to your App's current ECS `Resources` // when you construct your component. #[derive(Component, Reflect)] #[reflect(Component)] struct ComponentB { pub value: String, - #[reflect(ignore)] + #[reflect(skip_serializing)] pub _time_since_startup: Duration, }