-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Reflect Struct QOL #22708
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
base: main
Are you sure you want to change the base?
Reflect Struct QOL #22708
Conversation
commit 20e6765 Author: Diddykonga <[email protected]> Date: Sun Jan 25 16:53:38 2026 -0600 Suggestion: Change remove_if doc comment Co-authored-by: Gino Valente <[email protected]> commit a8e866c Author: Diddykonga <[email protected]> Date: Sun Jan 25 16:51:57 2026 -0600 Suggestions: remove_with to remove_if, fix core prefix commit 07dde6e Author: Diddykonga <[email protected]> Date: Sun Jan 25 16:01:34 2026 -0600 Fix CI - Update bundle.rs Fixed: clippy::semicolon-if-nothing-returned commit e606839 Author: Diddykonga <[email protected]> Date: Sun Jan 25 06:22:03 2026 -0600 DynamicStruct field removal methods and Struct QOL
| } | ||
|
|
||
| fn name_of(&self, field: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<&str> { | ||
| #(if #bevy_reflect_path::PartialReflect::reflect_partial_eq(#fields_ref, field).unwrap_or(false) { return #fqoption::Some(#field_names) })* |
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.
Nit: you can probably clean this up by using ? out of the reflect_partial_eq.
| } | ||
|
|
||
| fn index_of(&self, field: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<usize> { | ||
| #(if #bevy_reflect_path::PartialReflect::reflect_partial_eq(#fields_ref, field).unwrap_or(false) { return #fqoption::Some(#field_indices) })* |
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.
Same comment as above.
|
|
||
| impl<'a> Iterator for FieldIter<'a> { | ||
| type Item = &'a dyn PartialReflect; | ||
| type Item = (&'a str, &'a dyn PartialReflect); |
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.
This change needs a simple migration guide.
alice-i-cecile
left a comment
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.
Needs a small migration guide, and your other PR merged, but I like these changes. Particularly the iterator change to also include the names!
|
It looks like your PR is a breaking change, but you didn't provide a migration guide. Please review the instructions for writing migration guides, then expand or revise the content in the migration guides directory to reflect your changes. |
MrGVSV
left a comment
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.
I'm not sure I agree with Struct::index_of and Struct::name_of as they currently stand (i.e. taking in a &dyn PartialReflect). But I do like the other ones!
| } | ||
|
|
||
| fn name_of(&self, field: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<&str> { | ||
| #(if #bevy_reflect_path::PartialReflect::reflect_partial_eq(#fields_ref, field).unwrap_or(false) { return #fqoption::Some(#field_names) })* |
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.
As mentioned in this comment, I'm still not fully sold on this behavior.
To summarize my thoughts, I believe this behavior (i.e. where a field from one struct returns None on a field of another struct of the same type) may be too unintuitive.
I'm on board with index_by_name (though I think it should be renamed to index_of) and name_at, but index_of (in its current form) and name_of seem too situational and easy to mess up.
I'm willing to change my mind if people feel strongly that this is useful/necessary, though.
| /// Gets the name of the field, if it exists. | ||
| fn name_of(&self, field: &dyn PartialReflect) -> Option<&str>; |
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.
We should probably document that this only checks the address of the pointer. And because of this, the method will return None if it's given a reference to the same field from a different struct of the same type or if the field is not the original (i.e. it's a clone of the original).
As mentioned in my comment here, I do think that the address equality is a limitation of the current implementation. Therefore, it may not really belong in the documentation (in case users choose to manually implement it another way that can guarantee it works across struct instances and clones). However, the default implementation from the derive macro is going to be the most common, so I think it's definitely worth noting.
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.
This behaviour, during the split, was changed to rely upon PartialReflect::reflect_partial_eq, this comment seems assume its still using the previous core::ptr::addr_eq impl? Not sure how much of this coment chain applies with the change.
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.
Oh lol sorry yeah I just assumed it was a direct split and didn't actually check it was changed. My apologies!
However I think the same critique still applies: unless you’re using the same struct or struct with equivalent field value, this will return None. And now this may also return the incorrect Some value in the case that two fields have the same value.
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.
That's fair, I think both approaches have a downside, we could provide both? but that feels a little meh
name_of/name_of_field + name_at_addr.
| } | ||
|
|
||
| fn name_of(&self, field: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<&str> { | ||
| #(if #bevy_reflect_path::PartialReflect::reflect_partial_eq(#fields_ref, field).unwrap_or(false) { return #fqoption::Some(#field_names) })* |
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.
One idea for getting around the limitations I mention here, is to instead make name_of and friends return a Result with a potential error of like NameOfError::NonUniqueFieldType. And then return that error if the struct contains two of the same type field.
But I don't love that idea as it still is super limiting (just not as much).
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.
Another option that I think would work better is to provide a low-level API for working with struct field pointers and addresses directly. Then you could do something like:
let api = TheApi::from(&my_struct);
let field: NamedField = api.field_at_addr(&my_struct.field).unwrap();
let name = field.name();In fact, renaming Struct::name_of to Struct::name_at_addr or something would probably be an okay compromise. But I still also don't know if this sort of low level API belongs in Struct (of course, the benefit is that we don't have to go through get_represented_type_info).
Objective / Solution
Add methods for additional ways to get field info on
Struct:Change
FieldIterto return an tuple over the names/fields and implIntoIteratorfor&dyn Structfor ergonomics.Split From/Merge After: #22702
Testing
Migrations:
DynamicStruct::index_of(name)->Struct::index_by_name(name)Showcase
Click to view showcase