# Objective Fixes #5101 Alternative to #6511 ## Solution Corrected the behavior for ignored fields in `FromReflect`, which was previously using the incorrect field indexes. Similarly, fields marked with `#[reflect(skip_serializing)]` no longer break when using `FromReflect` after deserialization. This was done by modifying `SerializationData` to store a function pointer that can later be used to generate a default instance of the skipped field during deserialization. The function pointer points to a function generated by the derive macro using the behavior designated by `#[reflect(default)]` (or just `Default` if none provided). The entire output of the macro is now wrapped in an [unnamed constant](https://doc.rust-lang.org/stable/reference/items/constant-items.html#unnamed-constant) which keeps this behavior hygienic. #### Rationale The biggest downside to this approach is that it requires fields marked `#[reflect(skip_serializing)]` to provide the ability to create a default instance— either via a `Default` impl or by specifying a custom one. While this isn't great, I think it might be justified by the fact that we really need to create this value when using `FromReflect` on a deserialized object. And we need to do this _during_ deserialization because after that (at least for tuples and tuple structs) we lose information about which field is which: _"is the value at index 1 in this `DynamicTupleStruct` the actual value for index 1 or is it really the value for index 2 since index 1 is skippable...?"_ #### Alternatives An alternative would be to store `Option<Box<dyn Reflect>>` within `DynamicTuple` and `DynamicTupleStruct` instead of just `Box<dyn Reflect>`. This would allow us to insert "empty"/"missing" fields during deserialization, thus saving the positional information of the skipped fields. However, this may require changing the API of `Tuple` and `TupleStruct` such that they can account for their dynamic counterparts returning `None` for a skipped field. In practice this would probably mean exposing the `Option`-ness of the dynamics onto implementors via methods like `Tuple::drain` or `TupleStruct::field`. Personally, I think requiring `Default` would be better than muddying up the API to account for these special cases. But I'm open to trying out this other approach if the community feels that it's better. --- ## Changelog ### Public Changes #### Fixed - The behaviors of `#[reflect(ignore)]` and `#[reflect(skip_serializing)]` are no longer dependent on field order #### Changed - Fields marked with `#[reflect(skip_serializing)]` now need to either implement `Default` or specify a custom default function using `#[reflect(default = "path::to::some_func")]` - Deserializing a type with fields marked `#[reflect(skip_serializing)]` will now include that field initialized to its specified default value - `SerializationData::new` now takes the new `SkippedField` struct along with the skipped field index - Renamed `SerializationData::is_ignored_field` to `SerializationData::is_field_skipped` #### Added - Added `SkippedField` struct - Added methods `SerializationData::generate_default` and `SerializationData::iter_skipped` ### Internal Changes #### Changed - Replaced `members_to_serialization_denylist` and `BitSet<u32>` with `SerializationDataDef` - The `Reflect` derive is more hygienic as it now outputs within an [unnamed constant](https://doc.rust-lang.org/stable/reference/items/constant-items.html#unnamed-constant) - `StructField::index` has been split up into `StructField::declaration_index` and `StructField::reflection_index` #### Removed - Removed `bitset` dependency ## Migration Guide * Fields marked `#[reflect(skip_serializing)]` now must implement `Default` or specify a custom default function with `#[reflect(default = "path::to::some_func")]` ```rust #[derive(Reflect)] struct MyStruct { #[reflect(skip_serializing)] #[reflect(default = "get_foo_default")] foo: Foo, // <- `Foo` does not impl `Default` so requires a custom function #[reflect(skip_serializing)] bar: Bar, // <- `Bar` impls `Default` } #[derive(Reflect)] struct Foo(i32); #[derive(Reflect, Default)] struct Bar(i32); fn get_foo_default() -> Foo { Foo(123) } ``` * `SerializationData::new` has been changed to expect an iterator of `(usize, SkippedField)` rather than one of just `usize` ```rust // BEFORE SerializationData::new([0, 3].into_iter()); // AFTER SerializationData::new([ (0, SkippedField::new(field_0_default_fn)), (3, SkippedField::new(field_3_default_fn)), ].into_iter()); ``` * `Serialization::is_ignored_field` has been renamed to `Serialization::is_field_skipped` * Fields marked `#[reflect(skip_serializing)]` are now included in deserialization output. This may affect logic that expected those fields to be absent.
50 lines
2.1 KiB
Rust
50 lines
2.1 KiB
Rust
//! Contains code related specifically to Bevy's type registration.
|
|
|
|
use crate::derive_data::ReflectMeta;
|
|
use crate::serialization::SerializationDataDef;
|
|
use crate::utility::{extend_where_clause, WhereClauseOptions};
|
|
use quote::quote;
|
|
|
|
/// Creates the `GetTypeRegistration` impl for the given type data.
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub(crate) fn impl_get_type_registration(
|
|
meta: &ReflectMeta,
|
|
where_clause_options: &WhereClauseOptions,
|
|
serialization_data: Option<&SerializationDataDef>,
|
|
) -> proc_macro2::TokenStream {
|
|
let type_path = meta.type_path();
|
|
let bevy_reflect_path = meta.bevy_reflect_path();
|
|
let registration_data = meta.traits().idents();
|
|
let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl();
|
|
let where_reflect_clause = extend_where_clause(where_clause, where_clause_options);
|
|
|
|
let from_reflect_data = if meta.from_reflect().should_auto_derive() {
|
|
Some(quote! {
|
|
registration.insert::<#bevy_reflect_path::ReflectFromReflect>(#bevy_reflect_path::FromType::<Self>::from_type());
|
|
})
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let serialization_data = serialization_data.map(|data| {
|
|
let serialization_data = data.as_serialization_data(bevy_reflect_path);
|
|
quote! {
|
|
registration.insert::<#bevy_reflect_path::serde::SerializationData>(#serialization_data);
|
|
}
|
|
});
|
|
|
|
quote! {
|
|
#[allow(unused_mut)]
|
|
impl #impl_generics #bevy_reflect_path::GetTypeRegistration for #type_path #ty_generics #where_reflect_clause {
|
|
fn get_type_registration() -> #bevy_reflect_path::TypeRegistration {
|
|
let mut registration = #bevy_reflect_path::TypeRegistration::of::<Self>();
|
|
registration.insert::<#bevy_reflect_path::ReflectFromPtr>(#bevy_reflect_path::FromType::<Self>::from_type());
|
|
#from_reflect_data
|
|
#serialization_data
|
|
#(registration.insert::<#registration_data>(#bevy_reflect_path::FromType::<Self>::from_type());)*
|
|
registration
|
|
}
|
|
}
|
|
}
|
|
}
|