Skip to content
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

EntityRef/Mut get_components #13375

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

Conversation

cart
Copy link
Member

@cart cart commented May 14, 2024

Objective

Implements #13127

EntityRef / EntityMut usage is currently unnecessarily verbose and borrow-checker-constrained. It is currently impossible to use EntityMut to get mutable references to more than one component at the same time:

let mut entity = world.entity_mut(some_entity);
let mut a = entity.get_mut::<A>();
// this fails to compile because `entity` is already borrowed mutably
let mut b = entity.get_mut::<B>();

This makes EntityMut significantly less useful than it could be. Querying necessary components on demand is a pattern many prefer to use over traditional top-level queries.

Solution

Implement new get_components and components methods for EntityRef and EntityMut (and mutable variants for EntityMut) to provide multi-component access:

let (a, b) = world.entity(SOME_ENTITY).components::<(&A, &B)>();
let (a, b) = world.entity(SOME_ENTITY).get_components::<(&A, &B)>().unwrap();
let (mut a, b) = world.entity_mut(SOME_ENTITY).components_mut::<(&mut A, &B)>();
let (mut a, b) = world.entity_mut(SOME_ENTITY).get_components_mut::<(&mut A, &B)>().unwrap();

This is implemented using our existing Query implementation to provide multi-component access. It checks access without actually computing QueryState / access using the very convenient Q::matches_component_set.

Next Steps

  • This doesn't add FilteredEntityRef and FilteredEntityMut variants, as checking that access is actually more challenging (and with the current methods, likely prohibitively expensive because I think we need to compute Access<ComponentId> )
  • This doesn't do the components to get and get_components to try_get renaming / unification I proposed in Add EntityMut::components and EntityRef and EntityWorldMut equivalent #13127 as I suspect that will be more controversial, and it also touches a lot of code.

Changelog

Added

  • EntityRef::components, EntityRef::get_components, EntityMut::components, EntityMut::get_components, EntityMut::components_mut, and EntityMut::get_components_mut.

Changed

  • Renamed FilteredEntityRef::components to FilteredEntityRef::accessed_components and FilteredEntityMut::components to FilteredEntityMut::accessed_components to avoid future name clashes

Migration Guide

  • Rename FilteredEntityRef::components to FilteredEntityRef::accessed_components and FilteredEntityMut::components to FilteredEntityMut::accessed_components

@cart cart added the A-ECS Entities, components, systems, and events label May 14, 2024
@cart cart added this to the 0.14 milestone May 14, 2024
@msvbg
Copy link
Contributor

msvbg commented May 14, 2024

In light of #12660, I would vote against adding more panicking shorthands like components.

Copy link
Contributor

@cBournhonesque cBournhonesque left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this also allow for

let (mut a, mut b) = world.entity_mut(SOME_ENTITY).components_mut::<(&mut A, &mut B)>();

?

Maybe we can add a unit test for that case as well

@alice-i-cecile alice-i-cecile added C-Enhancement A new feature C-Needs-Release-Note Work that should be called out in the blog due to impact X-Uncontroversial This work is generally agreed upon X-Contentious There are nontrivial implications that should be thought through S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels May 14, 2024
@cart
Copy link
Member Author

cart commented May 15, 2024

In light of #12660, I would vote against adding more panicking shorthands like components.

I think discouraging unwrap/panicking variants entirely (as in, not providing impls) would have pretty nasty ergonomics implications for people, especially with the current Option vs Result compatibility problem for ?. I would be very uncomfortable removing those apis right now.

@ramirezmike
Copy link
Contributor

In light of #12660, I would vote against adding more panicking shorthands like components.

I think discouraging unwrap/panicking variants entirely (as in, not providing impls) would have pretty nasty ergonomics implications for people, especially with the current Option vs Result compatibility problem for ?. I would be very uncomfortable removing those apis right now.

I agree somewhat with this. It's frustrating when something like despawn causes a panic due to an unwrap, but if I want to shoot myself in the foot with something like single or components_mut I should be allowed to.

@cart
Copy link
Member Author

cart commented May 15, 2024

