Skip to content

Commit d30d3e7

Browse files
committed
bevy_reflect: Improve serialization format even more (#5723)
> Note: This is rebased off #4561 and can be viewed as a competitor to that PR. See `Comparison with #4561` section for details. # Objective The current serialization format used by `bevy_reflect` is both verbose and error-prone. Taking the following structs[^1] for example: ```rust // -- src/inventory.rs #[derive(Reflect)] struct Inventory { id: String, max_storage: usize, items: Vec<Item> } #[derive(Reflect)] struct Item { name: String } ``` Given an inventory of a single item, this would serialize to something like: ```rust // -- assets/inventory.ron { "type": "my_game::inventory::Inventory", "struct": { "id": { "type": "alloc::string::String", "value": "inv001", }, "max_storage": { "type": "usize", "value": 10 }, "items": { "type": "alloc::vec::Vec<alloc::string::String>", "list": [ { "type": "my_game::inventory::Item", "struct": { "name": { "type": "alloc::string::String", "value": "Pickaxe" }, }, }, ], }, }, } ``` Aside from being really long and difficult to read, it also has a few "gotchas" that users need to be aware of if they want to edit the file manually. A major one is the requirement that you use the proper keys for a given type. For structs, you need `"struct"`. For lists, `"list"`. For tuple structs, `"tuple_struct"`. And so on. It also ***requires*** that the `"type"` entry come before the actual data. Despite being a map— which in programming is almost always orderless by default— the entries need to be in a particular order. Failure to follow the ordering convention results in a failure to deserialize the data. This makes it very prone to errors and annoyances. ## Solution Using #4042, we can remove a lot of the boilerplate and metadata needed by this older system. Since we now have static access to type information, we can simplify our serialized data to look like: ```rust // -- assets/inventory.ron { "my_game::inventory::Inventory": ( id: "inv001", max_storage: 10, items: [ ( name: "Pickaxe" ), ], ), } ``` This is much more digestible and a lot less error-prone (no more key requirements and no more extra type names). Additionally, it is a lot more familiar to users as it follows conventional serde mechanics. For example, the struct is represented with `(...)` when serialized to RON. #### Custom Serialization Additionally, this PR adds the opt-in ability to specify a custom serde implementation to be used rather than the one created via reflection. For example[^1]: ```rust // -- src/inventory.rs #[derive(Reflect, Serialize)] #[reflect(Serialize)] struct Item { #[serde(alias = "id")] name: String } ``` ```rust // -- assets/inventory.ron { "my_game::inventory::Inventory": ( id: "inv001", max_storage: 10, items: [ ( id: "Pickaxe" ), ], ), }, ``` By allowing users to define their own serialization methods, we do two things: 1. We give more control over how data is serialized/deserialized to the end user 2. We avoid having to re-define serde's attributes and forcing users to apply both (e.g. we don't need a `#[reflect(alias)]` attribute). ### Improved Formats One of the improvements this PR provides is the ability to represent data in ways that are more conventional and/or familiar to users. Many users are familiar with RON so here are some of the ways we can now represent data in RON: ###### Structs ```js { "my_crate::Foo": ( bar: 123 ) } // OR { "my_crate::Foo": Foo( bar: 123 ) } ``` <details> <summary>Old Format</summary> ```js { "type": "my_crate::Foo", "struct": { "bar": { "type": "usize", "value": 123 } } } ``` </details> ###### Tuples ```js { "(f32, f32)": (1.0, 2.0) } ``` <details> <summary>Old Format</summary> ```js { "type": "(f32, f32)", "tuple": [ { "type": "f32", "value": 1.0 }, { "type": "f32", "value": 2.0 } ] } ``` </details> ###### Tuple Structs ```js { "my_crate::Bar": ("Hello World!") } // OR { "my_crate::Bar": Bar("Hello World!") } ``` <details> <summary>Old Format</summary> ```js { "type": "my_crate::Bar", "tuple_struct": [ { "type": "alloc::string::String", "value": "Hello World!" } ] } ``` </details> ###### Arrays It may be a bit surprising to some, but arrays now also use the tuple format. This is because they essentially _are_ tuples (a sequence of values with a fixed size), but only allow for homogenous types. Additionally, this is how RON handles them and is probably a result of the 32-capacity limit imposed on them (both by [serde](https://docs.rs/serde/latest/serde/trait.Serialize.html#impl-Serialize-for-%5BT%3B%2032%5D) and by [bevy_reflect](https://docs.rs/bevy/latest/bevy/reflect/trait.GetTypeRegistration.html#impl-GetTypeRegistration-for-%5BT%3B%2032%5D)). ```js { "[i32; 3]": (1, 2, 3) } ``` <details> <summary>Old Format</summary> ```js { "type": "[i32; 3]", "array": [ { "type": "i32", "value": 1 }, { "type": "i32", "value": 2 }, { "type": "i32", "value": 3 } ] } ``` </details> ###### Enums To make things simple, I'll just put a struct variant here, but the style applies to all variant types: ```js { "my_crate::ItemType": Consumable( name: "Healing potion" ) } ``` <details> <summary>Old Format</summary> ```js { "type": "my_crate::ItemType", "enum": { "variant": "Consumable", "struct": { "name": { "type": "alloc::string::String", "value": "Healing potion" } } } } ``` </details> ### Comparison with #4561 This PR is a rebased version of #4561. The reason for the split between the two is because this PR creates a _very_ different scene format. You may notice that the PR descriptions for either PR are pretty similar. This was done to better convey the changes depending on which (if any) gets merged first. If #4561 makes it in first, I will update this PR description accordingly. --- ## Changelog * Re-worked serialization/deserialization for reflected types * Added `TypedReflectDeserializer` for deserializing data with known `TypeInfo` * Renamed `ReflectDeserializer` to `UntypedReflectDeserializer` * ~~Replaced usages of `deserialize_any` with `deserialize_map` for non-self-describing formats~~ Reverted this change since there are still some issues that need to be sorted out (in a separate PR). By reverting this, crates like `bincode` can throw an error when attempting to deserialize non-self-describing formats (`bincode` results in `DeserializeAnyNotSupported`) * Structs, tuples, tuple structs, arrays, and enums are now all de/serialized using conventional serde methods ## Migration Guide * This PR reduces the verbosity of the scene format. Scenes will need to be updated accordingly: ```js // Old format { "type": "my_game::item::Item", "struct": { "id": { "type": "alloc::string::String", "value": "bevycraft:stone", }, "tags": { "type": "alloc::vec::Vec<alloc::string::String>", "list": [ { "type": "alloc::string::String", "value": "material" }, ], }, } // New format { "my_game::item::Item": ( id: "bevycraft:stone", tags: ["material"] ) } ``` [^1]: Some derives omitted for brevity.
1 parent bc863ce commit d30d3e7

File tree

22 files changed

+1555
-913
lines changed

22 files changed

+1555
-913
lines changed

assets/scenes/load_scene_example.scn.ron

+24-44
Original file line numberDiff line numberDiff line change
@@ -3,61 +3,41 @@
33
entity: 0,
44
components: [
55
{
6-
"type": "bevy_transform::components::transform::Transform",
7-
"struct": {
8-
"translation": {
9-
"type": "glam::f32::vec3::Vec3",
10-
"value": (0.0, 0.0, 0.0),
11-
},
12-
"rotation": {
13-
"type": "glam::f32::sse2::quat::Quat",
14-
"value": (0.0, 0.0, 0.0, 1.0),
15-
},
16-
"scale": {
17-
"type": "glam::f32::vec3::Vec3",
18-
"value": (1.0, 1.0, 1.0),
19-
},
20-
},
6+
"bevy_transform::components::transform::Transform": (
7+
translation: (
8+
x: 0.0,
9+
y: 0.0,
10+
z: 0.0
11+
),
12+
rotation: (0.0, 0.0, 0.0, 1.0),
13+
scale: (
14+
x: 1.0,
15+
y: 1.0,
16+
z: 1.0
17+
),
18+
),
2119
},
2220
{
23-
"type": "scene::ComponentB",
24-
"struct": {
25-
"value": {
26-
"type": "alloc::string::String",
27-
"value": "hello",
28-
},
29-
},
21+
"scene::ComponentB": (
22+
value: "hello",
23+
),
3024
},
3125
{
32-
"type": "scene::ComponentA",
33-
"struct": {
34-
"x": {
35-
"type": "f32",
36-
"value": 1.0,
37-
},
38-
"y": {
39-
"type": "f32",
40-
"value": 2.0,
41-
},
42-
},
26+
"scene::ComponentA": (
27+
x: 1.0,
28+
y: 2.0,
29+
),
4330
},
4431
],
4532
),
4633
(
4734
entity: 1,
4835
components: [
4936
{
50-
"type": "scene::ComponentA",
51-
"struct": {
52-
"x": {
53-
"type": "f32",
54-
"value": 3.0,
55-
},
56-
"y": {
57-
"type": "f32",
58-
"value": 4.0,
59-
},
60-
},
37+
"scene::ComponentA": (
38+
x: 3.0,
39+
y: 4.0,
40+
),
6141
},
6242
],
6343
),

crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs

+23-6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
2121
enum_name_at,
2222
enum_field_len,
2323
enum_variant_name,
24+
enum_variant_index,
2425
enum_variant_type,
2526
} = generate_impls(reflect_enum, &ref_index, &ref_name);
2627

@@ -53,12 +54,13 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
5354
}
5455
});
5556

57+
let string_name = enum_name.to_string();
5658
let typed_impl = impl_typed(
5759
enum_name,
5860
reflect_enum.meta().generics(),
5961
quote! {
6062
let variants = [#(#variant_info),*];
61-
let info = #bevy_reflect_path::EnumInfo::new::<Self>(&variants);
63+
let info = #bevy_reflect_path::EnumInfo::new::<Self>(#string_name, &variants);
6264
#bevy_reflect_path::TypeInfo::Enum(info)
6365
},
6466
bevy_reflect_path,
@@ -136,6 +138,14 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
136138
}
137139
}
138140

141+
#[inline]
142+
fn variant_index(&self) -> usize {
143+
match self {
144+
#(#enum_variant_index,)*
145+
_ => unreachable!(),
146+
}
147+
}
148+
139149
#[inline]
140150
fn variant_type(&self) -> #bevy_reflect_path::VariantType {
141151
match self {
@@ -254,6 +264,7 @@ struct EnumImpls {
254264
enum_name_at: Vec<proc_macro2::TokenStream>,
255265
enum_field_len: Vec<proc_macro2::TokenStream>,
256266
enum_variant_name: Vec<proc_macro2::TokenStream>,
267+
enum_variant_index: Vec<proc_macro2::TokenStream>,
257268
enum_variant_type: Vec<proc_macro2::TokenStream>,
258269
}
259270

@@ -267,13 +278,21 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden
267278
let mut enum_name_at = Vec::new();
268279
let mut enum_field_len = Vec::new();
269280
let mut enum_variant_name = Vec::new();
281+
let mut enum_variant_index = Vec::new();
270282
let mut enum_variant_type = Vec::new();
271283

272-
for variant in reflect_enum.variants() {
284+
for (variant_index, variant) in reflect_enum.variants().iter().enumerate() {
273285
let ident = &variant.data.ident;
274286
let name = ident.to_string();
275287
let unit = reflect_enum.get_unit(ident);
276288

289+
enum_variant_name.push(quote! {
290+
#unit{..} => #name
291+
});
292+
enum_variant_index.push(quote! {
293+
#unit{..} => #variant_index
294+
});
295+
277296
fn for_fields(
278297
fields: &[StructField],
279298
mut generate_for_field: impl FnMut(usize, usize, &StructField) -> proc_macro2::TokenStream,
@@ -301,9 +320,6 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden
301320
enum_field_len.push(quote! {
302321
#unit{..} => #field_len
303322
});
304-
enum_variant_name.push(quote! {
305-
#unit{..} => #name
306-
});
307323
enum_variant_type.push(quote! {
308324
#unit{..} => #bevy_reflect_path::VariantType::#variant
309325
});
@@ -342,7 +358,7 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden
342358
});
343359

344360
let field_ty = &field.data.ty;
345-
quote! { #bevy_reflect_path::NamedField::new::<#field_ty, _>(#field_name) }
361+
quote! { #bevy_reflect_path::NamedField::new::<#field_ty>(#field_name) }
346362
});
347363
let arguments = quote!(#name, &[ #(#argument),* ]);
348364
add_fields_branch("Struct", "StructVariantInfo", arguments, field_len);
@@ -358,6 +374,7 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden
358374
enum_name_at,
359375
enum_field_len,
360376
enum_variant_name,
377+
enum_variant_index,
361378
enum_variant_type,
362379
}
363380
}

crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,15 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream {
5151
}
5252
});
5353

54+
let string_name = struct_name.to_string();
5455
let typed_impl = impl_typed(
5556
struct_name,
5657
reflect_struct.meta().generics(),
5758
quote! {
5859
let fields = [
59-
#(#bevy_reflect_path::NamedField::new::<#field_types, _>(#field_names),)*
60+
#(#bevy_reflect_path::NamedField::new::<#field_types>(#field_names),)*
6061
];
61-
let info = #bevy_reflect_path::StructInfo::new::<Self>(&fields);
62+
let info = #bevy_reflect_path::StructInfo::new::<Self>(#string_name, &fields);
6263
#bevy_reflect_path::TypeInfo::Struct(info)
6364
},
6465
bevy_reflect_path,

crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,15 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream {
3535
}
3636
});
3737

38+
let string_name = struct_name.to_string();
3839
let typed_impl = impl_typed(
3940
struct_name,
4041
reflect_struct.meta().generics(),
4142
quote! {
4243
let fields = [
4344
#(#bevy_reflect_path::UnnamedField::new::<#field_types>(#field_idents),)*
4445
];
45-
let info = #bevy_reflect_path::TupleStructInfo::new::<Self>(&fields);
46+
let info = #bevy_reflect_path::TupleStructInfo::new::<Self>(#string_name, &fields);
4647
#bevy_reflect_path::TypeInfo::TupleStruct(info)
4748
},
4849
bevy_reflect_path,

crates/bevy_reflect/bevy_reflect_derive/src/utility.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,7 @@ where
124124
let mut bitset = BitSet::default();
125125

126126
member_iter.fold(0, |next_idx, member| match member {
127-
ReflectIgnoreBehavior::IgnoreAlways => {
128-
bitset.insert(next_idx);
129-
next_idx
130-
}
127+
ReflectIgnoreBehavior::IgnoreAlways => next_idx,
131128
ReflectIgnoreBehavior::IgnoreSerialization => {
132129
bitset.insert(next_idx);
133130
next_idx + 1

crates/bevy_reflect/src/enums/dynamic_enum.rs

+48-3
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ impl From<()> for DynamicVariant {
7777
pub struct DynamicEnum {
7878
name: String,
7979
variant_name: String,
80+
variant_index: usize,
8081
variant: DynamicVariant,
8182
}
8283

@@ -96,6 +97,30 @@ impl DynamicEnum {
9697
) -> Self {
9798
Self {
9899
name: name.into(),
100+
variant_index: 0,
101+
variant_name: variant_name.into(),
102+
variant: variant.into(),
103+
}
104+
}
105+
106+
/// Create a new [`DynamicEnum`] with a variant index to represent an enum at runtime.
107+
///
108+
/// # Arguments
109+
///
110+
/// * `name`: The type name of the enum
111+
/// * `variant_index`: The index of the variant to set
112+
/// * `variant_name`: The name of the variant to set
113+
/// * `variant`: The variant data
114+
///
115+
pub fn new_with_index<I: Into<String>, V: Into<DynamicVariant>>(
116+
name: I,
117+
variant_index: usize,
118+
variant_name: I,
119+
variant: V,
120+
) -> Self {
121+
Self {
122+
name: name.into(),
123+
variant_index,
99124
variant_name: variant_name.into(),
100125
variant: variant.into(),
101126
}
@@ -117,6 +142,18 @@ impl DynamicEnum {
117142
self.variant = variant.into();
118143
}
119144

145+
/// Set the current enum variant represented by this struct along with its variant index.
146+
pub fn set_variant_with_index<I: Into<String>, V: Into<DynamicVariant>>(
147+
&mut self,
148+
variant_index: usize,
149+
name: I,
150+
variant: V,
151+
) {
152+
self.variant_index = variant_index;
153+
self.variant_name = name.into();
154+
self.variant = variant.into();
155+
}
156+
120157
/// Create a [`DynamicEnum`] from an existing one.
121158
///
122159
/// This is functionally the same as [`DynamicEnum::from_ref`] except it takes an owned value.
@@ -129,8 +166,9 @@ impl DynamicEnum {
129166
/// This is functionally the same as [`DynamicEnum::from`] except it takes a reference.
130167
pub fn from_ref<TEnum: Enum>(value: &TEnum) -> Self {
131168
match value.variant_type() {
132-
VariantType::Unit => DynamicEnum::new(
169+
VariantType::Unit => DynamicEnum::new_with_index(
133170
value.type_name(),
171+
value.variant_index(),
134172
value.variant_name(),
135173
DynamicVariant::Unit,
136174
),
@@ -139,8 +177,9 @@ impl DynamicEnum {
139177
for field in value.iter_fields() {
140178
data.insert_boxed(field.value().clone_value());
141179
}
142-
DynamicEnum::new(
180+
DynamicEnum::new_with_index(
143181
value.type_name(),
182+
value.variant_index(),
144183
value.variant_name(),
145184
DynamicVariant::Tuple(data),
146185
)
@@ -151,8 +190,9 @@ impl DynamicEnum {
151190
let name = field.name().unwrap();
152191
data.insert_boxed(name, field.value().clone_value());
153192
}
154-
DynamicEnum::new(
193+
DynamicEnum::new_with_index(
155194
value.type_name(),
195+
value.variant_index(),
156196
value.variant_name(),
157197
DynamicVariant::Struct(data),
158198
)
@@ -226,6 +266,10 @@ impl Enum for DynamicEnum {
226266
&self.variant_name
227267
}
228268

269+
fn variant_index(&self) -> usize {
270+
self.variant_index
271+
}
272+
229273
fn variant_type(&self) -> VariantType {
230274
match &self.variant {
231275
DynamicVariant::Unit => VariantType::Unit,
@@ -237,6 +281,7 @@ impl Enum for DynamicEnum {
237281
fn clone_dynamic(&self) -> DynamicEnum {
238282
Self {
239283
name: self.name.clone(),
284+
variant_index: self.variant_index,
240285
variant_name: self.variant_name.clone(),
241286
variant: self.variant.clone(),
242287
}

0 commit comments

Comments
 (0)