mirror of
https://github.com/bevyengine/bevy
synced 2025-01-05 09:48:55 +00:00
10 commits
Author | SHA1 | Message | Date | |
---|---|---|---|---|
Gino Valente
|
19fc1f1ed2 |
bevy_reflect: Fix DynamicScene not respecting component registrations during serialization (#6288)
# Objective
When running the scene example, you might notice we end up printing out the following:
```ron
// ...
{
"scene::ComponentB": (
value: "hello",
_time_since_startup: (
secs: 0,
nanos: 0,
),
),
},
// ...
```
We should not be printing out `_time_since_startup` as the field is marked with `#[reflect(skip_serializing)]`:
```rust
#[derive(Component, Reflect)]
#[reflect(Component)]
struct ComponentB {
pub value: String,
#[reflect(skip_serializing)]
pub _time_since_startup: Duration,
}
```
This is because when we create the `DynamicScene`, we end up calling `Reflect::clone_value`:
|
||
Gino Valente
|
d30d3e752a |
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. |
||
Maksymilian Mozolewski
|
ac1aebed5e |
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 |
||
Gino Valente
|
15826d6019 |
bevy_reflect: Reflect enums (#4761)
# Objective
> This is a revival of #1347. Credit for the original PR should go to @Davier.
Currently, enums are treated as `ReflectRef::Value` types by `bevy_reflect`. Obviously, there needs to be better a better representation for enums using the reflection API.
## Solution
Based on prior work from @Davier, an `Enum` trait has been added as well as the ability to automatically implement it via the `Reflect` derive macro. This allows enums to be expressed dynamically:
```rust
#[derive(Reflect)]
enum Foo {
A,
B(usize),
C { value: f32 },
}
let mut foo = Foo::B(123);
assert_eq!("B", foo.variant_name());
assert_eq!(1, foo.field_len());
let new_value = DynamicEnum::from(Foo::C { value: 1.23 });
foo.apply(&new_value);
assert_eq!(Foo::C{value: 1.23}, foo);
```
### Features
#### Derive Macro
Use the `#[derive(Reflect)]` macro to automatically implement the `Enum` trait for enum definitions. Optionally, you can use `#[reflect(ignore)]` with both variants and variant fields, just like you can with structs. These ignored items will not be considered as part of the reflection and cannot be accessed via reflection.
```rust
#[derive(Reflect)]
enum TestEnum {
A,
// Uncomment to ignore all of `B`
// #[reflect(ignore)]
B(usize),
C {
// Uncomment to ignore only field `foo` of `C`
// #[reflect(ignore)]
foo: f32,
bar: bool,
},
}
```
#### Dynamic Enums
Enums may be created/represented dynamically via the `DynamicEnum` struct. The main purpose of this struct is to allow enums to be deserialized into a partial state and to allow dynamic patching. In order to ensure conversion from a `DynamicEnum` to a concrete enum type goes smoothly, be sure to add `FromReflect` to your derive macro.
```rust
let mut value = TestEnum::A;
// Create from a concrete instance
let dyn_enum = DynamicEnum::from(TestEnum::B(123));
value.apply(&dyn_enum);
assert_eq!(TestEnum::B(123), value);
// Create a purely dynamic instance
let dyn_enum = DynamicEnum::new("TestEnum", "A", ());
value.apply(&dyn_enum);
assert_eq!(TestEnum::A, value);
```
#### Variants
An enum value is always represented as one of its variants— never the enum in its entirety.
```rust
let value = TestEnum::A;
assert_eq!("A", value.variant_name());
// Since we are using the `A` variant, we cannot also be the `B` variant
assert_ne!("B", value.variant_name());
```
All variant types are representable within the `Enum` trait: unit, struct, and tuple.
You can get the current type like:
```rust
match value.variant_type() {
VariantType::Unit => println!("A unit variant!"),
VariantType::Struct => println!("A struct variant!"),
VariantType::Tuple => println!("A tuple variant!"),
}
```
> Notice that they don't contain any values representing the fields. These are purely tags.
If a variant has them, you can access the fields as well:
```rust
let mut value = TestEnum::C {
foo: 1.23,
bar: false
};
// Read/write specific fields
*value.field_mut("bar").unwrap() = true;
// Iterate over the entire collection of fields
for field in value.iter_fields() {
println!("{} = {:?}", field.name(), field.value());
}
```
#### Variant Swapping
It might seem odd to group all variant types under a single trait (why allow `iter_fields` on a unit variant?), but the reason this was done ~~is to easily allow *variant swapping*.~~ As I was recently drafting up the **Design Decisions** section, I discovered that other solutions could have been made to work with variant swapping. So while there are reasons to keep the all-in-one approach, variant swapping is _not_ one of them.
```rust
let mut value: Box<dyn Enum> = Box::new(TestEnum::A);
value.set(Box::new(TestEnum::B(123))).unwrap();
```
#### Serialization
Enums can be serialized and deserialized via reflection without needing to implement `Serialize` or `Deserialize` themselves (which can save thousands of lines of generated code). Below are the ways an enum can be serialized.
> Note, like the rest of reflection-based serialization, the order of the keys in these representations is important!
##### Unit
```json
{
"type": "my_crate::TestEnum",
"enum": {
"variant": "A"
}
}
```
##### Tuple
```json
{
"type": "my_crate::TestEnum",
"enum": {
"variant": "B",
"tuple": [
{
"type": "usize",
"value": 123
}
]
}
}
```
<details>
<summary>Effects on Option</summary>
This ends up making `Option` look a little ugly:
```json
{
"type": "core::option::Option<usize>",
"enum": {
"variant": "Some",
"tuple": [
{
"type": "usize",
"value": 123
}
]
}
}
```
</details>
##### Struct
```json
{
"type": "my_crate::TestEnum",
"enum": {
"variant": "C",
"struct": {
"foo": {
"type": "f32",
"value": 1.23
},
"bar": {
"type": "bool",
"value": false
}
}
}
}
```
## Design Decisions
<details>
<summary><strong>View Section</strong></summary>
This section is here to provide some context for why certain decisions were made for this PR, alternatives that could have been used instead, and what could be improved upon in the future.
### Variant Representation
One of the biggest decisions was to decide on how to represent variants. The current design uses a "all-in-one" design where unit, tuple, and struct variants are all simultaneously represented by the `Enum` trait. This is not the only way it could have been done, though.
#### Alternatives
##### 1. Variant Traits
One way of representing variants would be to define traits for each variant, implementing them whenever an enum featured at least one instance of them. This would allow us to define variants like:
```rust
pub trait Enum: Reflect {
fn variant(&self) -> Variant;
}
pub enum Variant<'a> {
Unit,
Tuple(&'a dyn TupleVariant),
Struct(&'a dyn StructVariant),
}
pub trait TupleVariant {
fn field_len(&self) -> usize;
// ...
}
```
And then do things like:
```rust
fn get_tuple_len(foo: &dyn Enum) -> usize {
match foo.variant() {
Variant::Tuple(tuple) => tuple.field_len(),
_ => panic!("not a tuple variant!")
}
}
```
The reason this PR does not go with this approach is because of the fact that variants are not separate types. In other words, we cannot implement traits on specific variants— these cover the *entire* enum. This means we offer an easy footgun:
```rust
let foo: Option<i32> = None;
let my_enum = Box::new(foo) as Box<dyn TupleVariant>;
```
Here, `my_enum` contains `foo`, which is a unit variant. However, since we need to implement `TupleVariant` for `Option` as a whole, it's possible to perform such a cast. This is obviously wrong, but could easily go unnoticed. So unfortunately, this makes it not a good candidate for representing variants.
##### 2. Variant Structs
To get around the issue of traits necessarily needing to apply to both the enum and its variants, we could instead use structs that are created on a per-variant basis. This was also considered but was ultimately [[removed](
|
||
James Liu
|
5498ef81fb |
bevy_reflect: support map insertion (#5173)
# Objective This is a rebase of #3701 which is currently scheduled for 0.8 but is marked for adoption. > Fixes https://github.com/bevyengine/bevy/discussions/3609 ## Solution > - add an `insert_boxed()` method on the `Map` trait > - implement it for `HashMap` using a new `FromReflect` generic bound > - add a `map_apply()` helper method to implement `Map::apply()`, that inserts new values instead of ignoring them --- ## Changelog TODO Co-authored-by: james7132 <contact@jamessliu.com> |
||
Jakob Hellermann
|
218b0fd3b6 |
bevy_reflect : put serialize into external ReflectSerialize type (#4782)
builds on top of #4780 # Objective `Reflect` and `Serialize` are currently very tied together because `Reflect` has a `fn serialize(&self) -> Option<Serializable<'_>>` method. Because of that, we can either implement `Reflect` for types like `Option<T>` with `T: Serialize` and have `fn serialize` be implemented, or without the bound but having `fn serialize` return `None`. By separating `ReflectSerialize` into a separate type (like how it already is for `ReflectDeserialize`, `ReflectDefault`), we could separately `.register::<Option<T>>()` and `.register_data::<Option<T>, ReflectSerialize>()` only if the type `T: Serialize`. This PR does not change the registration but allows it to be changed in a future PR. ## Solution - add the type ```rust struct ReflectSerialize { .. } impl<T: Reflect + Serialize> FromType<T> for ReflectSerialize { .. } ``` - remove `#[reflect(Serialize)]` special casing. - when serializing reflect value types, look for `ReflectSerialize` in the `TypeRegistry` instead of calling `value.serialize()` |
||
MrGVSV
|
acbee7795d |
bevy_reflect: Reflect arrays (#4701)
# Objective > ℹ️ **Note**: This is a rebased version of #2383. A large portion of it has not been touched (only a few minor changes) so that any additional discussion may happen here. All credit should go to @NathanSWard for their work on the original PR. - Currently reflection is not supported for arrays. - Fixes #1213 ## Solution * Implement reflection for arrays via the `Array` trait. * Note, `Array` is different from `List` in the way that you cannot push elements onto an array as they are statically sized. * Now `List` is defined as a sub-trait of `Array`. --- ## Changelog * Added the `Array` reflection trait * Allows arrays up to length 32 to be reflected via the `Array` trait ## Migration Guide * The `List` trait now has the `Array` supertrait. This means that `clone_dynamic` will need to specify which version to use: ```rust // Before let cloned = my_list.clone_dynamic(); // After let cloned = List::clone_dynamic(&my_list); ``` * All implementers of `List` will now need to implement `Array` (this mostly involves moving the existing methods to the `Array` impl) Co-authored-by: NathanW <nathansward@comcast.net> Co-authored-by: MrGVSV <49806985+MrGVSV@users.noreply.github.com> |
||
Christopher Durham
|
a60fe30ada |
Avoid some format! into immediate format! (#2913)
# Objective - Avoid usages of `format!` that ~immediately get passed to another `format!`. This avoids a temporary allocation and is just generally cleaner. ## Solution - `bevy_derive::shader_defs` does a `format!("{}", val.to_string())`, which is better written as just `format!("{}", val)` - `bevy_diagnostic::log_diagnostics_plugin` does a `format!("{:>}", format!(...))`, which is better written as `format!("{:>}", format_args!(...))` - `bevy_ecs::schedule` does `tracing::info!(..., name = &*format!("{:?}", val))`, which is better written with the tracing shorthand `tracing::info!(..., name = ?val)` - `bevy_reflect::reflect` does `f.write_str(&format!(...))`, which is better written as `write!(f, ...)` (this could also be written using `f.debug_tuple`, but I opted to maintain alt debug behavior) - `bevy_reflect::serde::{ser, de}` do `serde::Error::custom(format!(...))`, which is better written as `Error::custom(format_args!(...))`, as `Error::custom` takes `impl Display` and just immediately calls `format!` again |
||
TehPers
|
5e7456115a
|
Implement Reflect for tuples up to length 12 (#1218)
Add Reflect impls for tuples up to length 12 |
||
Carter Anderson
|
72b2fc9843
|
Bevy Reflection (#926)
Bevy Reflection |