Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bevy_reflect: Allow #[reflect(skip_serializing)] on enum variant fields #6767

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 38 additions & 14 deletions crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::container_attributes::ReflectTraits;
use crate::field_attributes::{parse_field_attrs, ReflectFieldAttr};
use crate::fq_std::{FQAny, FQDefault, FQSend, FQSync};
use crate::utility::{members_to_serialization_denylist, WhereClauseOptions};
use bit_set::BitSet;
use crate::utility::WhereClauseOptions;
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 @@ -60,7 +60,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 @@ -79,6 +79,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 @@ -199,8 +200,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 @@ -214,7 +215,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 @@ -345,10 +352,7 @@ 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
}

Expand All @@ -359,15 +363,13 @@ impl<'a> ReflectStruct<'a> {
&self,
where_clause_options: &WhereClauseOptions,
) -> 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(),
where_clause_options,
Some(&self.serialization_denylist),
Some(self.serialization_denylist()),
)
}

Expand Down Expand Up @@ -430,6 +432,11 @@ 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
Expand Down Expand Up @@ -472,6 +479,23 @@ impl<'a> ReflectEnum<'a> {
ignored_trait_bounds: quote! { #FQAny + #FQSend + #FQSync + #FQDefault },
}
}

/// 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,
where_clause_options: &WhereClauseOptions,
) -> 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(),
where_clause_options,
Some(self.serialization_denylist()),
)
}
}

impl<'a> EnumVariant<'a> {
Expand Down
5 changes: 2 additions & 3 deletions crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
bevy_reflect_path,
);

let get_type_registration_impl = reflect_enum
.meta()
.get_type_registration(&where_clause_options);
let get_type_registration_impl = reflect_enum.get_type_registration(&where_clause_options);

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
49 changes: 41 additions & 8 deletions crates/bevy_reflect/bevy_reflect_derive/src/registration.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
//! Contains code related specifically to Bevy's type registration.

use crate::serialization::SerializationDenylist;
use crate::utility::{extend_where_clause, WhereClauseOptions};
use bit_set::BitSet;
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 @@ -14,14 +14,47 @@ pub(crate) fn impl_get_type_registration(
registration_data: &[Ident],
generics: &Generics,
where_clause_options: &WhereClauseOptions,
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) => {
// This will contain a list of tuples containing:
// 1. The name of the variant
// 2. An iterator of the skipped indices
let mut indices = proc_macro2::TokenStream::new();
MrGVSV marked this conversation as resolved.
Show resolved Hide resolved
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 quote::quote;
use syn::{Member, Path, Type, WhereClause};
Expand Down Expand Up @@ -176,39 +174,3 @@ impl<T> ResultSifter<T> {
}
}
}

/// Converts an iterator over ignore behavior 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