Left some replies in #12660

);
assert_eq!(None, world.entity(e2).get_components::<(&X, &Y)>());
assert_eq!(None, world.entity(e3).get_components::<(&X, &Y)>());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a test to make sure it returns none or panics if you do world.entity_mut(e).get_components_mut::<&mut X, &mut X>()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also should have one for (&mut X, &X)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha I'm so glad you asked this question. The Access part of the Query infrastructure was what enforced this elsewhere, so this was allowed. We can't merge this as-is, and fixing it will almost certainly incur a significant perf cost.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't creating an empty access and calling update_component_access(state, &mut access) on it work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would. It would just be dissatisfying from a perf perspective because Access allocates.

}

/// Returns read-only components for the current entity that match the query `Q`.
pub fn get_components<Q: ReadOnlyQueryData>(&self) -> Option<Q::Item<'w>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should add some docs to explain why it might return None. Ditto for the other methods that return an option.

Copy link
Member

@james7132 james7132 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this kind of funny. Thinking about this historically, this feels like we've inverted the relationship between Query::get_component and EntityMut/EntityRef with this change.

I think discouraging unwrap/panicking variants entirely (as in, not providing impls) would have pretty nasty ergonomics implications for people, especially with the current Option vs Result compatibility problem for ?. I would be very uncomfortable removing those apis right now.

I agree that we probably shouldn't remove them until try-blocks or some equivalent are stabilized.

.get(location.archetype_id)
.debug_checked_unwrap()
};
if Q::matches_component_set(&state, &|id| archetype.contains(id)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use bool::then instead of the if/else here.

@ramirezmike
Copy link
Contributor

Since this uses QueryData, this would allow doing stuff like this, right?

world.entity(e).get_components::<(Entity, &X, Option<&Y>, Has<Z>)>();

"components" sorta makes sense for Option/Has... Is Entity a component though? What if QueryData gets implemented with more Component-adjacent stuff in the future?

I'm guessing the next steps item 2 would be renaming these to the get functions in the future?

@cart
Copy link
Member Author

cart commented May 15, 2024

I find this kind of funny. Thinking about this historically, this feels like we've inverted the relationship between Query::get_component and EntityMut/EntityRef with this change.

Haha yeah thats an interesting comparison. Although I think this is significantly more defensible / necessary than Query::get_component.

Since this uses QueryData, this would allow doing stuff like this, right?

Yup!

I'm guessing the next steps item 2 would be renaming these to the get functions in the future?

Yup that is something discussed in the Next Steps section above. Haven't thought of a better interim name. And components is approximately correct.

@cart
Copy link
Member Author

cart commented May 15, 2024

Welp this PR is sadly stalled until we sort out this problem:
#13375 (comment)

Not sure there will be a solution that doesn't involve doing access checks on each call.

@cart
Copy link
Member Author

cart commented May 15, 2024

I think the best we can hope for is "allocation free check every entry against every other entry" (similar to query.many_mut), but accomplishing that will require significant changes (or at least, additions) to how we compute access.

@alice-i-cecile alice-i-cecile added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed X-Uncontroversial This work is generally agreed upon S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels May 15, 2024
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in favor of this existing! I agree with the request for more thorough docs (shock). Nonblocking, but I also think we should be returning Result, not Option here (as with most of our ECS APIs). QueryEntityError looks like the closest fit, but would need to be tweaked to better fit the actual problems encountered (and API used).

Obviously the UB is a blocker here on this getting merged. Given the discovered complexity, I'm removing this from the 0.14 milestone <3 I still want this, and if it makes it it makes it, but it's not correct to try and wait for this to be resolved.

@alice-i-cecile alice-i-cecile removed this from the 0.14 milestone May 15, 2024
@alice-i-cecile
Copy link
Member

I think we should be able to reuse the access mechanisms from queries to validate soundness here. Would need to sit down with the code to figure out exactly how. I much prefer that over creating another mechanism though, especially since I'd like to be able to add untyped variants to this in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Enhancement A new feature C-Needs-Release-Note Work that should be called out in the blog due to impact S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged X-Contentious There are nontrivial implications that should be thought through
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants