-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Allow querying multiple components from FilteredEntityMut #21182
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?
Allow querying multiple components from FilteredEntityMut #21182
Conversation
Why not an I would expect the advantage of If we do stick with this approach, I'd be inclined to name it Alternately, we might want to copy pub unsafe fn reborrow_unsafe(&self) -> FilteredEntityMut<'_, 's> { That could help catch some errors, at the cost of being unable to return multiple items with the original |
That's a great point, I didn't know that we had |
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's add the other method in the same PR please: I think both should exist, but most use cases should use the cheaper and more scoped form.
@chescock @alice-i-cecile I added the two extra methods; also instead of
|
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 implementations all seem good, but I don't think UnsafeFilteredEntityMut
will actually address @hymm's concerns. A function that takes &FilteredEntityMut
could call UnsafeFilteredEntityMut::new_readonly(e).into_mut()
just as easily as it could call e.clone()
.
I'd be inclined to leave it out entirely, especially if we wind up making Query::reborrow_unsafe
non-pub
as @hymm suggests. But I also see that @alice-i-cecile asked you to include both methods in this PR.
/// use this in cases where the actual component types are not known at | ||
/// compile time.** | ||
/// | ||
/// Unlike [`FilteredEntityMut::get_mut`], this returns a raw pointer to the component, |
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.
Copying the existing doc comment seems good for this PR, but get_mut_by_id
does not return a raw pointer, so we should probably do a follow-up to fix the comments everywhere. (Maybe it was originally written before MutUntyped
existed?)
You're right. I guess a function could also take a I could manually use UnsafeWorldCell to recreate my UnsafeFilteredEntityMut, it's just very tedious to do so since I would have to manually create the Access, etc. So it's not like we're giving users new unsafe capabilities, they already exist via UnsafeWorldCell. As for this PR, I'm happy to do any of:
|
Co-authored-by: Chris Russell <[email protected]>
Co-authored-by: Chris Russell <[email protected]>
/// let a: Mut<A> = filtered_entity.get_mut().unwrap(); | ||
/// let b: Mut<B> = filtered_entity_clone.get_mut().unwrap(); | ||
/// ``` | ||
#[derive(Copy, Clone)] |
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 remove Copy here: it's far too much of a footgun for limited benefit.
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 don't think so?
You can't use UnsafeFilteredEntityMut
for anything else but to convert it back into a FilteredEntityMut, and you need an unsafe function call for that.
UnsafeWorldCell
is also Copy, for instance
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.
A few things:
- We should be adding breadcrumbs from
FilteredEntityMut
to this new type. - We should remove the
Copy
derive. - We should deduplicate the
get_mut
/get_mut_unchecked
code paths.
Once that's done I'm happy with this PR. I do think that there's real value in making it easier and less error-prone to use our unsafe methods: paving the metaphorical cow paths will make migrations easier and reduce accidental UB.
If we want an escape hatch I think we should expose a method to convert a *EntityMut to an UnsafeEntityCell. That way anything using the UnsafeEntityCell has to pass the unsafety upwards. |
Yes but then we would lose the information about access. The only 'unsafety' we want to not check is aliasing. |
This analogy isn't quite correct. UnsafeWorldCell has all the unsafe methods and World only has safe methods. This allows bevy to isolate the aliasing of world to clear points. In the multihreaded executor that means tracking the access of the systems and only converting back to a &mut World when no other systems are running. For an external library I would be surprised if any of them took a UnsafeWorldCell as an input, because it's very hard for the library to know the provenance of that UnsafeWorldCell and not cause UB. So if we were following that model we should move any unsafe methods for Query's are sort of in the situation where we had all the unsafe methods on &World before the creation of UnsafeWorldCell. The reason this isn't too much of a problem is that Query's are typed and it is hard for that reason for libraries to take a query as an input. Especially because of things like the orphan rule. Thinking about this a bit mixing things like query transmutes and the unsafe methods on Query would probably be bad, but I think they're both rare enough currently that it probably isn't a problem. If their use ever become more common then it certainly could be and we might want to consider making a UnsafeQuery type. So what does this mean for FilteredEntityMut? I do think FilteredEntityMut is more similar to World as it is unsafe. And I can see libraries taking a &mut FilteredEntityMut as a input. So I see a couple options here:
My vote is for 2 due to all the api duplication. I think if you're already in unsafe land it wouldn't be that much of a burden not to access anything the FilteredEntityMut shouldn't and the user will probably already have a good sense of what the provenance of it is so you might as well do less work and skip the access checks too. |
I thought of a third option. We could make a EntityCell that would be a fully runtime checked thing that would track all the borrows out of it. Potentially could be pretty useful. |
This came up ages ago in an old RFC: bevyengine/rfcs#42 . I've softened on it now that there's better patterns for 99% of uses, so I'm definitely open to this :) |
Objective
#20265 introduced a way to fetch multiple mutable components from an
EntityMut
, but it's still impossible to do so via anFilteredEntityMut
.I believe it is currently impossible to get two mutable components from a
FilteredEntityMut
, which somewhat limits use cases with dynamic queries.Solution
A similar solution is harder to implement for
FilteredEntityMut
because the QueryData must go through the access checks, and it's not obvious to get the ComponentIds from aReleaseStateQueryData
.Instead, I opt in to provide a similar abstraction as
UnsafeEntityCell
andUnsafeWorldCell
, which are both public and Clone: an opt-in escape catch for advanced users that can guarantee that they are not causing any aliasing violations.Here, instead we provide a method that copies the underlying
UnsafeEntityCell
, so the safety requirements are similar toUnsafeEntityCell
andUnsafeWorldCell
.Testing
Added a doctest.