Skip to content

Expose more data from FilteredAccess#23384

Merged
alice-i-cecile merged 7 commits intobevyengine:mainfrom
chescock:ComponentSet
Mar 19, 2026
Merged

Expose more data from FilteredAccess#23384
alice-i-cecile merged 7 commits intobevyengine:mainfrom
chescock:ComponentSet

Conversation

@chescock
Copy link
Contributor

Objective

Expose more data from FilteredAccess to allow third-party crates to inspect queries. In particular, expose required and filter_sets, which will allow third-party implementations of query observers (#20817) to determine what component observers they will need.

Solution

Add pub methods to expose required and filter_sets, make AccessFilters pub, and add methods to expose with and without.

Add a ComponentIdSet type that wraps FixedBitSet. This allows us to expose the efficient set operations like union_with that would not be available if we returned a simple Iterator<Item = ComponentId>. And not exposing the FixedBitSet directly avoids forcing users to manually convert between usize and ComponentId, and will let us change the implementation of the set (as in #18955) or representation of ComponentId without changing the public API. It also simplifies the internals a bit!

Showcase

Here is how to calculate the component observers required for a query observer in an implementation of #20817 using these APIs:

fn components_for_observers(
    access: &FilteredAccess,
) -> (&ComponentIdSet, ComponentIdSet, ComponentIdSet) {
    // We need `insert` observers for any component we read,
    // since the value has changed.
    // Unbounded access (`EntityRef`) is not supported,
    // since we cannot add observers to all possible components.
    let insert = access
        .access()
        .try_reads_and_writes()
        .expect("Query observers do not support unbounded access");

    // We need `add` observers for any component with archetypal access,
    // since `Has` may have changed,
    let mut add = access.access().archetypal().clone();
    // and any component with `With` filters,
    // since the query may start matching,
    for filter_set in access.filter_sets() {
        add.union_with(&filter_set.with());
    }
    // but not for any component already covered by `insert` observers
    add.difference_with(insert);

    // We need `remove` observers for any component we read or write,
    // since `Option<&T>` may have changed,
    let mut remove = insert.clone();
    // and for any component with archetypal access,
    // since `Has` may have changed,
    remove.union_with(access.access().archetypal());
    // and any component with `Without` filters,
    // since the query may start matching,
    for filter_set in access.filter_sets() {
        add.union_with(&filter_set.without());
    }
    // but not for any required component,
    // since it is guaranteed not to match after they are removed
    // (otherwise every `&T` would add a `remove` observer!)
    remove.difference_with(access.required());

    (insert, add, remove)
}

@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events C-Code-Quality A section of code that is hard to understand or change X-Uncontroversial This work is generally agreed upon D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Mar 16, 2026
@github-project-automation github-project-automation bot moved this to Needs SME Triage in ECS Mar 16, 2026
@alice-i-cecile alice-i-cecile modified the milestone: 0.19 Mar 16, 2026
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 much prefer the semantic newtype over ComponentIdSet: this would be worth doing on its own.

This information is fine to expose, and this is a good way to do so.

I would be happy if there were tests for ComponentIdSet, but I won't block on it :)

@chescock
Copy link
Contributor Author

I much prefer the semantic newtype over ComponentIdSet: this would be worth doing on its own.

Yeah, I wouldn't have thought to do it without the motivation of exposing it more publicly, but once I thought of it I liked it even internally!

I would be happy if there were tests for ComponentIdSet, but I won't block on it :)

Oh, fine, I guess if I'm going to claim that this will let us change the implementation, then I need to actually back it up by having tests :). I'll try to add some on Wednesday!

Copy link
Contributor

@Freyja-moth Freyja-moth left a comment

Choose a reason for hiding this comment

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

Can't see anything wrong and the newtype is appreciated

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Mar 19, 2026
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Mar 19, 2026
Merged via the queue into bevyengine:main with commit 9b91b56 Mar 19, 2026
40 checks passed
@github-project-automation github-project-automation bot moved this from Needs SME Triage to Done in ECS Mar 19, 2026
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-Code-Quality A section of code that is hard to understand or change C-Feature A new feature, making something new possible D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Uncontroversial This work is generally agreed upon

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants