-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
EntityRef/EntityMut deduplication
#22538
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?
Conversation
|
It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note. Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes. |
|
I'm not sure if the changes to |
hymm
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.
This is just a high level review. Generally onboard with these changes. It's nice that the new generic doesn't show up in user facing apis. We should probably run benches for any of the methods that we have them for, and I think we should do some spot checking comparing the before and after for the assembly generated.
|
|
||
| /// Returns a reference to the current [`AccessScope`]. | ||
| #[inline] | ||
| pub fn scope(&self) -> &S { |
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 fn scope(&self) -> &S { | |
| pub fn access(&self) -> &S { |
I think scope is a bit ambiguous here. Scope by itself is more a reference to lifetime scopes (or block, function, etc.)
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 down for a rename, but lets also rename AccessScope too; a few suggestions:
AccessPolicyAccessBoundsa bit redundant sinceComponentAccessAccessalready usesComponentIdsAccessFilter
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.
AccessKind
EntityAccess - Less precise if it works with World in the future
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 down for a rename, but lets also rename
AccessScopetoo
What about something like AsAccess? Another way of thinking about this is that we always have an Access, but for All we compress the &'static Access to a ZST.
Hmm, for that matter, would it work to use something like AsRef<Access> or Deref<Target = Access>? In theory, the compiler should be able to inline the Access::new_mutable call and then see that can_read always passes and still eliminate the whole thing. We'd have to test whether that works in practice, of course.
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've updated it to be trait AsAccess: Copy + Deref<Target = Access>. Thoughts?
What would an alternative look like? Since we need access to the fetched It should still be low overhead for power users if they provide an |
chescock
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.
Oh, yay, glad this is back on track! I left some thoughts, and I agree that we should bikeshed scope a bit, but everything looks reasonable!
I also agree that it's worth spot-checking some of the generated assembly. I've found https://github.com/pacak/cargo-show-asm helpful for that.
|
|
||
| /// Returns a reference to the current [`AccessScope`]. | ||
| #[inline] | ||
| pub fn scope(&self) -> &S { |
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 down for a rename, but lets also rename
AccessScopetoo
What about something like AsAccess? Another way of thinking about this is that we always have an Access, but for All we compress the &'static Access to a ZST.
Hmm, for that matter, would it work to use something like AsRef<Access> or Deref<Target = Access>? In theory, the compiler should be able to inline the Access::new_mutable call and then see that can_read always passes and still eliminate the whole thing. We'd have to test whether that works in practice, of course.
| self.world.assert_allows_mutable_access(); | ||
|
|
||
| if !scope.can_write(component_id, self.world.components()) { | ||
| return Err(GetEntityMutByIdError::ComponentNotFound); |
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.
Should this be a new error variant? It's a little misleading to report that the component isn't found when the issue is really that we couldn't access it. Although it would be unfortunate to make callers that want to be exhaustive with EntityMut::get_mut_by_id have to handle an impossible case.
|
This latest commit implements @chescock's suggestion for an I only minimally updated the docs for this rename so that might need an additional pass. cc @hymm |
cde8da9 to
7796fe6
Compare
chescock
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.
This looks very clean!
I do think we should check the assembly to make sure the checks are removed with All access, but I guess even the unoptimized version is just reading a bool, so it might not matter even if it's left in.
Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com>
Diddykonga
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.
LGTM, and that ratio sure is nice 😃
Objective
We can observe that
EntityRef,FilteredEntityRef, andEntityRefExceptprovide the same kind of access: reading components from a certain entity. However, they differ in what they're allowed to access.EntityRefs can access all components, whileFilteredEntityRef's access is determined at runtime andEntityRefExcept's at compile time.A problem arises where each new feature for entity reference types must be duplicated up to six(!) times. This is terrible for code review and maintainability! The solution below reduces the necessary duplication down to just two: one variant for immutable entity references and one variant for mutable ones.
Solution
All three immutable entity reference types (and all three mutable types) have the same mode of operation, so they can be collapsed into just
EntityRef<A: AsAccess = All>andEntityMut<A: AsAccess = All>.AsAccessis a new trait that checks read and write access for a given component:This PR provides the following three accesses:
All: has access to all components. It is the default access, so when you see a plainEntityReforEntityMut, it will act exactly the same as before.Filtered: access is dictated by a held&Access. It acts exactly the same asFilteredEntityRefandFilteredEntityMut, which are now type aliases ofEntityRef<Filtered>andEntityMut<Filtered>, respectively.Except<B: Bundle>: access is dictated by theBgeneric parameter. Just likeEntityRefExceptandEntityMutExcept, it too holds an&Access.EntityRefExcept<B>andEntityMutExcept<B>are now type aliases ofEntityRef<Except<B>>andEntityMut<Except<B>>, respectively.Notes for reviewers
EntityRef<All>andEntityMut<All>have been moved to a newall.rsmodule.Testing
Added tests for the newly introduced
AsAccesss. Otherwise reusing current tests.Looking ahead
Here's what I have plans for after this big collapse:
EntityRef/Mut::get_by_idfamily of functions, and rename them toget_untyped,get_many_untyped,iter_many_untyped,iter_untyped, etc.dyn Reflectinstead ofPtr:get_reflect,get_many_reflect,iter_many_reflect,iter_reflect, etc.EntityWorldMutwithEntityMut<Global>, introducing a newGlobalaccess. This means we getEntityRef<Global>AKAEntityWorldReffor free.EntityRef<Only<B>>andEntityMut<Only<B>>, introducing a newOnlyaccess. This would allow us to splitEntityMuts into pairs ofEntityMutExceptandEntityMutOnly, providing an alternative toEntityMut::get_components_mut.