Skip to content

Conversation

chescock
Copy link
Contributor

Objective

For #17647, we want to create a QueryData that can follow a relation and query data from an entity's parent. If the parent does not have the queried data, the child entity should be skipped in the query. However, there is no way to tell from the child's archetype whether the parent will match! So, we need to support non-archetypal QueryData, just as we support non-archetypal QueryFilters for Added and Changed.

That is, if Query<Parent<&T>> yields &T, and we do:

let parent1 = world.spawn(T).id();
let child1 = world.spawn(ChildOf(parent1));
let parent2 = world.spawn(()).id();
let child2 = world.spawn(ChildOf(parent2));

let query = world.query::<Parent<&T>>();

then query must yield a row for child1 but not for child2, even though they have the same archetype.

Solution

Change QueryData::fetch to return Option so that entities can be filtered during fetching by returning None.

To support ExactSizeIterator, introduce an ArchetypeQueryData trait and an QueryData::IS_ARCHETYPAL associated constant, similar to ArchetypeFilter and QueryFilter::IS_ARCHETYPAL. Implement this trait on existing QueryData types. Modify ExactSizeIterator implementations to require D: ArchetypeQueryData, and the size_hint() methods to return a minimum size of 0 if !D::IS_ARCHETYPAL.

Alternatives

We could do nothing here, and have Query<Parent<&T>> yield Option<&T>. That makes the API less convenient, though. Note that if one wants to query for Option, they can use either Query<Option<Parent<&T>> or Query<Parent<Option<&T>>, depending on whether they want to include entities with no parent.

Another option is to re-use the ArchetypeFilter trait instead of introducing a new one. There are no places where we want to abstract over both, however, and it would require writing bounds like D: QueryData + ArchetypeFilter, F: QueryFilter + ArchetypeFilter instead of simply D: ArchetypeQueryData, F: ArchetypeFilter.

@chescock chescock added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Oct 17, 2025
@alice-i-cecile alice-i-cecile added this to the 0.18 milestone Oct 17, 2025
@cBournhonesque
Copy link
Contributor

cBournhonesque commented Oct 18, 2025

I ran a benchmark (on laptop):

group                                         after                                  before
-----                                         -----                                  ------
empty_archetypes/iter/10                      1.00      7.7±0.05µs        ? ?/sec    1.02      7.9±0.46µs        ? ?/sec
empty_archetypes/iter/100                     1.04      8.4±1.41µs        ? ?/sec    1.00      8.1±0.37µs        ? ?/sec
empty_archetypes/iter/1000                    1.00      8.2±0.39µs        ? ?/sec    1.02      8.4±0.18µs        ? ?/sec
empty_archetypes/iter/10000                   1.06     11.3±2.08µs        ? ?/sec    1.00     10.6±0.73µs        ? ?/sec
events_iter/size_16_events_100                1.08     60.3±3.70ns        ? ?/sec    1.00     55.9±1.13ns        ? ?/sec
events_iter/size_16_events_1000               1.06   543.4±27.97ns        ? ?/sec    1.00   513.4±22.81ns        ? ?/sec
events_iter/size_16_events_10000              1.03      5.3±0.19µs        ? ?/sec    1.00      5.2±0.27µs        ? ?/sec
events_iter/size_4_events_100                 1.05     63.3±7.21ns        ? ?/sec    1.00     60.3±8.39ns        ? ?/sec
events_iter/size_4_events_1000                1.01   538.8±21.85ns        ? ?/sec    1.00   534.2±10.39ns        ? ?/sec
events_iter/size_4_events_10000               1.14      5.7±0.47µs        ? ?/sec    1.00      5.0±0.08µs        ? ?/sec
events_iter/size_512_events_100               1.02     58.5±2.47ns        ? ?/sec    1.00     57.6±2.06ns        ? ?/sec
events_iter/size_512_events_1000              1.00   531.8±25.83ns        ? ?/sec    1.01   536.6±48.19ns        ? ?/sec
events_iter/size_512_events_10000             1.03      5.3±0.19µs        ? ?/sec    1.00      5.2±0.22µs        ? ?/sec
iter_fragmented(4096)_empty/foreach_sparse    1.02      8.2±0.52µs        ? ?/sec    1.00      8.1±0.12µs        ? ?/sec
iter_fragmented(4096)_empty/foreach_table     1.00      2.1±0.19µs        ? ?/sec    1.02      2.2±0.20µs        ? ?/sec
iter_fragmented/base                          1.11   266.8±26.12ns        ? ?/sec    1.00    240.9±4.87ns        ? ?/sec
iter_fragmented/foreach                       1.01     78.9±4.20ns        ? ?/sec    1.00     78.4±3.63ns        ? ?/sec
iter_fragmented/foreach_wide                  1.00      2.2±0.02µs        ? ?/sec    1.07      2.4±0.21µs        ? ?/sec
iter_fragmented/wide                          1.02      2.6±0.24µs        ? ?/sec    1.00      2.6±0.06µs        ? ?/sec
iter_fragmented_sparse/base                   1.07      5.1±0.06ns        ? ?/sec    1.00      4.7±0.03ns        ? ?/sec
iter_fragmented_sparse/foreach                1.00      3.8±0.11ns        ? ?/sec    1.06      4.0±0.04ns        ? ?/sec
iter_fragmented_sparse/foreach_wide           1.00     31.9±2.32ns        ? ?/sec    1.04     33.1±2.29ns        ? ?/sec
iter_fragmented_sparse/wide                   1.02     37.4±2.71ns        ? ?/sec    1.00     36.6±0.84ns        ? ?/sec
iter_simple/base                              1.03      6.0±0.37µs        ? ?/sec    1.00      5.9±0.16µs        ? ?/sec
iter_simple/foreach                           1.01      3.3±0.08µs        ? ?/sec    1.00      3.3±0.02µs        ? ?/sec
iter_simple/foreach_hybrid                    1.03     10.2±0.64µs        ? ?/sec    1.00     10.0±0.19µs        ? ?/sec
iter_simple/foreach_sparse_set                1.01     14.4±0.66µs        ? ?/sec    1.00     14.3±0.61µs        ? ?/sec
iter_simple/foreach_wide                      1.00     15.4±0.44µs        ? ?/sec    1.01     15.5±0.96µs        ? ?/sec
iter_simple/foreach_wide_sparse_set           1.29   354.4±69.46µs        ? ?/sec    1.00   274.0±56.14µs        ? ?/sec
iter_simple/sparse_set                        1.05     15.4±1.75µs        ? ?/sec    1.00     14.7±0.60µs        ? ?/sec
iter_simple/system                            1.01      5.9±0.45µs        ? ?/sec    1.00      5.8±0.03µs        ? ?/sec
iter_simple/wide                              1.00     52.6±4.16µs        ? ?/sec    1.00     52.5±3.76µs        ? ?/sec
iter_simple/wide_sparse_set                   1.05   418.7±54.28µs        ? ?/sec    1.00   399.2±65.53µs        ? ?/sec
par_iter_simple/hybrid                        1.00     55.8±3.46µs        ? ?/sec    1.01     56.2±1.57µs        ? ?/sec
par_iter_simple/with_0_fragment               1.00     29.2±1.41µs        ? ?/sec    1.00     29.2±0.22µs        ? ?/sec
par_iter_simple/with_1000_fragment            1.00     36.6±2.72µs        ? ?/sec    1.01     37.1±0.28µs        ? ?/sec
par_iter_simple/with_100_fragment             1.01     30.5±2.54µs        ? ?/sec    1.00     30.2±0.54µs        ? ?/sec
par_iter_simple/with_10_fragment              1.02     30.5±4.57µs        ? ?/sec    1.00     30.0±1.72µs        ? ?/sec
world_query_iter/50000_entities_sparse        1.00     22.7±0.74µs        ? ?/sec    1.44     32.6±0.44µs        ? ?/sec
world_query_iter/50000_entities_table         1.02     16.0±0.52µs        ? ?/sec    1.00     15.7±0.26µs        ? ?/sec

Seems to be within noise.
I tried to look at the asm for fetch and got

LBB518_2:
        ldr x8, [sp, #24]
                // src/query/mod.rs:49
                if let Some(inner) = self {
        ldur x10, [x29, #-32]
        str x10, [sp, #16]
        ldur x9, [x29, #-24]
        str x9, [sp, #8]
        stur x10, [x29, #-16]
        stur x9, [x29, #-8]
                // src/query/fetch.rs:1658
                let table = unsafe { table.debug_checked_unwrap() };
        str x10, [sp, #56]
        str x9, [sp, #64]
                // src/query/fetch.rs:1660
                let item = unsafe { table.get(table_row.index()) };
        ldr w0, [x8]
        bl bevy_ecs::storage::table::TableRow::index
        ldr x1, [sp, #8]
        mov x2, x0
        ldr x0, [sp, #16]
        bl bevy_ptr::ThinSlicePtr<T>::get
        mov x8, x0
        stur x8, [x29, #-40]
                // src/query/fetch.rs:1661
                item.deref()
        bl <&core::cell::UnsafeCell<T> as bevy_ptr::UnsafeCellDeref<T>>::deref
                // src/query/fetch.rs:1662
                },
        ldp x29, x30, [sp, #112]
        add sp, sp, #128
        ret

But i don't know how to interpret the results; an LLM said that the Option was indeed optimized away

@cBournhonesque
Copy link
Contributor

Previous approval was a mistake, but I don't know how to remove it..

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.

Approved contingent on #17647 is the direction we want to go in. I haven't reviewed that pr yet, so I have no opinion on that yet. The changes here are pretty straightforward.

@cBournhonesque cBournhonesque 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 Oct 21, 2025
@chescock chescock mentioned this pull request Oct 22, 2025
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-Feature A new feature, making something new possible M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants