-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Add reflection for enum types #1347
Conversation
The second commit adds the ability to upcast to the |
8522dc2
to
51450c8
Compare
@Davier do you think this is worth merging even in its current form? |
The current form would not be very useful unfortunately. In theory it allows plugins like The serialization and I don't think the "help wanted" label is needed for now. |
It looks like enums with inlined struct definitions are not supported: // Not ok
#[derive(Clone, Reflect)]
enum A {
B { b: i32 }
}
// Ok
#[derive(Clone, Reflect)]
struct B {
b: i32
}
#[derive(Clone, Reflect)]
enum A {
B(B)
} Is this syntax what you refer to as a C like enum? |
A C like enum is an enum where none of the variants have any associated data. For example enum Foo {
A,
B,
C,
} |
Thank you for testing it! I had left the identifier of my test enum somewhere in the derive macro, so it worked only for that test...
I call these struct variants. For the record, generics are not supported yet. |
No worries, thanks for writing it! I've got another case for you 😄 The following: // not ok
#[derive(Reflect)]
pub enum Test {
A {
a: u64,
},
B {
b: u64,
},
} Causes
However if I use an enum with one variant it seems to work ok, although I have to also implement Clone (deriving Clone in the above example does not fix it but it does produce a different error message) // ok
#[derive(Reflect, Clone)]
pub enum Test {
A {
a: u64,
}
} |
Fixed, keep them coming :-) |
I'm curious if there is any desire to continue this or #1395 's work? @Davier If you're planning on coming back to these sometime soon I more than happy to review however, if you're busy, I'm also more than happy to take these branches and build on them and open new PRs if that's desired? |
I intend to finish it but I've been a bit busy :-) The next step is to decide how to solve #1395, I'll update it to main and start a discussion on discord, your input on that will be welcome. After that, depending on the amount of work to implement serialization, I may need some help to finish it sooner. Could you link to the issues that are related? I haven't followed bevy's development lately. |
@Davier Are you planning on coming back to this? If not, I'd be more than happy to take it off your hands (and give proper credit of course). My plan is to rebase this off #4042 and hopefully get it to a point where it's ready to be merged soon. I'm starting that process now, but let me know if you intend to revive it yourself. Thanks! |
# 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](https://github.com/bevyengine/bevy/pull/4761/commits/71d27ab3c6871bb188d8b46512db3b0922a56a0c)](https://github.com/bevyengine/bevy/pull/4761/commits/71d27ab3c6871bb188d8b46512db3b0922a56a0c) due to concerns about allocations. Each variant struct would probably look something like: ```rust pub trait Enum: Reflect { fn variant_mut(&self) -> VariantMut; } pub enum VariantMut<'a> { Unit, Tuple(TupleVariantMut), Struct(StructVariantMut), } struct StructVariantMut<'a> { fields: Vec<&'a mut dyn Reflect>, field_indices: HashMap<Cow<'static, str>, usize> } ``` This allows us to isolate struct variants into their own defined struct and define methods specifically for their use. It also prevents users from casting to it since it's not a trait. However, this is not an optimal solution. Both `field_indices` and `fields` will require an allocation (remember, a `Box<[T]>` still requires a `Vec<T>` in order to be constructed). This *might* be a problem if called frequently enough. ##### 3. Generated Structs The original design, implemented by @Davier, instead generates structs specific for each variant. So if we had a variant path like `Foo::Bar`, we'd generate a struct named `FooBarWrapper`. This would be newtyped around the original enum and forward tuple or struct methods to the enum with the chosen variant. Because it involved using the `Tuple` and `Struct` traits (which are also both bound on `Reflect`), this meant a bit more code had to be generated. For a single struct variant with one field, the generated code amounted to ~110LoC. However, each new field added to that variant only added ~6 more LoC. In order to work properly, the enum had to be transmuted to the generated struct: ```rust fn variant(&self) -> crate::EnumVariant<'_> { match self { Foo::Bar {value: i32} => { let wrapper_ref = unsafe { std::mem::transmute::<&Self, &FooBarWrapper>(self) }; crate::EnumVariant::Struct(wrapper_ref as &dyn crate::Struct) } } } ``` This works because `FooBarWrapper` is defined as `repr(transparent)`. Out of all the alternatives, this would probably be the one most likely to be used again in the future. The reasons for why this PR did not continue to use it was because: * To reduce generated code (which would hopefully speed up compile times) * To avoid cluttering the code with generated structs not visible to the user * To keep bevy_reflect simple and extensible (these generated structs act as proxies and might not play well with current or future systems) * To avoid additional unsafe blocks * My own misunderstanding of @Davier's code That last point is obviously on me. I misjudged the code to be too unsafe and unable to handle variant swapping (which it probably could) when I was rebasing it. Looking over it again when writing up this whole section, I see that it was actually a pretty clever way of handling variant representation. #### Benefits of All-in-One As stated before, the current implementation uses an all-in-one approach. All variants are capable of containing fields as far as `Enum` is concerned. This provides a few benefits that the alternatives do not (reduced indirection, safer code, etc.). The biggest benefit, though, is direct field access. Rather than forcing users to have to go through pattern matching, we grant direct access to the fields contained by the current variant. The reason we can do this is because all of the pattern matching happens internally. Getting the field at index `2` will automatically return `Some(...)` for the current variant if it has a field at that index or `None` if it doesn't (or can't). This could be useful for scenarios where the variant has already been verified or just set/swapped (or even where the type of variant doesn't matter): ```rust let dyn_enum: &mut dyn Enum = &mut Foo::Bar {value: 123}; // We know it's the `Bar` variant let field = dyn_enum.field("value").unwrap(); ``` Reflection is not a type-safe abstraction— almost every return value is wrapped in `Option<...>`. There are plenty of places to check and recheck that a value is what Reflect says it is. Forcing users to have to go through `match` each time they want to access a field might just be an extra step among dozens of other verification processes. Some might disagree, but ultimately, my view is that the benefit here is an improvement to the ergonomics and usability of reflected enums. </details> --- ## Changelog ### Added * Added `Enum` trait * Added `Enum` impl to `Reflect` derive macro * Added `DynamicEnum` struct * Added `DynamicVariant` * Added `EnumInfo` * Added `VariantInfo` * Added `StructVariantInfo` * Added `TupleVariantInfo` * Added `UnitVariantInfo` * Added serializtion/deserialization support for enums * Added `EnumSerializer` * Added `VariantType` * Added `VariantFieldIter` * Added `VariantField` * Added `enum_partial_eq(...)` * Added `enum_hash(...)` ### Changed * `Option<T>` now implements `Enum` * `bevy_window` now depends on `bevy_reflect` * Implemented `Reflect` and `FromReflect` for `WindowId` * Derive `FromReflect` on `PerspectiveProjection` * Derive `FromReflect` on `OrthographicProjection` * Derive `FromReflect` on `WindowOrigin` * Derive `FromReflect` on `ScalingMode` * Derive `FromReflect` on `DepthCalculation` ## Migration Guide * Enums no longer need to be treated as values and usages of `#[reflect_value(...)]` can be removed or replaced by `#[reflect(...)]` * Enums (including `Option<T>`) now take a different format when serializing. The format is described above, but this may cause issues for existing scenes that make use of enums. --- Also shout out to @nicopap for helping clean up some of the code here! It's a big feature so help like this is really appreciated! Co-authored-by: Gino Valente <gino.valente.code@gmail.com>
# Objective > This is a revival of bevyengine#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](https://github.com/bevyengine/bevy/pull/4761/commits/71d27ab3c6871bb188d8b46512db3b0922a56a0c)](https://github.com/bevyengine/bevy/pull/4761/commits/71d27ab3c6871bb188d8b46512db3b0922a56a0c) due to concerns about allocations. Each variant struct would probably look something like: ```rust pub trait Enum: Reflect { fn variant_mut(&self) -> VariantMut; } pub enum VariantMut<'a> { Unit, Tuple(TupleVariantMut), Struct(StructVariantMut), } struct StructVariantMut<'a> { fields: Vec<&'a mut dyn Reflect>, field_indices: HashMap<Cow<'static, str>, usize> } ``` This allows us to isolate struct variants into their own defined struct and define methods specifically for their use. It also prevents users from casting to it since it's not a trait. However, this is not an optimal solution. Both `field_indices` and `fields` will require an allocation (remember, a `Box<[T]>` still requires a `Vec<T>` in order to be constructed). This *might* be a problem if called frequently enough. ##### 3. Generated Structs The original design, implemented by @Davier, instead generates structs specific for each variant. So if we had a variant path like `Foo::Bar`, we'd generate a struct named `FooBarWrapper`. This would be newtyped around the original enum and forward tuple or struct methods to the enum with the chosen variant. Because it involved using the `Tuple` and `Struct` traits (which are also both bound on `Reflect`), this meant a bit more code had to be generated. For a single struct variant with one field, the generated code amounted to ~110LoC. However, each new field added to that variant only added ~6 more LoC. In order to work properly, the enum had to be transmuted to the generated struct: ```rust fn variant(&self) -> crate::EnumVariant<'_> { match self { Foo::Bar {value: i32} => { let wrapper_ref = unsafe { std::mem::transmute::<&Self, &FooBarWrapper>(self) }; crate::EnumVariant::Struct(wrapper_ref as &dyn crate::Struct) } } } ``` This works because `FooBarWrapper` is defined as `repr(transparent)`. Out of all the alternatives, this would probably be the one most likely to be used again in the future. The reasons for why this PR did not continue to use it was because: * To reduce generated code (which would hopefully speed up compile times) * To avoid cluttering the code with generated structs not visible to the user * To keep bevy_reflect simple and extensible (these generated structs act as proxies and might not play well with current or future systems) * To avoid additional unsafe blocks * My own misunderstanding of @Davier's code That last point is obviously on me. I misjudged the code to be too unsafe and unable to handle variant swapping (which it probably could) when I was rebasing it. Looking over it again when writing up this whole section, I see that it was actually a pretty clever way of handling variant representation. #### Benefits of All-in-One As stated before, the current implementation uses an all-in-one approach. All variants are capable of containing fields as far as `Enum` is concerned. This provides a few benefits that the alternatives do not (reduced indirection, safer code, etc.). The biggest benefit, though, is direct field access. Rather than forcing users to have to go through pattern matching, we grant direct access to the fields contained by the current variant. The reason we can do this is because all of the pattern matching happens internally. Getting the field at index `2` will automatically return `Some(...)` for the current variant if it has a field at that index or `None` if it doesn't (or can't). This could be useful for scenarios where the variant has already been verified or just set/swapped (or even where the type of variant doesn't matter): ```rust let dyn_enum: &mut dyn Enum = &mut Foo::Bar {value: 123}; // We know it's the `Bar` variant let field = dyn_enum.field("value").unwrap(); ``` Reflection is not a type-safe abstraction— almost every return value is wrapped in `Option<...>`. There are plenty of places to check and recheck that a value is what Reflect says it is. Forcing users to have to go through `match` each time they want to access a field might just be an extra step among dozens of other verification processes. Some might disagree, but ultimately, my view is that the benefit here is an improvement to the ergonomics and usability of reflected enums. </details> --- ## Changelog ### Added * Added `Enum` trait * Added `Enum` impl to `Reflect` derive macro * Added `DynamicEnum` struct * Added `DynamicVariant` * Added `EnumInfo` * Added `VariantInfo` * Added `StructVariantInfo` * Added `TupleVariantInfo` * Added `UnitVariantInfo` * Added serializtion/deserialization support for enums * Added `EnumSerializer` * Added `VariantType` * Added `VariantFieldIter` * Added `VariantField` * Added `enum_partial_eq(...)` * Added `enum_hash(...)` ### Changed * `Option<T>` now implements `Enum` * `bevy_window` now depends on `bevy_reflect` * Implemented `Reflect` and `FromReflect` for `WindowId` * Derive `FromReflect` on `PerspectiveProjection` * Derive `FromReflect` on `OrthographicProjection` * Derive `FromReflect` on `WindowOrigin` * Derive `FromReflect` on `ScalingMode` * Derive `FromReflect` on `DepthCalculation` ## Migration Guide * Enums no longer need to be treated as values and usages of `#[reflect_value(...)]` can be removed or replaced by `#[reflect(...)]` * Enums (including `Option<T>`) now take a different format when serializing. The format is described above, but this may cause issues for existing scenes that make use of enums. --- Also shout out to @nicopap for helping clean up some of the code here! It's a big feature so help like this is really appreciated! Co-authored-by: Gino Valente <gino.valente.code@gmail.com>
# Objective > This is a revival of bevyengine#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](https://github.com/bevyengine/bevy/pull/4761/commits/71d27ab3c6871bb188d8b46512db3b0922a56a0c)](https://github.com/bevyengine/bevy/pull/4761/commits/71d27ab3c6871bb188d8b46512db3b0922a56a0c) due to concerns about allocations. Each variant struct would probably look something like: ```rust pub trait Enum: Reflect { fn variant_mut(&self) -> VariantMut; } pub enum VariantMut<'a> { Unit, Tuple(TupleVariantMut), Struct(StructVariantMut), } struct StructVariantMut<'a> { fields: Vec<&'a mut dyn Reflect>, field_indices: HashMap<Cow<'static, str>, usize> } ``` This allows us to isolate struct variants into their own defined struct and define methods specifically for their use. It also prevents users from casting to it since it's not a trait. However, this is not an optimal solution. Both `field_indices` and `fields` will require an allocation (remember, a `Box<[T]>` still requires a `Vec<T>` in order to be constructed). This *might* be a problem if called frequently enough. ##### 3. Generated Structs The original design, implemented by @Davier, instead generates structs specific for each variant. So if we had a variant path like `Foo::Bar`, we'd generate a struct named `FooBarWrapper`. This would be newtyped around the original enum and forward tuple or struct methods to the enum with the chosen variant. Because it involved using the `Tuple` and `Struct` traits (which are also both bound on `Reflect`), this meant a bit more code had to be generated. For a single struct variant with one field, the generated code amounted to ~110LoC. However, each new field added to that variant only added ~6 more LoC. In order to work properly, the enum had to be transmuted to the generated struct: ```rust fn variant(&self) -> crate::EnumVariant<'_> { match self { Foo::Bar {value: i32} => { let wrapper_ref = unsafe { std::mem::transmute::<&Self, &FooBarWrapper>(self) }; crate::EnumVariant::Struct(wrapper_ref as &dyn crate::Struct) } } } ``` This works because `FooBarWrapper` is defined as `repr(transparent)`. Out of all the alternatives, this would probably be the one most likely to be used again in the future. The reasons for why this PR did not continue to use it was because: * To reduce generated code (which would hopefully speed up compile times) * To avoid cluttering the code with generated structs not visible to the user * To keep bevy_reflect simple and extensible (these generated structs act as proxies and might not play well with current or future systems) * To avoid additional unsafe blocks * My own misunderstanding of @Davier's code That last point is obviously on me. I misjudged the code to be too unsafe and unable to handle variant swapping (which it probably could) when I was rebasing it. Looking over it again when writing up this whole section, I see that it was actually a pretty clever way of handling variant representation. #### Benefits of All-in-One As stated before, the current implementation uses an all-in-one approach. All variants are capable of containing fields as far as `Enum` is concerned. This provides a few benefits that the alternatives do not (reduced indirection, safer code, etc.). The biggest benefit, though, is direct field access. Rather than forcing users to have to go through pattern matching, we grant direct access to the fields contained by the current variant. The reason we can do this is because all of the pattern matching happens internally. Getting the field at index `2` will automatically return `Some(...)` for the current variant if it has a field at that index or `None` if it doesn't (or can't). This could be useful for scenarios where the variant has already been verified or just set/swapped (or even where the type of variant doesn't matter): ```rust let dyn_enum: &mut dyn Enum = &mut Foo::Bar {value: 123}; // We know it's the `Bar` variant let field = dyn_enum.field("value").unwrap(); ``` Reflection is not a type-safe abstraction— almost every return value is wrapped in `Option<...>`. There are plenty of places to check and recheck that a value is what Reflect says it is. Forcing users to have to go through `match` each time they want to access a field might just be an extra step among dozens of other verification processes. Some might disagree, but ultimately, my view is that the benefit here is an improvement to the ergonomics and usability of reflected enums. </details> --- ## Changelog ### Added * Added `Enum` trait * Added `Enum` impl to `Reflect` derive macro * Added `DynamicEnum` struct * Added `DynamicVariant` * Added `EnumInfo` * Added `VariantInfo` * Added `StructVariantInfo` * Added `TupleVariantInfo` * Added `UnitVariantInfo` * Added serializtion/deserialization support for enums * Added `EnumSerializer` * Added `VariantType` * Added `VariantFieldIter` * Added `VariantField` * Added `enum_partial_eq(...)` * Added `enum_hash(...)` ### Changed * `Option<T>` now implements `Enum` * `bevy_window` now depends on `bevy_reflect` * Implemented `Reflect` and `FromReflect` for `WindowId` * Derive `FromReflect` on `PerspectiveProjection` * Derive `FromReflect` on `OrthographicProjection` * Derive `FromReflect` on `WindowOrigin` * Derive `FromReflect` on `ScalingMode` * Derive `FromReflect` on `DepthCalculation` ## Migration Guide * Enums no longer need to be treated as values and usages of `#[reflect_value(...)]` can be removed or replaced by `#[reflect(...)]` * Enums (including `Option<T>`) now take a different format when serializing. The format is described above, but this may cause issues for existing scenes that make use of enums. --- Also shout out to @nicopap for helping clean up some of the code here! It's a big feature so help like this is really appreciated! Co-authored-by: Gino Valente <gino.valente.code@gmail.com>
# Objective > This is a revival of bevyengine#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](https://github.com/bevyengine/bevy/pull/4761/commits/71d27ab3c6871bb188d8b46512db3b0922a56a0c)](https://github.com/bevyengine/bevy/pull/4761/commits/71d27ab3c6871bb188d8b46512db3b0922a56a0c) due to concerns about allocations. Each variant struct would probably look something like: ```rust pub trait Enum: Reflect { fn variant_mut(&self) -> VariantMut; } pub enum VariantMut<'a> { Unit, Tuple(TupleVariantMut), Struct(StructVariantMut), } struct StructVariantMut<'a> { fields: Vec<&'a mut dyn Reflect>, field_indices: HashMap<Cow<'static, str>, usize> } ``` This allows us to isolate struct variants into their own defined struct and define methods specifically for their use. It also prevents users from casting to it since it's not a trait. However, this is not an optimal solution. Both `field_indices` and `fields` will require an allocation (remember, a `Box<[T]>` still requires a `Vec<T>` in order to be constructed). This *might* be a problem if called frequently enough. ##### 3. Generated Structs The original design, implemented by @Davier, instead generates structs specific for each variant. So if we had a variant path like `Foo::Bar`, we'd generate a struct named `FooBarWrapper`. This would be newtyped around the original enum and forward tuple or struct methods to the enum with the chosen variant. Because it involved using the `Tuple` and `Struct` traits (which are also both bound on `Reflect`), this meant a bit more code had to be generated. For a single struct variant with one field, the generated code amounted to ~110LoC. However, each new field added to that variant only added ~6 more LoC. In order to work properly, the enum had to be transmuted to the generated struct: ```rust fn variant(&self) -> crate::EnumVariant<'_> { match self { Foo::Bar {value: i32} => { let wrapper_ref = unsafe { std::mem::transmute::<&Self, &FooBarWrapper>(self) }; crate::EnumVariant::Struct(wrapper_ref as &dyn crate::Struct) } } } ``` This works because `FooBarWrapper` is defined as `repr(transparent)`. Out of all the alternatives, this would probably be the one most likely to be used again in the future. The reasons for why this PR did not continue to use it was because: * To reduce generated code (which would hopefully speed up compile times) * To avoid cluttering the code with generated structs not visible to the user * To keep bevy_reflect simple and extensible (these generated structs act as proxies and might not play well with current or future systems) * To avoid additional unsafe blocks * My own misunderstanding of @Davier's code That last point is obviously on me. I misjudged the code to be too unsafe and unable to handle variant swapping (which it probably could) when I was rebasing it. Looking over it again when writing up this whole section, I see that it was actually a pretty clever way of handling variant representation. #### Benefits of All-in-One As stated before, the current implementation uses an all-in-one approach. All variants are capable of containing fields as far as `Enum` is concerned. This provides a few benefits that the alternatives do not (reduced indirection, safer code, etc.). The biggest benefit, though, is direct field access. Rather than forcing users to have to go through pattern matching, we grant direct access to the fields contained by the current variant. The reason we can do this is because all of the pattern matching happens internally. Getting the field at index `2` will automatically return `Some(...)` for the current variant if it has a field at that index or `None` if it doesn't (or can't). This could be useful for scenarios where the variant has already been verified or just set/swapped (or even where the type of variant doesn't matter): ```rust let dyn_enum: &mut dyn Enum = &mut Foo::Bar {value: 123}; // We know it's the `Bar` variant let field = dyn_enum.field("value").unwrap(); ``` Reflection is not a type-safe abstraction— almost every return value is wrapped in `Option<...>`. There are plenty of places to check and recheck that a value is what Reflect says it is. Forcing users to have to go through `match` each time they want to access a field might just be an extra step among dozens of other verification processes. Some might disagree, but ultimately, my view is that the benefit here is an improvement to the ergonomics and usability of reflected enums. </details> --- ## Changelog ### Added * Added `Enum` trait * Added `Enum` impl to `Reflect` derive macro * Added `DynamicEnum` struct * Added `DynamicVariant` * Added `EnumInfo` * Added `VariantInfo` * Added `StructVariantInfo` * Added `TupleVariantInfo` * Added `UnitVariantInfo` * Added serializtion/deserialization support for enums * Added `EnumSerializer` * Added `VariantType` * Added `VariantFieldIter` * Added `VariantField` * Added `enum_partial_eq(...)` * Added `enum_hash(...)` ### Changed * `Option<T>` now implements `Enum` * `bevy_window` now depends on `bevy_reflect` * Implemented `Reflect` and `FromReflect` for `WindowId` * Derive `FromReflect` on `PerspectiveProjection` * Derive `FromReflect` on `OrthographicProjection` * Derive `FromReflect` on `WindowOrigin` * Derive `FromReflect` on `ScalingMode` * Derive `FromReflect` on `DepthCalculation` ## Migration Guide * Enums no longer need to be treated as values and usages of `#[reflect_value(...)]` can be removed or replaced by `#[reflect(...)]` * Enums (including `Option<T>`) now take a different format when serializing. The format is described above, but this may cause issues for existing scenes that make use of enums. --- Also shout out to @nicopap for helping clean up some of the code here! It's a big feature so help like this is really appreciated! Co-authored-by: Gino Valente <gino.valente.code@gmail.com>
# Objective > This is a revival of bevyengine#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](https://github.com/bevyengine/bevy/pull/4761/commits/71d27ab3c6871bb188d8b46512db3b0922a56a0c)](https://github.com/bevyengine/bevy/pull/4761/commits/71d27ab3c6871bb188d8b46512db3b0922a56a0c) due to concerns about allocations. Each variant struct would probably look something like: ```rust pub trait Enum: Reflect { fn variant_mut(&self) -> VariantMut; } pub enum VariantMut<'a> { Unit, Tuple(TupleVariantMut), Struct(StructVariantMut), } struct StructVariantMut<'a> { fields: Vec<&'a mut dyn Reflect>, field_indices: HashMap<Cow<'static, str>, usize> } ``` This allows us to isolate struct variants into their own defined struct and define methods specifically for their use. It also prevents users from casting to it since it's not a trait. However, this is not an optimal solution. Both `field_indices` and `fields` will require an allocation (remember, a `Box<[T]>` still requires a `Vec<T>` in order to be constructed). This *might* be a problem if called frequently enough. ##### 3. Generated Structs The original design, implemented by @Davier, instead generates structs specific for each variant. So if we had a variant path like `Foo::Bar`, we'd generate a struct named `FooBarWrapper`. This would be newtyped around the original enum and forward tuple or struct methods to the enum with the chosen variant. Because it involved using the `Tuple` and `Struct` traits (which are also both bound on `Reflect`), this meant a bit more code had to be generated. For a single struct variant with one field, the generated code amounted to ~110LoC. However, each new field added to that variant only added ~6 more LoC. In order to work properly, the enum had to be transmuted to the generated struct: ```rust fn variant(&self) -> crate::EnumVariant<'_> { match self { Foo::Bar {value: i32} => { let wrapper_ref = unsafe { std::mem::transmute::<&Self, &FooBarWrapper>(self) }; crate::EnumVariant::Struct(wrapper_ref as &dyn crate::Struct) } } } ``` This works because `FooBarWrapper` is defined as `repr(transparent)`. Out of all the alternatives, this would probably be the one most likely to be used again in the future. The reasons for why this PR did not continue to use it was because: * To reduce generated code (which would hopefully speed up compile times) * To avoid cluttering the code with generated structs not visible to the user * To keep bevy_reflect simple and extensible (these generated structs act as proxies and might not play well with current or future systems) * To avoid additional unsafe blocks * My own misunderstanding of @Davier's code That last point is obviously on me. I misjudged the code to be too unsafe and unable to handle variant swapping (which it probably could) when I was rebasing it. Looking over it again when writing up this whole section, I see that it was actually a pretty clever way of handling variant representation. #### Benefits of All-in-One As stated before, the current implementation uses an all-in-one approach. All variants are capable of containing fields as far as `Enum` is concerned. This provides a few benefits that the alternatives do not (reduced indirection, safer code, etc.). The biggest benefit, though, is direct field access. Rather than forcing users to have to go through pattern matching, we grant direct access to the fields contained by the current variant. The reason we can do this is because all of the pattern matching happens internally. Getting the field at index `2` will automatically return `Some(...)` for the current variant if it has a field at that index or `None` if it doesn't (or can't). This could be useful for scenarios where the variant has already been verified or just set/swapped (or even where the type of variant doesn't matter): ```rust let dyn_enum: &mut dyn Enum = &mut Foo::Bar {value: 123}; // We know it's the `Bar` variant let field = dyn_enum.field("value").unwrap(); ``` Reflection is not a type-safe abstraction— almost every return value is wrapped in `Option<...>`. There are plenty of places to check and recheck that a value is what Reflect says it is. Forcing users to have to go through `match` each time they want to access a field might just be an extra step among dozens of other verification processes. Some might disagree, but ultimately, my view is that the benefit here is an improvement to the ergonomics and usability of reflected enums. </details> --- ## Changelog ### Added * Added `Enum` trait * Added `Enum` impl to `Reflect` derive macro * Added `DynamicEnum` struct * Added `DynamicVariant` * Added `EnumInfo` * Added `VariantInfo` * Added `StructVariantInfo` * Added `TupleVariantInfo` * Added `UnitVariantInfo` * Added serializtion/deserialization support for enums * Added `EnumSerializer` * Added `VariantType` * Added `VariantFieldIter` * Added `VariantField` * Added `enum_partial_eq(...)` * Added `enum_hash(...)` ### Changed * `Option<T>` now implements `Enum` * `bevy_window` now depends on `bevy_reflect` * Implemented `Reflect` and `FromReflect` for `WindowId` * Derive `FromReflect` on `PerspectiveProjection` * Derive `FromReflect` on `OrthographicProjection` * Derive `FromReflect` on `WindowOrigin` * Derive `FromReflect` on `ScalingMode` * Derive `FromReflect` on `DepthCalculation` ## Migration Guide * Enums no longer need to be treated as values and usages of `#[reflect_value(...)]` can be removed or replaced by `#[reflect(...)]` * Enums (including `Option<T>`) now take a different format when serializing. The format is described above, but this may cause issues for existing scenes that make use of enums. --- Also shout out to @nicopap for helping clean up some of the code here! It's a big feature so help like this is really appreciated! Co-authored-by: Gino Valente <gino.valente.code@gmail.com>
# Objective > This is a revival of bevyengine#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](https://github.com/bevyengine/bevy/pull/4761/commits/71d27ab3c6871bb188d8b46512db3b0922a56a0c)](https://github.com/bevyengine/bevy/pull/4761/commits/71d27ab3c6871bb188d8b46512db3b0922a56a0c) due to concerns about allocations. Each variant struct would probably look something like: ```rust pub trait Enum: Reflect { fn variant_mut(&self) -> VariantMut; } pub enum VariantMut<'a> { Unit, Tuple(TupleVariantMut), Struct(StructVariantMut), } struct StructVariantMut<'a> { fields: Vec<&'a mut dyn Reflect>, field_indices: HashMap<Cow<'static, str>, usize> } ``` This allows us to isolate struct variants into their own defined struct and define methods specifically for their use. It also prevents users from casting to it since it's not a trait. However, this is not an optimal solution. Both `field_indices` and `fields` will require an allocation (remember, a `Box<[T]>` still requires a `Vec<T>` in order to be constructed). This *might* be a problem if called frequently enough. ##### 3. Generated Structs The original design, implemented by @Davier, instead generates structs specific for each variant. So if we had a variant path like `Foo::Bar`, we'd generate a struct named `FooBarWrapper`. This would be newtyped around the original enum and forward tuple or struct methods to the enum with the chosen variant. Because it involved using the `Tuple` and `Struct` traits (which are also both bound on `Reflect`), this meant a bit more code had to be generated. For a single struct variant with one field, the generated code amounted to ~110LoC. However, each new field added to that variant only added ~6 more LoC. In order to work properly, the enum had to be transmuted to the generated struct: ```rust fn variant(&self) -> crate::EnumVariant<'_> { match self { Foo::Bar {value: i32} => { let wrapper_ref = unsafe { std::mem::transmute::<&Self, &FooBarWrapper>(self) }; crate::EnumVariant::Struct(wrapper_ref as &dyn crate::Struct) } } } ``` This works because `FooBarWrapper` is defined as `repr(transparent)`. Out of all the alternatives, this would probably be the one most likely to be used again in the future. The reasons for why this PR did not continue to use it was because: * To reduce generated code (which would hopefully speed up compile times) * To avoid cluttering the code with generated structs not visible to the user * To keep bevy_reflect simple and extensible (these generated structs act as proxies and might not play well with current or future systems) * To avoid additional unsafe blocks * My own misunderstanding of @Davier's code That last point is obviously on me. I misjudged the code to be too unsafe and unable to handle variant swapping (which it probably could) when I was rebasing it. Looking over it again when writing up this whole section, I see that it was actually a pretty clever way of handling variant representation. #### Benefits of All-in-One As stated before, the current implementation uses an all-in-one approach. All variants are capable of containing fields as far as `Enum` is concerned. This provides a few benefits that the alternatives do not (reduced indirection, safer code, etc.). The biggest benefit, though, is direct field access. Rather than forcing users to have to go through pattern matching, we grant direct access to the fields contained by the current variant. The reason we can do this is because all of the pattern matching happens internally. Getting the field at index `2` will automatically return `Some(...)` for the current variant if it has a field at that index or `None` if it doesn't (or can't). This could be useful for scenarios where the variant has already been verified or just set/swapped (or even where the type of variant doesn't matter): ```rust let dyn_enum: &mut dyn Enum = &mut Foo::Bar {value: 123}; // We know it's the `Bar` variant let field = dyn_enum.field("value").unwrap(); ``` Reflection is not a type-safe abstraction— almost every return value is wrapped in `Option<...>`. There are plenty of places to check and recheck that a value is what Reflect says it is. Forcing users to have to go through `match` each time they want to access a field might just be an extra step among dozens of other verification processes. Some might disagree, but ultimately, my view is that the benefit here is an improvement to the ergonomics and usability of reflected enums. </details> --- ## Changelog ### Added * Added `Enum` trait * Added `Enum` impl to `Reflect` derive macro * Added `DynamicEnum` struct * Added `DynamicVariant` * Added `EnumInfo` * Added `VariantInfo` * Added `StructVariantInfo` * Added `TupleVariantInfo` * Added `UnitVariantInfo` * Added serializtion/deserialization support for enums * Added `EnumSerializer` * Added `VariantType` * Added `VariantFieldIter` * Added `VariantField` * Added `enum_partial_eq(...)` * Added `enum_hash(...)` ### Changed * `Option<T>` now implements `Enum` * `bevy_window` now depends on `bevy_reflect` * Implemented `Reflect` and `FromReflect` for `WindowId` * Derive `FromReflect` on `PerspectiveProjection` * Derive `FromReflect` on `OrthographicProjection` * Derive `FromReflect` on `WindowOrigin` * Derive `FromReflect` on `ScalingMode` * Derive `FromReflect` on `DepthCalculation` ## Migration Guide * Enums no longer need to be treated as values and usages of `#[reflect_value(...)]` can be removed or replaced by `#[reflect(...)]` * Enums (including `Option<T>`) now take a different format when serializing. The format is described above, but this may cause issues for existing scenes that make use of enums. --- Also shout out to @nicopap for helping clean up some of the code here! It's a big feature so help like this is really appreciated! Co-authored-by: Gino Valente <gino.valente.code@gmail.com>
# Objective > This is a revival of bevyengine#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](https://github.com/bevyengine/bevy/pull/4761/commits/71d27ab3c6871bb188d8b46512db3b0922a56a0c)](https://github.com/bevyengine/bevy/pull/4761/commits/71d27ab3c6871bb188d8b46512db3b0922a56a0c) due to concerns about allocations. Each variant struct would probably look something like: ```rust pub trait Enum: Reflect { fn variant_mut(&self) -> VariantMut; } pub enum VariantMut<'a> { Unit, Tuple(TupleVariantMut), Struct(StructVariantMut), } struct StructVariantMut<'a> { fields: Vec<&'a mut dyn Reflect>, field_indices: HashMap<Cow<'static, str>, usize> } ``` This allows us to isolate struct variants into their own defined struct and define methods specifically for their use. It also prevents users from casting to it since it's not a trait. However, this is not an optimal solution. Both `field_indices` and `fields` will require an allocation (remember, a `Box<[T]>` still requires a `Vec<T>` in order to be constructed). This *might* be a problem if called frequently enough. ##### 3. Generated Structs The original design, implemented by @Davier, instead generates structs specific for each variant. So if we had a variant path like `Foo::Bar`, we'd generate a struct named `FooBarWrapper`. This would be newtyped around the original enum and forward tuple or struct methods to the enum with the chosen variant. Because it involved using the `Tuple` and `Struct` traits (which are also both bound on `Reflect`), this meant a bit more code had to be generated. For a single struct variant with one field, the generated code amounted to ~110LoC. However, each new field added to that variant only added ~6 more LoC. In order to work properly, the enum had to be transmuted to the generated struct: ```rust fn variant(&self) -> crate::EnumVariant<'_> { match self { Foo::Bar {value: i32} => { let wrapper_ref = unsafe { std::mem::transmute::<&Self, &FooBarWrapper>(self) }; crate::EnumVariant::Struct(wrapper_ref as &dyn crate::Struct) } } } ``` This works because `FooBarWrapper` is defined as `repr(transparent)`. Out of all the alternatives, this would probably be the one most likely to be used again in the future. The reasons for why this PR did not continue to use it was because: * To reduce generated code (which would hopefully speed up compile times) * To avoid cluttering the code with generated structs not visible to the user * To keep bevy_reflect simple and extensible (these generated structs act as proxies and might not play well with current or future systems) * To avoid additional unsafe blocks * My own misunderstanding of @Davier's code That last point is obviously on me. I misjudged the code to be too unsafe and unable to handle variant swapping (which it probably could) when I was rebasing it. Looking over it again when writing up this whole section, I see that it was actually a pretty clever way of handling variant representation. #### Benefits of All-in-One As stated before, the current implementation uses an all-in-one approach. All variants are capable of containing fields as far as `Enum` is concerned. This provides a few benefits that the alternatives do not (reduced indirection, safer code, etc.). The biggest benefit, though, is direct field access. Rather than forcing users to have to go through pattern matching, we grant direct access to the fields contained by the current variant. The reason we can do this is because all of the pattern matching happens internally. Getting the field at index `2` will automatically return `Some(...)` for the current variant if it has a field at that index or `None` if it doesn't (or can't). This could be useful for scenarios where the variant has already been verified or just set/swapped (or even where the type of variant doesn't matter): ```rust let dyn_enum: &mut dyn Enum = &mut Foo::Bar {value: 123}; // We know it's the `Bar` variant let field = dyn_enum.field("value").unwrap(); ``` Reflection is not a type-safe abstraction— almost every return value is wrapped in `Option<...>`. There are plenty of places to check and recheck that a value is what Reflect says it is. Forcing users to have to go through `match` each time they want to access a field might just be an extra step among dozens of other verification processes. Some might disagree, but ultimately, my view is that the benefit here is an improvement to the ergonomics and usability of reflected enums. </details> --- ## Changelog ### Added * Added `Enum` trait * Added `Enum` impl to `Reflect` derive macro * Added `DynamicEnum` struct * Added `DynamicVariant` * Added `EnumInfo` * Added `VariantInfo` * Added `StructVariantInfo` * Added `TupleVariantInfo` * Added `UnitVariantInfo` * Added serializtion/deserialization support for enums * Added `EnumSerializer` * Added `VariantType` * Added `VariantFieldIter` * Added `VariantField` * Added `enum_partial_eq(...)` * Added `enum_hash(...)` ### Changed * `Option<T>` now implements `Enum` * `bevy_window` now depends on `bevy_reflect` * Implemented `Reflect` and `FromReflect` for `WindowId` * Derive `FromReflect` on `PerspectiveProjection` * Derive `FromReflect` on `OrthographicProjection` * Derive `FromReflect` on `WindowOrigin` * Derive `FromReflect` on `ScalingMode` * Derive `FromReflect` on `DepthCalculation` ## Migration Guide * Enums no longer need to be treated as values and usages of `#[reflect_value(...)]` can be removed or replaced by `#[reflect(...)]` * Enums (including `Option<T>`) now take a different format when serializing. The format is described above, but this may cause issues for existing scenes that make use of enums. --- Also shout out to @nicopap for helping clean up some of the code here! It's a big feature so help like this is really appreciated! Co-authored-by: Gino Valente <gino.valente.code@gmail.com>
Enumerated types should be reflected like all other types. This is motivated and discussed in #1306.
Wanted features:
support C-like enums with explicit discriminants