-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
reflect: implement the unique reflect rfc #7207
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
Conversation
|
Yooo! Awesome. Thank you so much for taking the time to implement the RFC. I'm sorry for not doing it myself. This would unlock the next step for bevy_reflect. I'll at least take the time to review this thoroughfully. |
nicopap
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.
Theory hits reality at full speed and hard!
The PR is actually not bad, it's just a lot of simple changes. So I encourage people to check it out. It's just a bit time consuming.
This really questions the necessity/feasibility of unique reflect. With this PR, the problems the RFC sets to solve are not solved. And I'm not sure there is a clear path to actually solve them. Specifically, how could we get rid of the try_downcast_ref() methods on dyn PartialReflect while staying ergonomic?
I really don't want to force users to do my_value.as_full().unwrap().downcast_ref() instead of my_value.try_downcast_ref() everywhere.
I think a bit I missed in the RFC is this:
- Most things should now accept a
dyn PartialReflectrather than
dyn Reflect.
Should be:
- Most things should now accept a
dyn PartialReflectrather than
dyn Reflect.- Most thing that return
dyn Reflectshould still returndyn Reflect.
Then unique reflect would make sense, otherwise it's just needless boilerplate.
Is it an achievable goal? If not, I think we should drop the unique Reflect RFC.
If it is achievable (even if not within this PR), then just a few comments and this is good to go.
i think it's probably alright. most of the diff in this PR come from just renaming returning i'll give the changes a once over when i get the time because as you've pointed out a lot of return types and probably some trait bounds are i reckon some of the changes will be better suited to a follow-up PR merged in the same release (i'm not expecting this to be merged by 0.10) and/or when the current situation surrounding |
8e3488c to
910f654
Compare
nicopap
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.
Looks good. Just some minor issues to address.
9f10455 to
6663dfc
Compare
crates/bevy_reflect/bevy_reflect_derive/src/impls/full_reflect.rs
Outdated
Show resolved
Hide resolved
crates/bevy_reflect/src/reflect.rs
Outdated
| fn into_any(self: Box<Self>) -> Box<dyn Any>; | ||
| /// Returns the value as a fully-reflected [`&dyn Reflect`](Reflect), | ||
| /// or [`None`] if the value does not implement it. | ||
| fn try_as_reflect(&self) -> Option<&dyn Reflect>; |
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.
Bikeshed: I think the convention I've seen is that try_*** methods return a Result. I wonder if we should do the same. That might mean adding a new error type for the dynamic types to return.
We could also go back to naming these as_reflect and just return the Option.
Same for the methods on 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.
The RFC uses "canonical" for Reflect, so it's probably better to keep to .as_canonical or even .canonical_ref() , considering std::Any has a downcast_ref returning Option<&T>.
"Canonical" risks getting users asking "does this method only works on ubuntu-based linux distributions". But it is probably easier as an implementor to keep consistent, since that's the terminology we settled on.
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 think the convention I've seen is that try_*** methods return a Result.
this is certainly the most common form for try_*** methods, but i don't see any reason why Options shouldn't be included. the Try trait is implemented for both types, and so are supported by ?. std's methods try_for_each etc. support Option.
even if we consensus disagrees i would prefer returning a Result<&dyn Reflect, BikeshedUnitReflectUpcastError> than changing the name of the method.
i don't prefer as_canonical at all. it's both vague and conceptual: my rule of thumb for good naming is that users should be able to understand why the method has its name from just its signature and documentation, without doing any wider research and i don't think a method like this should have more than two lines of documentation.
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.
even if we consensus disagrees i would prefer returning a
Result<&dyn Reflect, BikeshedUnitReflectUpcastError>than changing the name of the method.
Yeah I agree. It might also set us up for potential error handling in the future.
i don't prefer
as_canonicalat all. it's both vague and conceptual
Yeah canonical works for the RFC, but in practice I think I like the reflect/partial_reflect relation better. I think it will be a lot easier to grasp for newcomers.
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.
The objections make sense. I'm not married to canonical. I think "try_as_foo" is a bit weird though. Maybe "as_reflect" or "try_reflect"
| key: Box<dyn Reflect>, | ||
| mut value: Box<dyn Reflect>, | ||
| ) -> Option<Box<dyn Reflect>> { | ||
| key: Box<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.
Most dynamic types don't implement reflect_hash. So maybe until #8695, we should enforce this by making the key dyn Reflect (and same for all other methods that require a hashable key). While this would be more strict, it would also be more helpful since we could catch this error at compile time rather than runtime.
Thoughts?
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'd rather not change this method twice. reflect_hash is quite goofy but i'd rather not induce two sets of churn for an often-unused api with a solution already in the works.
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 wouldn't say it's unused— we get a fair number of people inadvertently running into this. But your reasoning makes sense and we can just wait for #8695 to land before slapping on bandaids.
nicopap
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 approve, because it makes a much needed change and I don't see anything wrong. But I really think we should reconsider naming.
I looked back at the RFC. And I think the old name was better. This adds a lot of noise to the Reflect API. Basically 70% of the code change here could be removed if we swapped what was renamed.
It's important to have the most common type be the short name, so that when naming things, the additional qualifier in the name shows what's special about it. It builds on how we human pattern-match the world. The other way around is confusing IMO.
So the RFC used CanonReflect. What about:
CanonicalReflectorUniqueReflectfor what the RFC callsReflectReflectfor what the RFC callsPartialReflect
| pub contains: fn(EntityRef) -> bool, | ||
| /// Function pointer implementing [`ReflectComponent::reflect()`]. | ||
| pub reflect: fn(EntityRef) -> Option<&dyn Reflect>, | ||
| pub reflect: fn(EntityRef) -> Option<&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.
The components are always stored as concrete types, might as well return the restricted type.
| pub reflect: fn(EntityRef) -> Option<&dyn PartialReflect>, | |
| pub reflect: fn(EntityRef) -> Option<&dyn Reflect>, |
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 totally agree that this can be changed without much resistance but i'm not sure we want to. currently all ComponentIds map 1-to-1 with Rust types, but if we (and we do want to) support dynamic component types this relationship will change, and i think there's good reasons to want to reflect those components with this api.
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 still think we need a place where we collect all the "either PartialReflect or Reflect" decisions. If you are willing to write a comment in the RFC discussions, I'll take care to update the RFC with such a new section.
| pub reflect: fn(EntityRef) -> Option<&dyn PartialReflect>, | ||
| /// Function pointer implementing [`ReflectComponent::reflect_mut()`]. | ||
| pub reflect_mut: for<'a> fn(&'a mut EntityMut<'_>) -> Option<Mut<'a, dyn Reflect>>, | ||
| pub reflect_mut: for<'a> fn(&'a mut EntityMut<'_>) -> Option<Mut<'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.
same here
| pub reflect_mut: for<'a> fn(&'a mut EntityMut<'_>) -> Option<Mut<'a, dyn PartialReflect>>, | |
| pub reflect_mut: for<'a> fn(&'a mut EntityMut<'_>) -> Option<Mut<'a, dyn Reflect>>, |
| pub remove: fn(&mut World), | ||
| /// Function pointer implementing [`ReflectResource::reflect()`]. | ||
| pub reflect: fn(&World) -> Option<&dyn Reflect>, | ||
| pub reflect: fn(&World) -> Option<&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.
| pub reflect: fn(&World) -> Option<&dyn PartialReflect>, | |
| pub reflect: fn(&World) -> Option<&dyn Reflect>, |
crates/bevy_reflect/src/reflect.rs
Outdated
| fn into_any(self: Box<Self>) -> Box<dyn Any>; | ||
| /// Returns the value as a fully-reflected [`&dyn Reflect`](Reflect), | ||
| /// or [`None`] if the value does not implement it. | ||
| fn try_as_reflect(&self) -> Option<&dyn Reflect>; |
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.
The RFC uses "canonical" for Reflect, so it's probably better to keep to .as_canonical or even .canonical_ref() , considering std::Any has a downcast_ref returning Option<&T>.
"Canonical" risks getting users asking "does this method only works on ubuntu-based linux distributions". But it is probably easier as an implementor to keep consistent, since that's the terminology we settled on.
| /// struct MyTupleStruct(usize); | ||
| /// | ||
| /// let my_tuple_struct: &dyn Reflect = &MyTupleStruct(123); | ||
| /// let my_tuple_struct: &dyn PartialReflect = &MyTupleStruct(123); |
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.
| /// let my_tuple_struct: &dyn PartialReflect = &MyTupleStruct(123); | |
| /// let my_tuple_struct: &dyn Reflect = &MyTupleStruct(123); |
|
@soqb, are you able to clean this up? This is in the 0.12 milestone and I'd like to move this forward, but it looks like there's more work to be done here. |
|
sorry for the late reply, i've had an incredibly busy week but hope to find some time tonight to address what i need to here. |
This was briefly talked about in this thread, but I'd like to push back on this. If I remember right, the reason we chose However, I will admit that So my vote is for |
|
Pointing that out to avoid regrets down the line. Unlike Instead of seeing I'm fine with the current compromise though. |
I don't think its usage as a trait object prevents us from using the
I think the difference with That being said, I see your point about not needing a
I left a message on Discord to draw more attention to this issue since the entire PR is a big shift and we want to ensure the community is generally on board with whatever we end up doing. |
i think it boils down to what is the most accessible. even with good docs, to me, it seems more helpful to say |
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.
Looks solid: no blocking concerns! I've merged a couple of grammar nits. @soqb, once this is merge conflict free ping me and let's get this merged.
…t-unique-reflect
Co-authored-by: Alice Cecile <[email protected]> Co-authored-by: Gino Valente <[email protected]>
ea99863 to
6363a74
Compare
|
A doc link on ReflectSet needs cleaning up before this can merge: https://github.com/bevyengine/bevy/actions/runs/10324580350/job/28584326492?pr=7207#step:6:2042 |
|
Thank you to everyone involved with the authoring or reviewing of this PR! This work is relatively important and needs release notes! Head over to bevyengine/bevy-website#1671 if you'd like to help out. |
|
Looks like this PR missing in the migration guide for some reason. |
Objective
Solution
Reflect: AnybutPartialReflect: ?Any. During initial implementation I tried this, but we assume thePartialReflect: 'staticin a lot of places and the changes required crept out of the scope of this PR.PartialReflect::try_into_reflectoriginally returnedOption<Box<dyn Reflect>>but i changed this toResult<Box<dyn Reflect>, Box<dyn PartialReflect>>since the method takes by value and otherwise there would be no way to recover the type.as_fullandas_full_mutboth still returnOption<&(mut) dyn Reflect>.Changelog
PartialReflect.Reflectis now a subtrait ofPartialReflect.Reflectto the newPartialReflect.PartialReflect::{as_partial_reflect, as_partial_reflect_mut, into_partial_reflect}.PartialReflect::{try_as_reflect, try_as_reflect_mut, try_into_reflect}.<dyn PartialReflect>::{try_downcast_ref, try_downcast_mut, try_downcast, try_take}supplementing the methods ondyn Reflect.Migration Guide
dyn Reflectshould be changed todyn PartialReflectwhich is less restrictive, however trait bounds should generally stay asT: Reflect.PartialReflect::{as_partial_reflect, as_partial_reflect_mut, into_partial_reflect, try_as_reflect, try_as_reflect_mut, try_into_reflect}methods as well asReflect::{as_reflect, as_reflect_mut, into_reflect}will need to be implemented for manual implementors ofReflect.Future Work
Reflectinstead ofPartialReflect.try_*methods ondyn PartialReflectsince they are stop gaps.ReflectComponent. In the places they currently usePartialReflect, should they be changed to useReflect?Reflectabletrait and makeFromReflecta requirement to improveFromReflectergonomics. This is currently not possible because dynamic types cannot sensibly beFromReflect.Reflectabletrait #5772, bevy_reflect: Recursive registration #5781 would be made cleaner.