Skip to content

Add system parameter for relation quieries #19526

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from

Conversation

KurlykovDanila
Copy link

@KurlykovDanila KurlykovDanila commented Jun 7, 2025

Objective

Improved interaction with entities in relationships. Added the ability to make combined queries.
This solution is alternative for #17647 (the goal is one, but the methods of achieving it are different)

Naive solution

Created a system parameter that combines 2 queries. One query (QueryData) is addressed to the relationship target, the other query (QueryFilter) is for related entities. After that, these queries are combined. (Each function is 2-3 lines of code, so it's easier to look at the code, it's really just 2 combined queries and nothing more.)
struct Related<D: QueryData, R: RelatedTarget, F: QueryFilter>

API example (the system will select all entities that are parents with health and have children (1st order descendants) with a shield and without a helmet):

fn system(r: Related<(Entity, &Health), Children, (With<Shield>, Without<Helmet>)>) { }

Testing

  • Basic comparison test of: 2 separate queries with brute force and Related query
  • Check correct work of Related::get
  • Check correct work of Related::get_mut
  • Check correct work of Related::iter
  • Check correct work of Related::iter_mut

Comparison with alternative (WorldQuery)

General position.

It is important to understand what types of queries we want to support for relationships. The basic categories of queries that I see (I will give examples on Parent and Children, because otherwise I get confused) are queries that take into account the hierarchy (they look at any level of the hierarchy), and "linear" queries with the first or last member of the hierarchy.

Obviously, the easiest to implement are "linear" queries (these are always 2 independent combined queries). It is also important that they are predictable in performance.

Difficulties arise with queries with a hierarchy when we can get to any level of nesting (traversal of the entire relationship graph). And the main difficulty is caused by matching the identifiers of the child and parent entities (the speed depends on the depth of the hierarchy). And if you decide to compare all sublevels, then checking their correct relative position will take an irrational amount of time.

As the experiment with WorldQuery showed, the proposed API easily expresses any nesting, but these queries are initially designed to work with only one query. I believe that this behavior cannot be violated and here's why.

  • Understanding performance for users - now query is a linear query, any query that you create (even for all entities) will be executed almost instantly. It is important for the user to know that the query he wrote is not a performance bottleneck. While hierarchical queries themselves imply nested queries to different archetypes. Moreover, if you implement these queries using WorldQuery, the nesting can be absolutely any.

Another problem follows from this:

  • Violation of data locality - if nested queries are executed sequentially, for example, we will change the archetype for them sequentially, then we throw away the advantages of data locality. The essence of queries is precisely in the sequential access to one component sequentially, and not jumping from one archetype to another, it is important to stay on one storage as long as possible. That is, there is no point in executing each query by iteration, it is necessary to execute them one after another, or in different threads.

Queries over entities with relations are a separate "type" of queries, so I think that it is necessary to take it out into a separate type of system parameter. Perhaps a new API should be added for such types of queries (like combined queries).

Copy link
Contributor

github-actions bot commented Jun 7, 2025

Welcome, new contributor!

Please make sure you've read our contributing guide and we look forward to reviewing your pull request shortly ✨

@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events M-Needs-Release-Note Work that should be called out in the blog due to impact 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 X-Contentious There are nontrivial implications that should be thought through labels Jun 7, 2025
Copy link
Contributor

github-actions bot commented Jun 7, 2025

It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note.

Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes.

world::{unsafe_world_cell::UnsafeWorldCell, World},
};

// SystemParam for combine 2 related queries
Copy link
Member

Choose a reason for hiding this comment

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

This is going to need much stronger doc strings.


// SystemParam for combine 2 related queries
pub struct Related<'w, 's, D: QueryData, R: RelationshipTarget, F: QueryFilter> {
data_query: Query<'w, 's, D, With<R>>,
Copy link
Member

Choose a reason for hiding this comment

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

It feels odd that we can't specify a filter for this query. Maybe F1 and F2?

world: UnsafeWorldCell<'world>,
_: Tick,
) -> Self::Item<'world, 'state> {
let data_query = unsafe { state.0.query_unchecked_manual(world) };
Copy link
Member

Choose a reason for hiding this comment

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

SAFETY comments.

>,
> {
self.data_query
.iter_many(self.filter_query.clone().map(|r| r.get()))
Copy link
Member

Choose a reason for hiding this comment

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

These clone calls are suspicious; I'm not sure I understand the overall strategy here very well.

@alice-i-cecile
Copy link
Member

Interesting; I think this is worth exploring! Please let us know if you need help with anything; this is a relatively complex and high impact feature despite the simple code, so I'm going to be fairly aggressive on documentation requirements before merging.

@alice-i-cecile
Copy link
Member

I poked at #17649 again, and I don't think that the approach I used there is viable. It's swimming upstream too much, and like you say, it will have surprising performance implications.

@KurlykovDanila
Copy link
Author

@alice-i-cecile To be honest, I'm not sure that this implementation is the best solution, because now for each separate type of relationship query you will have to create your own system parameter.

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 D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes M-Needs-Release-Note Work that should be called out in the blog due to impact S-Needs-Review Needs reviewer attention (from anyone!) to move forward X-Contentious There are nontrivial implications that should be thought through
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants