Add reflect(skip_serializing)
which retains reflection but disables automatic serialization (#5250)
# Objective - To address problems outlined in https://github.com/bevyengine/bevy/issues/5245 ## Solution - Introduce `reflect(skip_serializing)` on top of `reflect(ignore)` which disables automatic serialisation to scenes, but does not disable reflection of the field. --- ## Changelog - Adds: - `bevy_reflect::serde::type_data` module - `SerializationData` structure for describing which fields are to be/not to be ignored, automatically registers as type_data for struct-based types - the `skip_serialization` flag for `#[reflect(...)]` - Removes: - ability to ignore Enum variants in serialization, since that didn't work anyway ## Migration Guide - Change `#[reflect(ignore)]` to `#[reflect(skip_serializing)]` where disabling reflection is not the intended effect. - Remove ignore/skip attributes from enum variants as these won't do anything anymore
This commit is contained in:
parent
f2ad11104d
commit
ac1aebed5e
@ -18,3 +18,4 @@ syn = { version = "1.0", features = ["full"] }
|
|||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
uuid = { version = "1.1", features = ["v4"] }
|
uuid = { version = "1.1", features = ["v4"] }
|
||||||
|
bit-set = "0.5.2"
|
@ -1,11 +1,13 @@
|
|||||||
use crate::container_attributes::ReflectTraits;
|
use crate::container_attributes::ReflectTraits;
|
||||||
use crate::field_attributes::{parse_field_attrs, ReflectFieldAttr};
|
use crate::field_attributes::{parse_field_attrs, ReflectFieldAttr};
|
||||||
|
use crate::utility::members_to_serialization_denylist;
|
||||||
|
use bit_set::BitSet;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
use crate::{utility, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME};
|
use crate::{utility, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME};
|
||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
use syn::spanned::Spanned;
|
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> {
|
pub(crate) enum ReflectDerive<'a> {
|
||||||
Struct(ReflectStruct<'a>),
|
Struct(ReflectStruct<'a>),
|
||||||
@ -54,6 +56,7 @@ pub(crate) struct ReflectMeta<'a> {
|
|||||||
/// ```
|
/// ```
|
||||||
pub(crate) struct ReflectStruct<'a> {
|
pub(crate) struct ReflectStruct<'a> {
|
||||||
meta: ReflectMeta<'a>,
|
meta: ReflectMeta<'a>,
|
||||||
|
serialization_denylist: BitSet<u32>,
|
||||||
fields: Vec<StructField<'a>>,
|
fields: Vec<StructField<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +95,7 @@ pub(crate) struct EnumVariant<'a> {
|
|||||||
/// The fields within this variant.
|
/// The fields within this variant.
|
||||||
pub fields: EnumVariantFields<'a>,
|
pub fields: EnumVariantFields<'a>,
|
||||||
/// The reflection-based attributes on the variant.
|
/// The reflection-based attributes on the variant.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub attrs: ReflectFieldAttr,
|
pub attrs: ReflectFieldAttr,
|
||||||
/// The index of this variant within the enum.
|
/// The index of this variant within the enum.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -125,18 +129,24 @@ impl<'a> ReflectDerive<'a> {
|
|||||||
_ => continue,
|
_ => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let meta = ReflectMeta::new(&input.ident, &input.generics, traits);
|
|
||||||
|
|
||||||
if force_reflect_value {
|
if force_reflect_value {
|
||||||
return Ok(Self::Value(meta));
|
return Ok(Self::Value(ReflectMeta::new(
|
||||||
|
&input.ident,
|
||||||
|
&input.generics,
|
||||||
|
traits,
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return match &input.data {
|
return match &input.data {
|
||||||
Data::Struct(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 {
|
let reflect_struct = ReflectStruct {
|
||||||
meta,
|
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 {
|
match data.fields {
|
||||||
@ -146,10 +156,10 @@ impl<'a> ReflectDerive<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Data::Enum(data) => {
|
Data::Enum(data) => {
|
||||||
let reflect_enum = ReflectEnum {
|
let variants = Self::collect_enum_variants(&data.variants)?;
|
||||||
meta,
|
let meta = ReflectMeta::new(&input.ident, &input.generics, traits);
|
||||||
variants: Self::collect_enum_variants(&data.variants)?,
|
|
||||||
};
|
let reflect_enum = ReflectEnum { meta, variants };
|
||||||
Ok(Self::Enum(reflect_enum))
|
Ok(Self::Enum(reflect_enum))
|
||||||
}
|
}
|
||||||
Data::Union(..) => Err(syn::Error::new(
|
Data::Union(..) => Err(syn::Error::new(
|
||||||
@ -246,6 +256,7 @@ impl<'a> ReflectMeta<'a> {
|
|||||||
&self.bevy_reflect_path,
|
&self.bevy_reflect_path,
|
||||||
self.traits.idents(),
|
self.traits.idents(),
|
||||||
self.generics,
|
self.generics,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -256,19 +267,50 @@ impl<'a> ReflectStruct<'a> {
|
|||||||
&self.meta
|
&self.meta
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an iterator over the active fields.
|
/// 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<u32> {
|
||||||
|
&self.serialization_denylist
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 types which are exposed to the reflection API
|
||||||
|
pub fn active_types(&self) -> Vec<syn::Type> {
|
||||||
|
self.fields
|
||||||
|
.iter()
|
||||||
|
.filter(move |field| field.attrs.ignore.is_active())
|
||||||
|
.map(|field| field.data.ty.clone())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an iterator of fields which are exposed to the reflection API
|
||||||
pub fn active_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
|
pub fn active_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
|
||||||
self.fields.iter().filter(|field| !field.attrs.ignore)
|
self.fields
|
||||||
|
.iter()
|
||||||
|
.filter(move |field| field.attrs.ignore.is_active())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an iterator over the ignored fields.
|
/// Get an iterator of fields which are ignored by the reflection API
|
||||||
pub fn ignored_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
|
pub fn ignored_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
|
||||||
self.fields.iter().filter(|field| field.attrs.ignore)
|
self.fields
|
||||||
}
|
.iter()
|
||||||
|
.filter(move |field| field.attrs.ignore.is_ignored())
|
||||||
/// Get a collection of all active types.
|
|
||||||
pub fn active_types(&self) -> impl Iterator<Item = &Type> {
|
|
||||||
self.active_fields().map(|field| &field.data.ty)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The complete set of fields in this struct.
|
/// The complete set of fields in this struct.
|
||||||
@ -284,17 +326,6 @@ impl<'a> ReflectEnum<'a> {
|
|||||||
&self.meta
|
&self.meta
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an iterator over the active variants.
|
|
||||||
pub fn active_variants(&self) -> impl Iterator<Item = &EnumVariant<'a>> {
|
|
||||||
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<Item = &EnumVariant<'a>> {
|
|
||||||
self.variants.iter().filter(|variant| variant.attrs.ignore)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the given ident as a qualified unit variant of this enum.
|
/// Returns the given ident as a qualified unit variant of this enum.
|
||||||
pub fn get_unit(&self, variant: &Ident) -> proc_macro2::TokenStream {
|
pub fn get_unit(&self, variant: &Ident) -> proc_macro2::TokenStream {
|
||||||
let name = self.meta.type_name;
|
let name = self.meta.type_name;
|
||||||
@ -304,7 +335,6 @@ impl<'a> ReflectEnum<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The complete set of variants in this enum.
|
/// The complete set of variants in this enum.
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn variants(&self) -> &[EnumVariant<'a>] {
|
pub fn variants(&self) -> &[EnumVariant<'a>] {
|
||||||
&self.variants
|
&self.variants
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ pub(crate) fn get_variant_constructors(
|
|||||||
let mut variant_names = Vec::with_capacity(variant_count);
|
let mut variant_names = Vec::with_capacity(variant_count);
|
||||||
let mut variant_constructors = 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 ident = &variant.data.ident;
|
||||||
let name = ident.to_string();
|
let name = ident.to_string();
|
||||||
let variant_constructor = reflect_enum.get_unit(ident);
|
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 mut reflect_index: usize = 0;
|
||||||
let constructor_fields = fields.iter().enumerate().map(|(declar_index, field)| {
|
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_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() }
|
quote! { Default::default() }
|
||||||
} else {
|
} else {
|
||||||
let error_repr = field.data.ident.as_ref().map_or_else(
|
let error_repr = field.data.ident.as_ref().map_or_else(
|
||||||
|
@ -9,14 +9,47 @@ use quote::ToTokens;
|
|||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::{Attribute, Lit, Meta, NestedMeta};
|
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";
|
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.
|
/// A container for attributes defined on a reflected type's field.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct ReflectFieldAttr {
|
pub(crate) struct ReflectFieldAttr {
|
||||||
/// Determines if this field should be ignored.
|
/// Determines how this field should be ignored if at all.
|
||||||
pub ignore: bool,
|
pub ignore: ReflectIgnoreBehavior,
|
||||||
/// Sets the default behavior of this field.
|
/// Sets the default behavior of this field.
|
||||||
pub default: DefaultBehavior,
|
pub default: DefaultBehavior,
|
||||||
}
|
}
|
||||||
@ -65,9 +98,15 @@ pub(crate) fn parse_field_attrs(attrs: &[Attribute]) -> Result<ReflectFieldAttr,
|
|||||||
/// Recursively parses attribute metadata for things like `#[reflect(ignore)]` and `#[reflect(default = "foo")]`
|
/// Recursively parses attribute metadata for things like `#[reflect(ignore)]` and `#[reflect(default = "foo")]`
|
||||||
fn parse_meta(args: &mut ReflectFieldAttr, meta: &Meta) -> Result<(), syn::Error> {
|
fn parse_meta(args: &mut ReflectFieldAttr, meta: &Meta) -> Result<(), syn::Error> {
|
||||||
match meta {
|
match meta {
|
||||||
Meta::Path(path) if path.is_ident(IGNORE_ATTR) => {
|
Meta::Path(path) if path.is_ident(IGNORE_SERIALIZATION_ATTR) => {
|
||||||
args.ignore = true;
|
(args.ignore == ReflectIgnoreBehavior::None)
|
||||||
Ok(())
|
.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) => {
|
Meta::Path(path) if path.is_ident(DEFAULT_ATTR) => {
|
||||||
args.default = DefaultBehavior::Default;
|
args.default = DefaultBehavior::Default;
|
||||||
|
@ -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_name = Vec::new();
|
||||||
let mut enum_variant_type = 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 ident = &variant.data.ident;
|
||||||
let name = ident.to_string();
|
let name = ident.to_string();
|
||||||
let unit = reflect_enum.get_unit(ident);
|
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 constructor_argument = Vec::new();
|
||||||
let mut reflect_idx = 0;
|
let mut reflect_idx = 0;
|
||||||
for field in fields.iter() {
|
for field in fields.iter() {
|
||||||
if field.attrs.ignore {
|
if field.attrs.ignore.is_ignored() {
|
||||||
// Ignored field
|
// Ignored field
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream {
|
|||||||
bevy_reflect_path,
|
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) =
|
let (impl_generics, ty_generics, where_clause) =
|
||||||
reflect_struct.meta().generics().split_for_impl();
|
reflect_struct.meta().generics().split_for_impl();
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ use syn::{Index, Member};
|
|||||||
pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream {
|
pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream {
|
||||||
let bevy_reflect_path = reflect_struct.meta().bevy_reflect_path();
|
let bevy_reflect_path = reflect_struct.meta().bevy_reflect_path();
|
||||||
let struct_name = reflect_struct.meta().type_name();
|
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
|
let field_idents = reflect_struct
|
||||||
.active_fields()
|
.active_fields()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
//! Contains code related specifically to Bevy's type registration.
|
//! Contains code related specifically to Bevy's type registration.
|
||||||
|
|
||||||
|
use bit_set::BitSet;
|
||||||
use proc_macro2::Ident;
|
use proc_macro2::Ident;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{Generics, Path};
|
use syn::{Generics, Path};
|
||||||
@ -10,14 +11,24 @@ pub(crate) fn impl_get_type_registration(
|
|||||||
bevy_reflect_path: &Path,
|
bevy_reflect_path: &Path,
|
||||||
registration_data: &[Ident],
|
registration_data: &[Ident],
|
||||||
generics: &Generics,
|
generics: &Generics,
|
||||||
|
serialization_denylist: Option<&BitSet<u32>>,
|
||||||
) -> proc_macro2::TokenStream {
|
) -> proc_macro2::TokenStream {
|
||||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
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! {
|
quote! {
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
impl #impl_generics #bevy_reflect_path::GetTypeRegistration for #type_name #ty_generics #where_clause {
|
impl #impl_generics #bevy_reflect_path::GetTypeRegistration for #type_name #ty_generics #where_clause {
|
||||||
fn get_type_registration() -> #bevy_reflect_path::TypeRegistration {
|
fn get_type_registration() -> #bevy_reflect_path::TypeRegistration {
|
||||||
let mut registration = #bevy_reflect_path::TypeRegistration::of::<#type_name #ty_generics>();
|
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());
|
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.insert::<#registration_data>(#bevy_reflect_path::FromType::<#type_name #ty_generics>::from_type());)*
|
||||||
registration
|
registration
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
//! General-purpose utility functions for internal usage within this crate.
|
//! General-purpose utility functions for internal usage within this crate.
|
||||||
|
|
||||||
|
use crate::field_attributes::ReflectIgnoreBehavior;
|
||||||
use bevy_macro_utils::BevyManifest;
|
use bevy_macro_utils::BevyManifest;
|
||||||
|
use bit_set::BitSet;
|
||||||
use proc_macro2::{Ident, Span};
|
use proc_macro2::{Ident, Span};
|
||||||
use syn::{Member, Path};
|
use syn::{Member, Path};
|
||||||
|
|
||||||
@ -96,3 +98,42 @@ impl<T> ResultSifter<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<T>(member_iter: T) -> BitSet<u32>
|
||||||
|
where
|
||||||
|
T: Iterator<Item = ReflectIgnoreBehavior>,
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@ use std::any::Any;
|
|||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
|
|
||||||
/// A dynamic representation of an enum variant.
|
/// A dynamic representation of an enum variant.
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum DynamicVariant {
|
pub enum DynamicVariant {
|
||||||
Unit,
|
Unit,
|
||||||
Tuple(DynamicTuple),
|
Tuple(DynamicTuple),
|
||||||
@ -72,7 +73,7 @@ impl From<()> for DynamicVariant {
|
|||||||
/// // Tada!
|
/// // Tada!
|
||||||
/// assert_eq!(None, value);
|
/// assert_eq!(None, value);
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Default)]
|
#[derive(Default, Debug)]
|
||||||
pub struct DynamicEnum {
|
pub struct DynamicEnum {
|
||||||
name: String,
|
name: String,
|
||||||
variant_name: String,
|
variant_name: String,
|
||||||
|
@ -283,30 +283,6 @@ mod tests {
|
|||||||
value.apply(&dyn_tuple);
|
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]
|
#[test]
|
||||||
fn should_skip_ignored_fields() {
|
fn should_skip_ignored_fields() {
|
||||||
#[derive(Reflect, Debug, PartialEq)]
|
#[derive(Reflect, Debug, PartialEq)]
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
mod de;
|
mod de;
|
||||||
mod ser;
|
mod ser;
|
||||||
|
mod type_data;
|
||||||
|
|
||||||
pub use de::*;
|
pub use de::*;
|
||||||
pub use ser::*;
|
pub use ser::*;
|
||||||
|
pub use type_data::*;
|
||||||
|
|
||||||
pub(crate) mod type_fields {
|
pub(crate) mod type_fields {
|
||||||
pub const TYPE: &str = "type";
|
pub const TYPE: &str = "type";
|
||||||
@ -16,3 +18,91 @@ pub(crate) mod type_fields {
|
|||||||
pub const ARRAY: &str = "array";
|
pub const ARRAY: &str = "array";
|
||||||
pub const VALUE: &str = "value";
|
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::<TestStruct>();
|
||||||
|
|
||||||
|
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::<DynamicStruct>().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::<TestStruct>();
|
||||||
|
|
||||||
|
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::<DynamicTupleStruct>().unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
expected.reflect_partial_eq(&deserialized).unwrap(),
|
||||||
|
"Expected {expected:?} found {deserialized:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,6 +8,8 @@ use serde::{
|
|||||||
Serialize, Serializer,
|
Serialize, Serializer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::SerializationData;
|
||||||
|
|
||||||
pub enum Serializable<'a> {
|
pub enum Serializable<'a> {
|
||||||
Owned(Box<dyn erased_serde::Serialize + 'a>),
|
Owned(Box<dyn erased_serde::Serialize + 'a>),
|
||||||
Borrowed(&'a dyn erased_serde::Serialize),
|
Borrowed(&'a dyn erased_serde::Serialize),
|
||||||
@ -154,7 +156,18 @@ impl<'a> Serialize for StructValueSerializer<'a> {
|
|||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
{
|
{
|
||||||
let mut state = serializer.serialize_map(Some(self.struct_value.field_len()))?;
|
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::<SerializationData>());
|
||||||
|
|
||||||
for (index, value) in self.struct_value.iter_fields().enumerate() {
|
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();
|
let key = self.struct_value.name_at(index).unwrap();
|
||||||
state.serialize_entry(key, &ReflectSerializer::new(value, self.registry))?;
|
state.serialize_entry(key, &ReflectSerializer::new(value, self.registry))?;
|
||||||
}
|
}
|
||||||
@ -197,7 +210,18 @@ impl<'a> Serialize for TupleStructValueSerializer<'a> {
|
|||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
{
|
{
|
||||||
let mut state = serializer.serialize_seq(Some(self.tuple_struct.field_len()))?;
|
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::<SerializationData>());
|
||||||
|
|
||||||
|
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.serialize_element(&ReflectSerializer::new(value, self.registry))?;
|
||||||
}
|
}
|
||||||
state.end()
|
state.end()
|
||||||
@ -350,6 +374,7 @@ impl<'a> Serialize for TupleValueSerializer<'a> {
|
|||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
{
|
{
|
||||||
let mut state = serializer.serialize_seq(Some(self.tuple.field_len()))?;
|
let mut state = serializer.serialize_seq(Some(self.tuple.field_len()))?;
|
||||||
|
|
||||||
for value in self.tuple.iter_fields() {
|
for value in self.tuple.iter_fields() {
|
||||||
state.serialize_element(&ReflectSerializer::new(value, self.registry))?;
|
state.serialize_element(&ReflectSerializer::new(value, self.registry))?;
|
||||||
}
|
}
|
||||||
|
34
crates/bevy_reflect/src/serde/type_data.rs
Normal file
34
crates/bevy_reflect/src/serde/type_data.rs
Normal file
@ -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<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<I: Iterator<Item = usize>>(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)
|
||||||
|
}
|
||||||
|
}
|
@ -59,7 +59,7 @@ pub trait Struct: Reflect {
|
|||||||
/// Returns the number of fields in the struct.
|
/// Returns the number of fields in the struct.
|
||||||
fn field_len(&self) -> usize;
|
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;
|
fn iter_fields(&self) -> FieldIter;
|
||||||
|
|
||||||
/// Clones the struct into a [`DynamicStruct`].
|
/// Clones the struct into a [`DynamicStruct`].
|
||||||
|
@ -185,7 +185,7 @@ impl TupleInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A tuple which allows fields to be added at runtime.
|
/// A tuple which allows fields to be added at runtime.
|
||||||
#[derive(Default)]
|
#[derive(Default, Debug)]
|
||||||
pub struct DynamicTuple {
|
pub struct DynamicTuple {
|
||||||
name: String,
|
name: String,
|
||||||
fields: Vec<Box<dyn Reflect>>,
|
fields: Vec<Box<dyn Reflect>>,
|
||||||
|
@ -272,6 +272,15 @@ pub struct TypeRegistration {
|
|||||||
type_info: &'static TypeInfo,
|
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 {
|
impl TypeRegistration {
|
||||||
/// Returns the [`TypeId`] of the type.
|
/// 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.
|
/// A trait for types generated by the [`#[reflect_trait]`][0] attribute macro.
|
||||||
///
|
///
|
||||||
/// [0]: crate::reflect_trait
|
/// [0]: crate::reflect_trait
|
||||||
|
@ -30,14 +30,14 @@ struct ComponentA {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Some components have fields that cannot (or should not) be written to scene files. These can be
|
// 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`
|
// trait comes into play. `FromWorld` gives you access to your App's current ECS `Resources`
|
||||||
// when you construct your component.
|
// when you construct your component.
|
||||||
#[derive(Component, Reflect)]
|
#[derive(Component, Reflect)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
struct ComponentB {
|
struct ComponentB {
|
||||||
pub value: String,
|
pub value: String,
|
||||||
#[reflect(ignore)]
|
#[reflect(skip_serializing)]
|
||||||
pub _time_since_startup: Duration,
|
pub _time_since_startup: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user