Skip to content

Conversation

@cBournhonesque
Copy link
Owner

@cBournhonesque cBournhonesque commented Oct 21, 2025

I'm wondering how we would evolve the bevy Query type to support multi-term queries.

@quartermeister 's PR would already bring us a long way, since it would allow for things like
Query<(Entity, DockedTo<()>), With> (flecs: SpaceShip, (DockedTo, *))
Query<(Entity, DockedTo<(), With>), With> (flecs: SpaceShip($this), DockedTo($this, $Location), Planet($Location))

But not things like this are probably not possible
SpaceShip($spaceship),
Faction($spaceship, $spaceship_faction),
DockedTo($spaceship, $planet),
Planet($planet),
RuledBy($planet, $planet_faction),
AlliedWith($spaceship_faction, $planet_faction)

The thing is that bevy's syntax would be complicated by the fact that we need to specify at the same time what data we query (to set the accesses correctly for multithreading systems, etc.) and which entities we query

I guess these kind of complicated multi-term queries would only be dynamic, in which case we could do:
builder
.with(0)
.related_to(0, 1)
.with(1) // HERE: we add the extra components we want to access on any term
.related_to(0, 2)
.with(2)
.related_to(2, 3)
.related_to(1, 3)

And then we would do
builder
.build::() // gives us a QueryState<Dynamic, ()>

Where Dynamic is roughly a wrapper around a tuple of FilteredEntityRef/Mut. Maybe we could then call things like dynamic.query<(&C1, &C2)>(1) which uses transmuting to return a QueryState for the term 1 of the query ($spaceship_faction). The builder needs to store the access for each term to be able to check if the transmutes are valid.

guess in this scenario my question is how things would related to @quartermeister 's bevyengine#21557.
IterQueryData and SingleEntityQueryData are definitely necessary pieces, no matter how multi-term queries are implemented
we still need init_nested_access, which would be used by Dynamic to provide the access of non-source terms.
would something like Query<(Entity, DockedTo<()>), With> use a nested QueryState, or would we want to convert that QueryState to the dynamic query plan, resolve it there, and then transmute it back to the original type?
would we be able to transmute something like
builder
.with(0)
.related_to(0, 1)
.with(1)

back to a Query<Faction<&C>> or is that too hard to do? (i.e. if the relationships in the query builder is a tree, it should be able to be transmuted back to a nested QueryStates form)
If it's not possible, then the Query<Faction<&C>> would be a nice UX gain for simple relationship queries. More complicated use-cases could fallback to using the dynamic query builder

So i guess if we were to attempt a prototype:
A) extend QueryBuilder to accept multiple query terms linked by relationships, similar to what james did: bevyengine@b56e1c7#diff-b40f95bb6879b8c95b1be48e381a6cb274db01b5919024eb359a4a2feaefb160R6
we can just do a Vec for now
B) hard: update the matching logic to not use only the access, but use the actual plan
i'm more blurry on this. It seems flecs has some crazy state machine inspired by prolog but I'd like to start with the simplest thing possible.
even without thinking about the logic, this requires a bunch of changes to our matching logic. The matching logic currently just checks if the current access matches. Instead we would be providing a more complex plan. If the plan has no multi-term it can just be the ComponentAccess (for API compatibility with existing QueryData)
C) introduce Dynamic, a WorldQuery that would let you access data dynamically for any of the terms of the query plan
D) support transmutes from parts of dynamic so that we could do dynamic.query::<(&C1, &C2, FilteredEntityRef)>(1) or maybe even dynamic.transmute::<(&C1, ChildOf<&C2, With>)>. @quartermeister (again!) paved the way with the amazing: bevyengine#18236

I'm still wondering how to adapt this for Bevy.

Option 1:

  • keep QueryIterationCursor roughly as is.
  • the Fetch is something like your IterState, some state that tracks which entities we are tracking
  • the Item returned is something like a DynamicIter, where the user could access components for any of the sources, fix the sources to a specific entity (update written)
  • D::set_table sets the table for the main source, then for each entity of the main source I struggle to see what Item. D::Fetch would give you. I guess the DynamicIter with the main source already set to the matched entity?

Charles Bournhonesque and others added 2 commits October 20, 2025 20:59
Change-Id: Ia9d487d0a7d0bc873b90a93385ec8b6e3b79db9f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants