Skip to content

Faster FilteredEntity(Ref|Mut) and Entity(Ref|Mut)Except by borrowing Access #20111

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

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

Conversation

chescock
Copy link
Contributor

Objective

Improve the performance of queries using FilteredEntityRef, FilteredEntityMut, EntityRefExcept, and EntityMutExcept. In particular, this appears to speed up bevy_animation::animate_targets by 10% in many-foxes.

FilteredEntity(Ref|Mut) needs to store an Access to determine which components may be accessed. Prior to #15396, this required cloning the Access for each instance. Now, we can borrow the Access from the query state and make cheap pointer copies.

Entity(Ref|Mut)Except avoided needing to clone an Access by calling functions on the Bundle trait. Unfortunately, that meant we needed to convert from a type to a ComponentId for every component in the bundle on every check. Now, we can do those conversions up front and pass references to an Access.

Finally, fix a bug where Entity(Ref|Mut)Except would not initialize their components during init_state. I noticed this while updating init_state and fixed it while I was there. That was normally harmless because the components would be registered elsewhere, but a system like fn system(_q1: Query<EntityMutExcept<C>>, _q2: Query<&mut C>) {} would fail to find the ComponentId for C and not exclude it from the access for q1, and then panic with conflicting access from q2.

Solution

Change FilteredEntityRef and FilteredEntityMut to store &'s Access instead of Access, and change EntityRefExcept and EntityMutExcept to store an extra &'s Access.

This adds the 's lifetime to those four types, and most changes are adding lifetimes as appropriate.

Change the WorldQuery::State for Entity(Ref|Mut)Except to store an Access that can be borrowed from, replacing the SmallVec<[ComponentId; 4]> that was used only to set the query access.

To support the conversions from EntityRef and EntityMut, we need to be able to create a &'static Access for read-all or write-all. I could not change fn read_all_components() to be const because it called the non-const FixedBitSet::clear(), so I created separate constructor functions.

Testing

Ran cargo run --example many_foxes --features bevy/trace_tracy --release before and after, and compared the results of animate_targets, since that is the only in-engine use of EntityMutExcept and was the motivation for creating it.

Yellow is this PR, red is main:

image

@chescock chescock added A-ECS Entities, components, systems, and events C-Performance A change motivated by improving speed, memory usage or compile times S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jul 13, 2025
Copy link
Contributor

@hymm hymm left a comment

Choose a reason for hiding this comment

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

The changes make sense to me and the code feels a bit cleaner now too. Nice trick to make a const access for the conversions. I wouldn't of thought to do that.

Could you add a test for the bug you found? We've had issues with init_state before and it's easy for this stuff to get messed up in refactors.

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-Performance A change motivated by improving speed, memory usage or compile times S-Needs-Review Needs reviewer attention (from anyone!) to move forward
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants