Allow dense iteration for FilteredEntity(Ref|Mut) in more cases.#16396
Allow dense iteration for FilteredEntity(Ref|Mut) in more cases.#16396alice-i-cecile merged 12 commits intobevyengine:mainfrom
Conversation
Can you open an issue for this? :) |
This is possible? This is not documented under |
It does work! I confess I only looked at the implementation and not the documentation, so I didn't realize it wasn't documented :). It looks like the subset check hasn't changed much since transmutes were introduced, so I think it's always worked. Looking at the documentation now, that list doesn't seem especially exhaustive. You can also add |
cBournhonesque
left a comment
There was a problem hiding this comment.
I wonder if there is a way to write a test for it?
- we could have a small test showing that
FilteredEntityRefwithOption<&Sparse>access still is dense - but how can we show that it doesn't miss any entity during iteration?
…dentityref # Conflicts: # crates/bevy_ecs/src/query/builder.rs # crates/bevy_ecs/src/query/state.rs
…dentityref # Conflicts: # crates/bevy_ecs/src/query/state.rs
…dentityref # Conflicts: # crates/bevy_ecs/src/query/builder.rs
…dentityref # Conflicts: # crates/bevy_ecs/src/query/state.rs
|
@villor and @Victoronz could you two take a look at this? |
…dentityref # Conflicts: # crates/bevy_ecs/src/query/builder.rs
…dentityref # Conflicts: # crates/bevy_ecs/src/query/state.rs
Objective
Improve the performance of some dynamic queries with
FilteredEntity(Ref|Mut)by allowing dense iteration in more cases, and remove a call to the sort-of deprecatedAccess::component_reads_and_writes()method.QueryBuildercurrently requires sparse iteration if any sparse set components may be read. We do need sparse iteration if sparse set components are used in the filters, butFilteredEntityRefcan still perform dense iteration when reading optional components or when reading all components.Note that the optional case is different from
Option, which performs sparse iteration when the inner query is sparse so that it can cache whether the inner query matches for an entire archetype.Solution
Change
FilteredEntity(Ref|Mut)to haveIS_DENSE = true. It used to require sparse iteration in order to filter theAccessfor each archetype, but #15207 changed it to copy the entire access.Change
QueryBuilder::is_dense()to checkD::IS_DENSE && F::IS_DENSEinstead of looking at the component reads and writes.QueryBuilder::is_dense()still checks the filters, sobuilder.data::<&Sparse>()will still cause sparse iteration, butbuilder.data::<Option<&Sparse>>()no longer will.I believe this is sound, even in the presence of query transmutes. The only
WorldQueryimplementations that rely on a sparse query being sparse for soundness are&,&mut,Ref, andMut, but they can only be transmuted to if the component is in therequiredset. If a dynamic query has the component in therequiredset, then it appears in the filters and the query will use sparse iteration.Note that
OptionandHaswill misbehave and reportNoneandfalsefor all entities if they do a dense query while wrapping a sparse component, but they won't cause UB. And it's already possible to hit that case by transmuting fromQuery<EntityMut>toQuery<Option<&Sparse>>.