mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 06:00:20 +00:00
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 changed files with 330 additions and 74 deletions
|
@ -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"
|
|
@ -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<u32>,
|
||||
fields: Vec<StructField<'a>>,
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
/// 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>> {
|
||||
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>> {
|
||||
self.fields.iter().filter(|field| field.attrs.ignore)
|
||||
}
|
||||
|
||||
/// Get a collection of all active types.
|
||||
pub fn active_types(&self) -> impl Iterator<Item = &Type> {
|
||||
self.active_fields().map(|field| &field.data.ty)
|
||||
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<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.
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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<ReflectFieldAttr,
|
|||
/// Recursively parses attribute metadata for things like `#[reflect(ignore)]` and `#[reflect(default = "foo")]`
|
||||
fn parse_meta(args: &mut ReflectFieldAttr, meta: &Meta) -> 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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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<u32>>,
|
||||
) -> 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
|
||||
}
|
||||
|
|
|
@ -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<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;
|
||||
|
||||
/// 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,
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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::<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,
|
||||
};
|
||||
|
||||
use super::SerializationData;
|
||||
|
||||
pub enum Serializable<'a> {
|
||||
Owned(Box<dyn erased_serde::Serialize + 'a>),
|
||||
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::<SerializationData>());
|
||||
|
||||
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::<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.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))?;
|
||||
}
|
||||
|
|
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.
|
||||
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`].
|
||||
|
|
|
@ -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<Box<dyn Reflect>>,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue