-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
[Merged by Bors] - Add FromReflect trait to convert dynamic types to concrete types #1395
Conversation
This is definitely worth considering, but its also a pretty big change to the "scene/entity" lifecycle so I'll need to spend some time considering the design. I'll get to this as soon as I can, but I probably won't prioritize this for the 0.5 release. |
I updated the PR and trimmed it down, I hope it feels more like a small improvement to fix an issue and less like a big change to the "scene/entity" lifecycle :-) What I kept
What I removed
|
This PR was discussed here: https://discord.com/channels/691052431525675048/745805740274614303/869276523503435817 I think the main point was: "Reflect should be derivable for every type, including those which cannot be constructed from a combination of FromReflect and Default impls" (from cart) Suggested changes are:
For future reference, I wanted to use FromReflect in ReflectComponent because I had several cases where a Component could not correctly implement Default/FromWorld. For instance, components that contain an Entity must implement Default with a fake Entity in order to be reflected (see Parent). |
Have you seen the technique used in #2491? Perhaps we could use it here as well, to get elegant handling of the various initialization methods. |
I don't think so unfortunately, there is no wrapping type like After thinking a bit more about it, introducing |
Here is how the alternative looks like: Davier@e621fcc |
Other users structs containing Then, we could change Last time this came up, there was some frustration that we can't assign |
I hate having to handle the invalid |
I reverted the modifications to the reflection example, since Also, should I do the refactoring to deduplicate the code between |
As promised a while ago, here is a summary of this PR. The problemsThis PR aims to solve two issues. Issue 1: Reflecting a component requires that it implements
|
// TODO: We need to impl either FromWorld or Default so Parent can be registered as Properties. | |
// This is because Properties deserialize by creating an instance and apply a patch on top. | |
// However Parent should only ever be set with a real user-defined entity. Its worth looking into | |
// better ways to handle cases like this. | |
impl FromWorld for Parent { | |
fn from_world(_world: &mut World) -> Self { | |
Parent(Entity::new(u32::MAX)) | |
} | |
} |
Besides, this process of default initialization + applying patch is also how we copy components between worlds (i.e. when spawning a scene).
TL;DR: both deserializing and spawning a scene currently needs to default-construct every component, but some components do not have a sane default.
Issue 2: Reflection of a component that has a non-empty container causes a panic #2215
The panic comes from list_apply()
:
bevy/crates/bevy_reflect/src/list.rs
Lines 162 to 176 in cf221f9
pub fn list_apply<L: List>(a: &mut L, b: &dyn Reflect) { | |
if let ReflectRef::List(list_value) = b.reflect_ref() { | |
for (i, value) in list_value.iter().enumerate() { | |
if i < a.len() { | |
if let Some(v) = a.get_mut(i) { | |
v.apply(value); | |
} | |
} else { | |
List::push(a, value.clone_value()); | |
} | |
} | |
} else { | |
panic!("Attempted to apply a non-list type to a list type."); | |
} | |
} |
The reason is linked to the explanation of Issue 1: when a component is deserialized or copied,
we may get a dynamic type. If one field of the component is a container, the contained items may also be dynamic types.
As explained before, we default-initialize the component (with an empty container), and apply the dynamic type on it. In the case of a Vec<T>
, this calls list_apply()
, which tries to push every new item in the container. However, if what we have is not the concrete type T
, but a dynamic one, this panic occurs.
I'm aware of two components that have containers in bevy:
Children
: it works becauseEntity
usesreflect_value
, there are no dynamic typesText
: currently cannot be correctly reflected because of thesections
field. Trying to deserialize any UI with text triggers the panic.
TL;DR: it's currently impossible to have containers of complex types in a scene
The solutions
Require Default
for types inside reflected containers (NOT this PR)
One way to fix Issue 2 is to use the same trick as for reflecting components. We require that any type inside a reflected container implements Default
. In list_apply()
, if an item is a dynamic type instead of a concrete one, we default-initialize it and apply the dynamic type.
Obviously, this means that Issue 1 is now also applicable for these contained types.
Use Option<T>
instead of T
if a type has no sane default (NOT this PR)
A solution to Issue 1 suggested by @alice-i-cecile is to place any type that does not have a sane default in an Option
, and use None
as the default.
However, this means we would have to handle that invalid value everywhere. This is IMO a significant downgrade in ergonomics. Invalid values should not exist, at all.
(Note: this solution would benefit from the optimization in #3022, but is otherwise orthogonal)
Create the concrete types directly from the reflected values (this PR)
We add a way to directly construct concrete types from the reflected values. This removes an unnecessary step, along with the FromWorld
/Default
requirement.
Issue 1 is solved because there is no need for a FromWorld
bound on components anymore.
Issue 2 is solved because we have a way to systematically construct the concrete types.
Status of the PR
This PR introduces the FromReflect
trait, along with a derive macro.
pub trait FromReflect: Reflect + Sized {
/// Creates a clone of a reflected value, converting it to a concrete type if it was a dynamic types (e.g. [DynamicStruct])
fn from_reflect(reflect: &dyn Reflect) -> Option<Self>;
}
New constraints:
- fields that are ignored for reflection and derive
FromReflect
must implementDefault
- reflected containers (e.g.
Vec<T>
) now have aFromReflect
bound onT
instead ofReflect
At first, deriving Reflect
also automatically derived FromReflect
, but as requested I split it into its own macro. This means there is a lot of duplicated boilerplate, but it can be merged back in a later PR if all goes well.
I was also asked to revert the FromWorld
bound on components, so currently Issue 1 is not fixed by this PR.
Personally, this PR is blocking for:
- reflecting
enum
s, since I need a way to directly construct each variant (Add reflection for enum types #1347) - reflecting
bevy_ui
, because ofText
(and I want to put the UI in aScene
in my widget prototype lib)
Issue 2 also seem to block #2359 (see here) and #1429.
I hope this explanation is useful, and that I managed to show that the PR:
- is not such a big change (it's now completely opt-in)
- fixes an important bug
- enables cool stuff
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok so I like FromReflect / it seems like a relatively uncontroversial addition to the reflection toolset. I'm also convinced that using FromReflect in collections is a good solution in the short term. Just one small nit and I think we should merge this!
bors r+ |
Dynamic types (`DynamicStruct`, `DynamicTupleStruct`, `DynamicTuple`, `DynamicList` and `DynamicMap`) are used when deserializing scenes, but currently they can only be applied to existing concrete types. This leads to issues when trying to spawn non trivial deserialized scene. For components, the issue is avoided by requiring that reflected components implement ~~`FromResources`~~ `FromWorld` (or `Default`). When spawning, a new concrete type is created that way, and the dynamic type is applied to it. Unfortunately, some components don't have any valid implementation of these traits. In addition, any `Vec` or `HashMap` inside a component will panic when a dynamic type is pushed into it (for instance, `Text` panics when adding a text section). To solve this issue, this PR adds the `FromReflect` trait that creates a concrete type from a dynamic type that represent it, derives the trait alongside the `Reflect` trait, drops the ~~`FromResources`~~ `FromWorld` requirement on reflected components, ~~and enables reflection for UI and Text bundles~~. It also adds the requirement that fields ignored with `#[reflect(ignore)]` implement `Default`, since we need to initialize them somehow. Co-authored-by: Carter Anderson <mcanders1@gmail.com>
Pull request successfully merged into main. Build succeeded: |
…4140) # Objective Currently, `FromReflect` makes a couple assumptions: * Ignored fields must implement `Default` * Active fields must implement `FromReflect` * The reflected must be fully populated for active fields (can't use an empty `DynamicStruct`) However, one or both of these requirements might be unachievable, such as for external types. In these cases, it might be nice to tell `FromReflect` to use a custom default. ## Solution Added the `#[reflect(default)]` derive helper attribute. This attribute can be applied to any field (ignored or not) and will allow a default value to be specified in place of the regular `from_reflect()` call. It takes two forms: `#[reflect(default)]` and `#[reflect(default = "some_func")]`. The former specifies that `Default::default()` should be used while the latter specifies that `some_func()` should be used. This is pretty much [how serde does it](https://serde.rs/field-attrs.html#default). ### Example ```rust #[derive(Reflect, FromReflect)] struct MyStruct { // Use `Default::default()` #[reflect(default)] foo: String, // Use `get_bar_default()` #[reflect(default = "get_bar_default")] #[reflect(ignore)] bar: usize, } fn get_bar_default() -> usize { 123 } ``` ### Active Fields As an added benefit, this also allows active fields to be completely missing from their dynamic object. This is because the attribute tells `FromReflect` how to handle missing active fields (it still tries to use `from_reflect` first so the `FromReflect` trait is still required). ```rust let dyn_struct = DynamicStruct::default(); // We can do this without actually including the active fields since they have `#[reflect(default)]` let my_struct = <MyStruct as FromReflect>::from_reflect(&dyn_struct); ``` ### Container Defaults Also, with the addition of #3733, people will likely start adding `#[reflect(Default)]` to their types now. Just like with the fields, we can use this to mark the entire container as "defaultable". This grants us the ability to completely remove the field markers altogether if our type implements `Default` (and we're okay with fields using that instead of their own `Default` impls): ```rust #[derive(Reflect, FromReflect)] #[reflect(Default)] struct MyStruct { foo: String, #[reflect(ignore)] bar: usize, } impl Default for MyStruct { fn default() -> Self { Self { foo: String::from("Hello"), bar: 123, } } } // Again, we can now construct this from nothing pretty much let dyn_struct = DynamicStruct::default(); let my_struct = <MyStruct as FromReflect>::from_reflect(&dyn_struct); ``` Now if _any_ field is missing when using `FromReflect`, we simply fallback onto the container's `Default` implementation. This behavior can be completely overridden on a per-field basis, of course, by simply defining those same field attributes like before. ### Related * #3733 * #1395 * #2377 --- ## Changelog * Added `#[reflect(default)]` field attribute for `FromReflect` * Allows missing fields to be given a default value when using `FromReflect` * `#[reflect(default)]` - Use the field's `Default` implementation * `#[reflect(default = "some_fn")]` - Use a custom function to get the default value * Allow `#[reflect(Default)]` to have a secondary usage as a container attribute * Allows missing fields to be given a default value based on the container's `Default` impl when using `FromReflect` Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
…4140) # Objective Currently, `FromReflect` makes a couple assumptions: * Ignored fields must implement `Default` * Active fields must implement `FromReflect` * The reflected must be fully populated for active fields (can't use an empty `DynamicStruct`) However, one or both of these requirements might be unachievable, such as for external types. In these cases, it might be nice to tell `FromReflect` to use a custom default. ## Solution Added the `#[reflect(default)]` derive helper attribute. This attribute can be applied to any field (ignored or not) and will allow a default value to be specified in place of the regular `from_reflect()` call. It takes two forms: `#[reflect(default)]` and `#[reflect(default = "some_func")]`. The former specifies that `Default::default()` should be used while the latter specifies that `some_func()` should be used. This is pretty much [how serde does it](https://serde.rs/field-attrs.html#default). ### Example ```rust #[derive(Reflect, FromReflect)] struct MyStruct { // Use `Default::default()` #[reflect(default)] foo: String, // Use `get_bar_default()` #[reflect(default = "get_bar_default")] #[reflect(ignore)] bar: usize, } fn get_bar_default() -> usize { 123 } ``` ### Active Fields As an added benefit, this also allows active fields to be completely missing from their dynamic object. This is because the attribute tells `FromReflect` how to handle missing active fields (it still tries to use `from_reflect` first so the `FromReflect` trait is still required). ```rust let dyn_struct = DynamicStruct::default(); // We can do this without actually including the active fields since they have `#[reflect(default)]` let my_struct = <MyStruct as FromReflect>::from_reflect(&dyn_struct); ``` ### Container Defaults Also, with the addition of #3733, people will likely start adding `#[reflect(Default)]` to their types now. Just like with the fields, we can use this to mark the entire container as "defaultable". This grants us the ability to completely remove the field markers altogether if our type implements `Default` (and we're okay with fields using that instead of their own `Default` impls): ```rust #[derive(Reflect, FromReflect)] #[reflect(Default)] struct MyStruct { foo: String, #[reflect(ignore)] bar: usize, } impl Default for MyStruct { fn default() -> Self { Self { foo: String::from("Hello"), bar: 123, } } } // Again, we can now construct this from nothing pretty much let dyn_struct = DynamicStruct::default(); let my_struct = <MyStruct as FromReflect>::from_reflect(&dyn_struct); ``` Now if _any_ field is missing when using `FromReflect`, we simply fallback onto the container's `Default` implementation. This behavior can be completely overridden on a per-field basis, of course, by simply defining those same field attributes like before. ### Related * #3733 * #1395 * #2377 --- ## Changelog * Added `#[reflect(default)]` field attribute for `FromReflect` * Allows missing fields to be given a default value when using `FromReflect` * `#[reflect(default)]` - Use the field's `Default` implementation * `#[reflect(default = "some_fn")]` - Use a custom function to get the default value * Allow `#[reflect(Default)]` to have a secondary usage as a container attribute * Allows missing fields to be given a default value based on the container's `Default` impl when using `FromReflect` Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
…evyengine#4140) # Objective Currently, `FromReflect` makes a couple assumptions: * Ignored fields must implement `Default` * Active fields must implement `FromReflect` * The reflected must be fully populated for active fields (can't use an empty `DynamicStruct`) However, one or both of these requirements might be unachievable, such as for external types. In these cases, it might be nice to tell `FromReflect` to use a custom default. ## Solution Added the `#[reflect(default)]` derive helper attribute. This attribute can be applied to any field (ignored or not) and will allow a default value to be specified in place of the regular `from_reflect()` call. It takes two forms: `#[reflect(default)]` and `#[reflect(default = "some_func")]`. The former specifies that `Default::default()` should be used while the latter specifies that `some_func()` should be used. This is pretty much [how serde does it](https://serde.rs/field-attrs.html#default). ### Example ```rust #[derive(Reflect, FromReflect)] struct MyStruct { // Use `Default::default()` #[reflect(default)] foo: String, // Use `get_bar_default()` #[reflect(default = "get_bar_default")] #[reflect(ignore)] bar: usize, } fn get_bar_default() -> usize { 123 } ``` ### Active Fields As an added benefit, this also allows active fields to be completely missing from their dynamic object. This is because the attribute tells `FromReflect` how to handle missing active fields (it still tries to use `from_reflect` first so the `FromReflect` trait is still required). ```rust let dyn_struct = DynamicStruct::default(); // We can do this without actually including the active fields since they have `#[reflect(default)]` let my_struct = <MyStruct as FromReflect>::from_reflect(&dyn_struct); ``` ### Container Defaults Also, with the addition of bevyengine#3733, people will likely start adding `#[reflect(Default)]` to their types now. Just like with the fields, we can use this to mark the entire container as "defaultable". This grants us the ability to completely remove the field markers altogether if our type implements `Default` (and we're okay with fields using that instead of their own `Default` impls): ```rust #[derive(Reflect, FromReflect)] #[reflect(Default)] struct MyStruct { foo: String, #[reflect(ignore)] bar: usize, } impl Default for MyStruct { fn default() -> Self { Self { foo: String::from("Hello"), bar: 123, } } } // Again, we can now construct this from nothing pretty much let dyn_struct = DynamicStruct::default(); let my_struct = <MyStruct as FromReflect>::from_reflect(&dyn_struct); ``` Now if _any_ field is missing when using `FromReflect`, we simply fallback onto the container's `Default` implementation. This behavior can be completely overridden on a per-field basis, of course, by simply defining those same field attributes like before. ### Related * bevyengine#3733 * bevyengine#1395 * bevyengine#2377 --- ## Changelog * Added `#[reflect(default)]` field attribute for `FromReflect` * Allows missing fields to be given a default value when using `FromReflect` * `#[reflect(default)]` - Use the field's `Default` implementation * `#[reflect(default = "some_fn")]` - Use a custom function to get the default value * Allow `#[reflect(Default)]` to have a secondary usage as a container attribute * Allows missing fields to be given a default value based on the container's `Default` impl when using `FromReflect` Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
…evyengine#4140) # Objective Currently, `FromReflect` makes a couple assumptions: * Ignored fields must implement `Default` * Active fields must implement `FromReflect` * The reflected must be fully populated for active fields (can't use an empty `DynamicStruct`) However, one or both of these requirements might be unachievable, such as for external types. In these cases, it might be nice to tell `FromReflect` to use a custom default. ## Solution Added the `#[reflect(default)]` derive helper attribute. This attribute can be applied to any field (ignored or not) and will allow a default value to be specified in place of the regular `from_reflect()` call. It takes two forms: `#[reflect(default)]` and `#[reflect(default = "some_func")]`. The former specifies that `Default::default()` should be used while the latter specifies that `some_func()` should be used. This is pretty much [how serde does it](https://serde.rs/field-attrs.html#default). ### Example ```rust #[derive(Reflect, FromReflect)] struct MyStruct { // Use `Default::default()` #[reflect(default)] foo: String, // Use `get_bar_default()` #[reflect(default = "get_bar_default")] #[reflect(ignore)] bar: usize, } fn get_bar_default() -> usize { 123 } ``` ### Active Fields As an added benefit, this also allows active fields to be completely missing from their dynamic object. This is because the attribute tells `FromReflect` how to handle missing active fields (it still tries to use `from_reflect` first so the `FromReflect` trait is still required). ```rust let dyn_struct = DynamicStruct::default(); // We can do this without actually including the active fields since they have `#[reflect(default)]` let my_struct = <MyStruct as FromReflect>::from_reflect(&dyn_struct); ``` ### Container Defaults Also, with the addition of bevyengine#3733, people will likely start adding `#[reflect(Default)]` to their types now. Just like with the fields, we can use this to mark the entire container as "defaultable". This grants us the ability to completely remove the field markers altogether if our type implements `Default` (and we're okay with fields using that instead of their own `Default` impls): ```rust #[derive(Reflect, FromReflect)] #[reflect(Default)] struct MyStruct { foo: String, #[reflect(ignore)] bar: usize, } impl Default for MyStruct { fn default() -> Self { Self { foo: String::from("Hello"), bar: 123, } } } // Again, we can now construct this from nothing pretty much let dyn_struct = DynamicStruct::default(); let my_struct = <MyStruct as FromReflect>::from_reflect(&dyn_struct); ``` Now if _any_ field is missing when using `FromReflect`, we simply fallback onto the container's `Default` implementation. This behavior can be completely overridden on a per-field basis, of course, by simply defining those same field attributes like before. ### Related * bevyengine#3733 * bevyengine#1395 * bevyengine#2377 --- ## Changelog * Added `#[reflect(default)]` field attribute for `FromReflect` * Allows missing fields to be given a default value when using `FromReflect` * `#[reflect(default)]` - Use the field's `Default` implementation * `#[reflect(default = "some_fn")]` - Use a custom function to get the default value * Allow `#[reflect(Default)]` to have a secondary usage as a container attribute * Allows missing fields to be given a default value based on the container's `Default` impl when using `FromReflect` Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
Dynamic types (
DynamicStruct
,DynamicTupleStruct
,DynamicTuple
,DynamicList
andDynamicMap
) are used when deserializing scenes, but currently they can only be applied to existing concrete types. This leads to issues when trying to spawn non trivial deserialized scene.For components, the issue is avoided by requiring that reflected components implement
FromResources
FromWorld
(orDefault
). When spawning, a new concrete type is created that way, and the dynamic type is applied to it. Unfortunately, some components don't have any valid implementation of these traits.In addition, any
Vec
orHashMap
inside a component will panic when a dynamic type is pushed into it (for instance,Text
panics when adding a text section).To solve this issue, this PR adds the
FromReflect
trait that creates a concrete type from a dynamic type that represent it, derives the trait alongside theReflect
trait, drops theFromResources
FromWorld
requirement on reflected components,and enables reflection for UI and Text bundles. It also adds the requirement that fields ignored with#[reflect(ignore)]
implementDefault
, since we need to initialize them somehow.