Skip to content

Conversation

cBournhonesque
Copy link
Contributor

@cBournhonesque cBournhonesque commented Oct 23, 2025

Objective

We want to be able to run queries that span multiple entities, for example:
Get the Name of entities with Spaceship, that are DocketTo entities with Planet
(which is roughly Query<(&Name, DockedTo<(), With<Planet>>), With<SpaceShip>)>

It is already possible to do this by using multiple queries in a system, but the search can be more efficient is a single query is aware of all the constraints between the multiple sources.

Here we use 'source' to designate an entity for which we are trying to fit a constraint.
In bevy, all the current queries are single-source: we are iterating through entities that fit the constraint Query<D, F>.

For multi-source queries, you iterate through tuples of entities that fit the constraint.
For example Query<(&Name, DockedTo<(), With<Planet>>), With<SpaceShip>)>, iterates through all pairs (E1, E2) where
E1 satisfies Query<(&Name, With<SpaceShip>), E2 satisfies Query<(), With<Planet>> and E1/E2 are linked with DockedTo(E1, E2).

Solution

This PR currently adds everything in a separate file that doesn't interact with the rest of the codebase because:

  • the current QueryState/QueryIterationCursor makes some assumptions that only work for single-source queries
  • the PR is WIP, and I am still unsure if we want to

It adds:

  • a QueryPlan which contains an untyped way of representing the multi-source query, as well as a QueryPlanBuilder.
    The query is built like so:
builder
   .add_source<D, F>()   // source 0
   .add_relationship<R>(0, 1)    // relationship from source 0 to 1
   .add_source<D2, F2>()     // source 1
   .add_relationship<R2>(1, 2)   // relationship from source 1 to 2
   .add_source<D2, F2>()     // source 2
  • A function QueryPlan::query_iter() which returns an iterator that returns the tuple of entities (one entity per source) that matches the query. The item we return is actually a tuple of UnsafeEntityCell; we could convert it to a tuple of FilteredEntityRef/Mut (if the query has dynamic components) or a tuple of (D1, D2, D3) (one typed-data per source).

Soft limitations (these are not real blockers, they were just not implemented in this prototype)

  • only supports Relationships, not RelationshipTarget
  • doesn't support sparse sets
  • performance depends a lot on the correct ordering of the query terms in the query plan, and there is no compilation/optimization step

Bigger limitations

  • uses the FilteredAccess to check if the query matches the archetype, so doesn't support non-archetypal filters and some QueryData. To fix this we could either add these concepts in the dynamic query plan ?

Todo

  • only tested with a single case, I am sure that the current logic is incorrect.
    -> I don't update the VariableState correctly on backtracking!!
    -> I don't update the written correctly on backtracking!!

  • one long-term goal could be to switch ALL existing queries to use this approach, in which case the QueryState and QueryIterationCursor would be modified? All existing queries would become a one-source QueryPlan. The difficult part would be check how we can encode all existing QueryData/QueryFilter implementations in the QueryPlan.

@cBournhonesque cBournhonesque added the A-ECS Entities, components, systems, and events label Oct 23, 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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant