-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
[Merged by Bors] - Add a method iter_combinations
on query to iterate over combinations of query results
#1763
Conversation
I'm guessing this would need Rust 1.51 - just a note to keep track of the minimum supported rust version Also, this seems like a nice generalisation of #1263 |
crates/bevy_ecs/src/query/iter.rs
Outdated
'outer: for i in (0..K).rev() { | ||
match self.cursors[i].next(&self.tables, &self.archetypes, &self.query_state) { | ||
Some(_) => { | ||
// walk forward up to last element, propagating cursor state forward | ||
for j in (i + 1)..K { | ||
self.cursors[j] = self.cursors[j - 1].clone(); | ||
match self.cursors[j].next( | ||
&self.tables, | ||
&self.archetypes, | ||
&self.query_state, | ||
) { | ||
Some(_) => {} | ||
None if i > 0 => continue 'outer, | ||
None => return None, | ||
} | ||
} | ||
break; | ||
} | ||
None if i > 0 => continue, | ||
None => return None, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a fan of this complex double loop, and I believe this can be written simpler. Just wasn't able to simplify it more for now. If you have any idea how to do that, I'm all ears.
I'd love to have a short user facing example for this. It's very useful non-obvious functionality that will be hard to discover otherwise. |
58b332a
to
fd71bef
Compare
I don't believe it's a generalization, but those could work together. The linked PR is about getting a singular entity from the query. The permutation iterator doesn't allow you to do so. But it does allow getting a (mutable) reference to multiple entities from a single query at once. You can do If we would generalize #1263 to support this, we would have something like this: fn exactly_k::<const K: usize>(&self) -> Result<[<Q::Fetch as Fetch<'_>>::Item; K], QueryExactlyError> Which would basically do Is this worth adding to this PR? The implementation of that wouldn't be terribly complicated, given that |
I don't mind the idea of |
40785c3
to
08f100c
Compare
I think the name |
The n_bodies example is cool! It definitely doesn't belong in the 3D folder though: that's for 3D rendering / assets mostly. In its current form, it would be a good fit for the ecs folder, renamed to Wherever it ends up, make sure to update the examples/README.md to make sure it's discoverable. |
Agree, will change it that way.
I don't exactly want to write the full example game just yet :) Maybe after this PR lands the example could be extended. But I agree that in current form it fits into ECS. That was also my initial idea, but I changed my mind after seeing no other examples with 3d rendering in ecs folder. I will move it now. |
k_iter
on query to iterate over permutations of query resultsiter_permutations
on query to iterate over permutations of query results
Hmm, should we use |
Very much agree with iter_combinations, since order doesn't matter. That's the correct behavior for 99% of the use cases. Using the right name is much less confusing and lets us use |
Is there anything that could be done about that Android build failing? It is clearly caused by too old rust version. All other builders are already updated and passing. |
For consistency with iter_tools, do we want this to be a method on |
Agree again, |
I'm not sure how itertools come to the picture here, but |
iter_permutations
on query to iterate over permutations of query resultsiter_combinations
on query to iterate over combinations of query results
iter_combinations
on query to iterate over combinations of query resultsiter_combinations
on query to iterate over combinations of query results
It's just the common ecosystem crate for this sort of thing.
Okay, good to know. Should be fine to leave as is then :) |
Fixed the doc comments. Should be ready to go. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alrighty this looks good to me! It definitely fills a painful functionality gap. I did my two regular benchmarks for ECS changes:
- A clean build of Bevy looking for significant compile time regressions. Nothing measurable to report here, which is in line with my expectations for a change like this.
- A run of my fork of ecs_bench_suite. QueryIter is notoriously subject to the whims of the compiler and sometimes even minor structural changes can cause regressions. Unfortunately this regresses a few benchmarks enough to merit some investigation:
This is surprising given the relatively superficial QueryIterationCursor refactor. Structurally and functionally it seems pretty much identical. I think its worth experimenting with adding/removing inlines here and there to see if we can regain whatever optimization magic the compiler is currently giving us.
} | ||
|
||
#[inline] | ||
pub fn iter_combinations_mut<'w, 's, const K: usize>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this could use docs explaining the quirks of how to use it and the current rust limitations that force it to work that way (ex: how to write the while loop that consumes the iterator, why we can't safely implement Iterator for mutable combinations, and why for_each (which should be the favored implementation) can't work due to rustc errors).
I expect this to be a source of confusion, so we should try to nip it in the bud.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is thoroughly documented on Query::iter_combinations_mut
. I wonder if I should repeat those docs here as well. I specifically documented in on Query
, as this is how other docs are done as well. All other QueryState
methods are left undocumented except for safety, so I was just following an established pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm yeah thats reasonable. QueryState is the api people doing direct World queries will use, so it should be documented, but its also unfair to hold you to a standard that I didn't hold myself to :)
I created a new issue to track this work #2090
The "insert" benchmarks do vary wildly for me as well. I think this is because its largely a benchmark of the os memory allocator, which I would expect to swing around based on the current memory usage / cached allocations. The iterator benchmarks (edit: don't) swing around for me in the same way. Usually they stay "within noise threshold" for me. |
Is there a way to convince the benchmarking library to intersperse the two versions? That would help control for the random noise, especially when combined with increasing the sample size. |
@alice-i-cecile : not that I know of. Criterion is a "compiled in" orchestrator, so it would need to be a post-processing step that somehow consolidates two sets of results. Can someone that isn't me try to validate the regression here? Ex: run the benchmark suite once on |
I can try to find time to play around with optimizations, but that won't be for at least a couple of weeks while we sort out rendering. |
I think "worst case scenario" we can just use the old impl for normal iterators and just eat the code-duplication complexity from the added "cursor" impl. |
I ended up focusing on a few benchmarks that I was able to reliably test against
The foreach variants don't actually excersize changed code, so I was kinda using them as a control group. I've only looked at benchmarks where those had no performance change detected. And I indeed have seen the regression at around 70% worse performance in Unfortunately, no amount of adding or removing |
I think its worth considering using a macro to cut down on code duplication here, but I don't see that as a prerequisite for merging. I added comments to remind devs to update iterators in multiple places (and a reference to this issue explaining why the code isn't unified). |
bors r+ |
I see the added comments about duplicated code, and while this is the only exact copy, those aren't really the only similar ones. There is also |
bors r- |
…s of query results (#1763) Related to [discussion on discord](https://discord.com/channels/691052431525675048/742569353878437978/824731187724681289) With const generics, it is now possible to write generic iterator over multiple entities at once. This enables patterns of query iterations like ```rust for [e1, e2, e3] in query.iter_combinations() { // do something with relation of all three entities } ``` The compiler is able to infer the correct iterator for given size of array, so either of those work ```rust for [e1, e2] in query.iter_combinations() { ... } for [e1, e2, e3] in query.iter_combinations() { ... } ``` This feature can be very useful for systems like collision detection. When you ask for permutations of size K of N entities: - if K == N, you get one result of all entities - if K < N, you get all possible subsets of N with size K, without repetition - if K > N, the result set is empty (no permutation of size K exist) Co-authored-by: Carter Anderson <mcanders1@gmail.com>
Canceled. |
Updated! |
bors r+ |
…s of query results (#1763) Related to [discussion on discord](https://discord.com/channels/691052431525675048/742569353878437978/824731187724681289) With const generics, it is now possible to write generic iterator over multiple entities at once. This enables patterns of query iterations like ```rust for [e1, e2, e3] in query.iter_combinations() { // do something with relation of all three entities } ``` The compiler is able to infer the correct iterator for given size of array, so either of those work ```rust for [e1, e2] in query.iter_combinations() { ... } for [e1, e2, e3] in query.iter_combinations() { ... } ``` This feature can be very useful for systems like collision detection. When you ask for permutations of size K of N entities: - if K == N, you get one result of all entities - if K < N, you get all possible subsets of N with size K, without repetition - if K > N, the result set is empty (no permutation of size K exist) Co-authored-by: Carter Anderson <mcanders1@gmail.com>
Pull request successfully merged into main. Build succeeded: |
iter_combinations
on query to iterate over combinations of query resultsiter_combinations
on query to iterate over combinations of query results
…s of query results (bevyengine#1763) Related to [discussion on discord](https://discord.com/channels/691052431525675048/742569353878437978/824731187724681289) With const generics, it is now possible to write generic iterator over multiple entities at once. This enables patterns of query iterations like ```rust for [e1, e2, e3] in query.iter_combinations() { // do something with relation of all three entities } ``` The compiler is able to infer the correct iterator for given size of array, so either of those work ```rust for [e1, e2] in query.iter_combinations() { ... } for [e1, e2, e3] in query.iter_combinations() { ... } ``` This feature can be very useful for systems like collision detection. When you ask for permutations of size K of N entities: - if K == N, you get one result of all entities - if K < N, you get all possible subsets of N with size K, without repetition - if K > N, the result set is empty (no permutation of size K exist) Co-authored-by: Carter Anderson <mcanders1@gmail.com>
Related to discussion on discord
With const generics, it is now possible to write generic iterator over multiple entities at once.
This enables patterns of query iterations like
The compiler is able to infer the correct iterator for given size of array, so either of those work
This feature can be very useful for systems like collision detection.
When you ask for permutations of size K of N entities: