Skip to content

Commit

Permalink
Allow #[reflect(skip_serializing)] on variant fields
Browse files Browse the repository at this point in the history
  • Loading branch information
MrGVSV committed Dec 9, 2022
1 parent b58ca87 commit a9e5b8a
Show file tree
Hide file tree
Showing 10 changed files with 404 additions and 87 deletions.
76 changes: 61 additions & 15 deletions crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
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::serialization::SerializationDenylist;
use crate::{utility, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
Expand Down Expand Up @@ -59,7 +58,7 @@ pub(crate) struct ReflectMeta<'a> {
/// ```
pub(crate) struct ReflectStruct<'a> {
meta: ReflectMeta<'a>,
serialization_denylist: BitSet<u32>,
serialization_denylist: SerializationDenylist,
fields: Vec<StructField<'a>>,
}

Expand All @@ -78,6 +77,7 @@ pub(crate) struct ReflectStruct<'a> {
/// ```
pub(crate) struct ReflectEnum<'a> {
meta: ReflectMeta<'a>,
serialization_denylist: SerializationDenylist,
variants: Vec<EnumVariant<'a>>,
}

Expand Down Expand Up @@ -198,8 +198,8 @@ impl<'a> ReflectDerive<'a> {
let fields = Self::collect_struct_fields(&data.fields)?;
let reflect_struct = ReflectStruct {
meta,
serialization_denylist: members_to_serialization_denylist(
fields.iter().map(|v| v.attrs.ignore),
serialization_denylist: SerializationDenylist::from_struct_fields(
fields.iter(),
),
fields,
};
Expand All @@ -213,7 +213,13 @@ impl<'a> ReflectDerive<'a> {
Data::Enum(data) => {
let variants = Self::collect_enum_variants(&data.variants)?;

let reflect_enum = ReflectEnum { meta, variants };
let reflect_enum = ReflectEnum {
meta,
serialization_denylist: SerializationDenylist::from_enum_variants(
variants.iter(),
),
variants,
};
Ok(Self::Enum(reflect_enum))
}
Data::Union(..) => Err(syn::Error::new(
Expand Down Expand Up @@ -340,25 +346,20 @@ impl<'a> ReflectStruct<'a> {
}

/// 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> {
pub fn serialization_denylist(&self) -> &SerializationDenylist {
&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
/// Returns a specific implementation for structs that should be preferred 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.bevy_reflect_path(),
self.meta.traits().idents(),
self.meta.generics(),
Some(&self.serialization_denylist),
Some(self.serialization_denylist()),
)
}

Expand Down Expand Up @@ -406,8 +407,53 @@ impl<'a> ReflectEnum<'a> {
}
}

/// Access the data about which fields should be ignored during serialization.
pub fn serialization_denylist(&self) -> &SerializationDenylist {
&self.serialization_denylist
}

/// The complete set of variants in this enum.
pub fn variants(&self) -> &[EnumVariant<'a>] {
&self.variants
}

/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
///
/// Returns a specific implementation for enums that should be preferred over the generic [`get_type_registration`](crate::ReflectMeta) method.
pub fn get_type_registration(&self) -> proc_macro2::TokenStream {
crate::registration::impl_get_type_registration(
self.meta.type_name(),
self.meta.bevy_reflect_path(),
self.meta.traits().idents(),
self.meta.generics(),
Some(self.serialization_denylist()),
)
}
}

impl<'a> EnumVariant<'a> {
/// Get an iterator of fields which are exposed to the reflection API
#[allow(dead_code)]
pub fn active_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
self.fields()
.iter()
.filter(move |field| field.attrs.ignore.is_active())
}

/// Get an iterator of fields which are ignored by the reflection API
#[allow(dead_code)]
pub fn ignored_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
self.fields()
.iter()
.filter(move |field| field.attrs.ignore.is_ignored())
}

/// The complete set of fields in this variant.
#[allow(dead_code)]
pub fn fields(&self) -> &[StructField<'a>] {
match &self.fields {
EnumVariantFields::Named(fields) | EnumVariantFields::Unnamed(fields) => fields,
EnumVariantFields::Unit => &[],
}
}
}
2 changes: 1 addition & 1 deletion crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
bevy_reflect_path,
);

let get_type_registration_impl = reflect_enum.meta().get_type_registration();
let get_type_registration_impl = reflect_enum.get_type_registration();
let (impl_generics, ty_generics, where_clause) =
reflect_enum.meta().generics().split_for_impl();

Expand Down
1 change: 1 addition & 0 deletions crates/bevy_reflect/bevy_reflect_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ mod from_reflect;
mod impls;
mod reflect_value;
mod registration;
mod serialization;
mod trait_reflection;
mod type_uuid;
mod utility;
Expand Down
46 changes: 38 additions & 8 deletions crates/bevy_reflect/bevy_reflect_derive/src/registration.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Contains code related specifically to Bevy's type registration.
use bit_set::BitSet;
use crate::serialization::SerializationDenylist;
use proc_macro2::Ident;
use quote::quote;
use quote::{quote, ToTokens};
use syn::{Generics, Path};

/// Creates the `GetTypeRegistration` impl for the given type data.
Expand All @@ -11,14 +11,44 @@ pub(crate) fn impl_get_type_registration(
bevy_reflect_path: &Path,
registration_data: &[Ident],
generics: &Generics,
serialization_denylist: Option<&BitSet<u32>>,
serialization_denylist: Option<&SerializationDenylist>,
) -> 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();
quote! {
let ignored_indices = ::core::iter::IntoIterator::into_iter([#(#denylist),*]);
registration.insert::<#bevy_reflect_path::serde::SerializationData>(#bevy_reflect_path::serde::SerializationData::new(ignored_indices));

let serialization_data = serialization_denylist.map(|denylist| match denylist {
SerializationDenylist::Struct(denylist) => {
let denylist = denylist.into_iter();
quote! {
let ignored_indices = ::core::iter::IntoIterator::into_iter([#(#denylist),*]);
registration.insert(
#bevy_reflect_path::serde::SerializationData::Struct(
#bevy_reflect_path::serde::StructSerializationData::new(ignored_indices)
)
);
}
}
SerializationDenylist::Enum(denylist) => {
let mut indices = proc_macro2::TokenStream::new();
for (variant_name, variant_fields) in denylist {
let variant_fields = variant_fields.into_iter();
quote!((
#variant_name,
::core::iter::Iterator::copied(
::core::iter::IntoIterator::into_iter(
&[#(#variant_fields),*] as &[usize]
)
)
),)
.to_tokens(&mut indices);
}
quote! {
let ignored_indices = ::core::iter::IntoIterator::into_iter([#indices]);
registration.insert(
#bevy_reflect_path::serde::SerializationData::Enum(
#bevy_reflect_path::serde::EnumSerializationData::new(ignored_indices)
)
);
}
}
});

Expand Down
76 changes: 76 additions & 0 deletions crates/bevy_reflect/bevy_reflect_derive/src/serialization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use crate::derive_data::{EnumVariant, StructField};
use crate::field_attributes::ReflectIgnoreBehavior;
use bit_set::BitSet;

/// A bitset of fields to ignore for serialization.
///
/// This uses the `#[reflect(skip_serializing)]` and `#[reflect(ignore)]` attributes to determine
/// which fields to mark as skippable for serialization.
///
/// This data is encoded into a bitset over the fields' indices.
///
/// For enums, this also contains the name of the variant associated with the bitset.
pub(crate) enum SerializationDenylist {
Struct(BitSet<usize>),
Enum(Vec<(String, BitSet<usize>)>),
}

impl SerializationDenylist {
/// Create a new bitset for a struct's fields.
///
/// This will return a [`SerializationDenylist::Struct`] value.
pub fn from_struct_fields<'a>(fields: impl Iterator<Item = &'a StructField<'a>>) -> Self {
Self::Struct(Self::generate_bitset(fields))
}

/// Create a new bitset for an enum's fields.
///
/// This will return a [`SerializationDenylist::Enum`] value.
pub fn from_enum_variants<'a>(variants: impl Iterator<Item = &'a EnumVariant<'a>>) -> Self {
Self::Enum(
variants
.map(|variant| {
let name = variant.data.ident.to_string();
let denylist = Self::generate_bitset(variant.fields().iter());
(name, denylist)
})
.collect(),
)
}

/// Converts an iterator over fields 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).
/// Keep in mind, however, that it is always recommended that
/// `#[reflect(skip_serializing)]` comes _before_ `#[reflect(ignore)]`.
/// The example above is meant for demonstration purposes only.
///
fn generate_bitset<'a>(fields: impl Iterator<Item = &'a StructField<'a>>) -> BitSet<usize> {
let mut bitset = BitSet::default();

fields.fold(0, |next_idx, member| match member.attrs.ignore {
ReflectIgnoreBehavior::IgnoreAlways => next_idx,
ReflectIgnoreBehavior::IgnoreSerialization => {
bitset.insert(next_idx);
next_idx + 1
}
ReflectIgnoreBehavior::None => next_idx + 1,
});

bitset
}
}
38 changes: 0 additions & 38 deletions crates/bevy_reflect/bevy_reflect_derive/src/utility.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
//! 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};

Expand Down Expand Up @@ -98,39 +96,3 @@ 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 => next_idx,
ReflectIgnoreBehavior::IgnoreSerialization => {
bitset.insert(next_idx);
next_idx + 1
}
ReflectIgnoreBehavior::None => next_idx + 1,
});

bitset
}
Loading

0 comments on commit a9e5b8a

Please sign in to comment.