Skip to content

Commit 1321cba

Browse files
nicopapItsDoot
authored andcommitted
Fix size_hint for partially consumed QueryIter and QueryCombinationIter (bevyengine#5214)
# Objective Fix bevyengine#5149 ## Solution Instead of returning the **total count** of elements in the `QueryIter` in `size_hint`, we return the **count of remaining elements**. This Fixes bevyengine#5149 even when bevyengine#5148 gets merged. - bevyengine#5149 - bevyengine#5148 --- ## Changelog - Fix partially consumed `QueryIter` and `QueryCombinationIter` having invalid `size_hint` Co-authored-by: Nicola Papale <nicopap@users.noreply.github.com>
1 parent 407d428 commit 1321cba

File tree

2 files changed

+72
-142
lines changed

2 files changed

+72
-142
lines changed

crates/bevy_ecs/src/query/iter.rs

+37-34
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Iterator for QueryIter<'w, 's
5656
}
5757

5858
fn size_hint(&self) -> (usize, Option<usize>) {
59-
let max_size = self
60-
.query_state
61-
.matched_archetype_ids
62-
.iter()
63-
.map(|id| self.archetypes[*id].len())
64-
.sum();
65-
59+
let max_size = self.cursor.max_remaining(self.tables, self.archetypes);
6660
let archetype_query = Q::IS_ARCHETYPAL && F::IS_ARCHETYPAL;
6761
let min_size = if archetype_query { max_size } else { 0 };
6862
(min_size, Some(max_size))
@@ -351,11 +345,16 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery, const K: usize>
351345
return None;
352346
}
353347

354-
// first, iterate from last to first until next item is found
348+
// PERF: can speed up the following code using `cursor.remaining()` instead of `next_item.is_none()`
349+
// when Q::IS_ARCHETYPAL && F::IS_ARCHETYPAL
350+
//
351+
// let `i` be the index of `c`, the last cursor in `self.cursors` that
352+
// returns `K-i` or more elements.
353+
// Make cursor in index `j` for all `j` in `[i, K)` a copy of `c` advanced `j-i+1` times.
354+
// If no such `c` exists, return `None`
355355
'outer: for i in (0..K).rev() {
356356
match self.cursors[i].next(self.tables, self.archetypes, self.query_state) {
357357
Some(_) => {
358-
// walk forward up to last element, propagating cursor state forward
359358
for j in (i + 1)..K {
360359
self.cursors[j] = self.cursors[j - 1].clone_cursor();
361360
match self.cursors[j].next(self.tables, self.archetypes, self.query_state) {
@@ -409,36 +408,29 @@ impl<'w, 's, Q: ReadOnlyWorldQuery, F: ReadOnlyWorldQuery, const K: usize> Itera
409408
}
410409

411410
fn size_hint(&self) -> (usize, Option<usize>) {
412-
if K == 0 {
413-
return (0, Some(0));
414-
}
415-
416-
let max_size: usize = self
417-
.query_state
418-
.matched_archetype_ids
419-
.iter()
420-
.map(|id| self.archetypes[*id].len())
421-
.sum();
422-
423-
if max_size < K {
424-
return (0, Some(0));
425-
}
426-
if max_size == K {
427-
return (1, Some(1));
428-
}
429-
430411
// binomial coefficient: (n ; k) = n! / k!(n-k)! = (n*n-1*...*n-k+1) / k!
431412
// See https://en.wikipedia.org/wiki/Binomial_coefficient
432413
// See https://blog.plover.com/math/choose.html for implementation
433414
// It was chosen to reduce overflow potential.
434415
fn choose(n: usize, k: usize) -> Option<usize> {
416+
if k > n || n == 0 {
417+
return Some(0);
418+
}
419+
let k = k.min(n - k);
435420
let ks = 1..=k;
436421
let ns = (n - k + 1..=n).rev();
437422
ks.zip(ns)
438423
.try_fold(1_usize, |acc, (k, n)| Some(acc.checked_mul(n)? / k))
439424
}
440-
let smallest = K.min(max_size - K);
441-
let max_combinations = choose(max_size, smallest);
425+
// sum_i=0..k choose(cursors[i].remaining, k-i)
426+
let max_combinations = self
427+
.cursors
428+
.iter()
429+
.enumerate()
430+
.try_fold(0, |acc, (i, cursor)| {
431+
let n = cursor.max_remaining(self.tables, self.archetypes);
432+
Some(acc + choose(n, K - i)?)
433+
});
442434

443435
let archetype_query = F::IS_ARCHETYPAL && Q::IS_ARCHETYPAL;
444436
let known_max = max_combinations.unwrap_or(usize::MAX);
@@ -452,11 +444,7 @@ where
452444
F: ArchetypeFilter,
453445
{
454446
fn len(&self) -> usize {
455-
self.query_state
456-
.matched_archetype_ids
457-
.iter()
458-
.map(|id| self.archetypes[*id].len())
459-
.sum()
447+
self.size_hint().0
460448
}
461449
}
462450

@@ -571,6 +559,21 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
571559
}
572560
}
573561

562+
/// How many values will this cursor return at most?
563+
///
564+
/// Note that if `Q::IS_ARCHETYPAL && F::IS_ARCHETYPAL`, the return value
565+
/// will be **the exact count of remaining values**.
566+
fn max_remaining(&self, tables: &'w Tables, archetypes: &'w Archetypes) -> usize {
567+
let remaining_matched: usize = if Self::IS_DENSE {
568+
let ids = self.table_id_iter.clone();
569+
ids.map(|id| tables[*id].entity_count()).sum()
570+
} else {
571+
let ids = self.archetype_id_iter.clone();
572+
ids.map(|id| archetypes[*id].len()).sum()
573+
};
574+
remaining_matched + self.current_len - self.current_index
575+
}
576+
574577
// NOTE: If you are changing query iteration code, remember to update the following places, where relevant:
575578
// QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual
576579
/// # Safety

crates/bevy_ecs/src/query/mod.rs

+35-108
Original file line numberDiff line numberDiff line change
@@ -96,100 +96,6 @@ mod tests {
9696

9797
#[test]
9898
fn query_filtered_exactsizeiterator_len() {
99-
fn assert_all_sizes_iterator_equal(
100-
iterator: impl ExactSizeIterator,
101-
expected_size: usize,
102-
query_type: &'static str,
103-
) {
104-
let len = iterator.len();
105-
let size_hint_0 = iterator.size_hint().0;
106-
let size_hint_1 = iterator.size_hint().1;
107-
// `count` tests that not only it is the expected value, but also
108-
// the value is accurate to what the query returns.
109-
let count = iterator.count();
110-
// This will show up when one of the asserts in this function fails
111-
println!(
112-
r#"query declared sizes:
113-
for query: {query_type}
114-
expected: {expected_size}
115-
len: {len}
116-
size_hint().0: {size_hint_0}
117-
size_hint().1: {size_hint_1:?}
118-
count(): {count}"#
119-
);
120-
assert_eq!(len, expected_size);
121-
assert_eq!(size_hint_0, expected_size);
122-
assert_eq!(size_hint_1, Some(expected_size));
123-
assert_eq!(count, expected_size);
124-
}
125-
fn assert_all_sizes_equal<Q, F>(world: &mut World, expected_size: usize)
126-
where
127-
Q: ReadOnlyWorldQuery,
128-
F: ReadOnlyWorldQuery,
129-
F::ReadOnly: ArchetypeFilter,
130-
{
131-
let mut query = world.query_filtered::<Q, F>();
132-
let iter = query.iter(world);
133-
let query_type = type_name::<QueryState<Q, F>>();
134-
assert_all_sizes_iterator_equal(iter, expected_size, query_type);
135-
}
136-
137-
let mut world = World::new();
138-
world.spawn((A(1), B(1)));
139-
world.spawn(A(2));
140-
world.spawn(A(3));
141-
142-
assert_all_sizes_equal::<&A, With<B>>(&mut world, 1);
143-
assert_all_sizes_equal::<&A, Without<B>>(&mut world, 2);
144-
145-
let mut world = World::new();
146-
world.spawn((A(1), B(1), C(1)));
147-
world.spawn((A(2), B(2)));
148-
world.spawn((A(3), B(3)));
149-
world.spawn((A(4), C(4)));
150-
world.spawn((A(5), C(5)));
151-
world.spawn((A(6), C(6)));
152-
world.spawn(A(7));
153-
world.spawn(A(8));
154-
world.spawn(A(9));
155-
world.spawn(A(10));
156-
157-
// With/Without for B and C
158-
assert_all_sizes_equal::<&A, With<B>>(&mut world, 3);
159-
assert_all_sizes_equal::<&A, With<C>>(&mut world, 4);
160-
assert_all_sizes_equal::<&A, Without<B>>(&mut world, 7);
161-
assert_all_sizes_equal::<&A, Without<C>>(&mut world, 6);
162-
163-
// With/Without (And) combinations
164-
assert_all_sizes_equal::<&A, (With<B>, With<C>)>(&mut world, 1);
165-
assert_all_sizes_equal::<&A, (With<B>, Without<C>)>(&mut world, 2);
166-
assert_all_sizes_equal::<&A, (Without<B>, With<C>)>(&mut world, 3);
167-
assert_all_sizes_equal::<&A, (Without<B>, Without<C>)>(&mut world, 4);
168-
169-
// With/Without Or<()> combinations
170-
assert_all_sizes_equal::<&A, Or<(With<B>, With<C>)>>(&mut world, 6);
171-
assert_all_sizes_equal::<&A, Or<(With<B>, Without<C>)>>(&mut world, 7);
172-
assert_all_sizes_equal::<&A, Or<(Without<B>, With<C>)>>(&mut world, 8);
173-
assert_all_sizes_equal::<&A, Or<(Without<B>, Without<C>)>>(&mut world, 9);
174-
assert_all_sizes_equal::<&A, (Or<(With<B>,)>, Or<(With<C>,)>)>(&mut world, 1);
175-
assert_all_sizes_equal::<&A, Or<(Or<(With<B>, With<C>)>, With<D>)>>(&mut world, 6);
176-
177-
for i in 11..14 {
178-
world.spawn((A(i), D(i)));
179-
}
180-
181-
assert_all_sizes_equal::<&A, Or<(Or<(With<B>, With<C>)>, With<D>)>>(&mut world, 9);
182-
assert_all_sizes_equal::<&A, Or<(Or<(With<B>, With<C>)>, Without<D>)>>(&mut world, 10);
183-
184-
// a fair amount of entities
185-
for i in 14..20 {
186-
world.spawn((C(i), D(i)));
187-
}
188-
assert_all_sizes_equal::<Entity, (With<C>, With<D>)>(&mut world, 6);
189-
}
190-
191-
#[test]
192-
fn query_filtered_combination_size() {
19399
fn choose(n: usize, k: usize) -> usize {
194100
if n == 0 || k == 0 || n < k {
195101
return 0;
@@ -200,52 +106,73 @@ mod tests {
200106
}
201107
fn assert_combination<Q, F, const K: usize>(world: &mut World, expected_size: usize)
202108
where
203-
Q: WorldQuery,
109+
Q: ReadOnlyWorldQuery,
204110
F: ReadOnlyWorldQuery,
205111
F::ReadOnly: ArchetypeFilter,
206112
{
207113
let mut query = world.query_filtered::<Q, F>();
208-
let iter = query.iter_combinations::<K>(world);
209114
let query_type = type_name::<QueryCombinationIter<Q, F, K>>();
210-
assert_all_sizes_iterator_equal(iter, expected_size, query_type);
115+
let iter = query.iter_combinations::<K>(world);
116+
assert_all_sizes_iterator_equal(iter, expected_size, 0, query_type);
117+
let iter = query.iter_combinations::<K>(world);
118+
assert_all_sizes_iterator_equal(iter, expected_size, 1, query_type);
119+
let iter = query.iter_combinations::<K>(world);
120+
assert_all_sizes_iterator_equal(iter, expected_size, 5, query_type);
211121
}
212122
fn assert_all_sizes_equal<Q, F>(world: &mut World, expected_size: usize)
213123
where
214-
Q: WorldQuery,
124+
Q: ReadOnlyWorldQuery,
215125
F: ReadOnlyWorldQuery,
216126
F::ReadOnly: ArchetypeFilter,
217127
{
218128
let mut query = world.query_filtered::<Q, F>();
219-
let iter = query.iter(world);
220129
let query_type = type_name::<QueryState<Q, F>>();
221-
assert_all_sizes_iterator_equal(iter, expected_size, query_type);
130+
assert_all_exact_sizes_iterator_equal(query.iter(world), expected_size, 0, query_type);
131+
assert_all_exact_sizes_iterator_equal(query.iter(world), expected_size, 1, query_type);
132+
assert_all_exact_sizes_iterator_equal(query.iter(world), expected_size, 5, query_type);
222133

223134
let expected = expected_size;
224135
assert_combination::<Q, F, 0>(world, choose(expected, 0));
225136
assert_combination::<Q, F, 1>(world, choose(expected, 1));
226137
assert_combination::<Q, F, 2>(world, choose(expected, 2));
227138
assert_combination::<Q, F, 5>(world, choose(expected, 5));
228139
assert_combination::<Q, F, 43>(world, choose(expected, 43));
229-
assert_combination::<Q, F, 128>(world, choose(expected, 128));
140+
assert_combination::<Q, F, 64>(world, choose(expected, 64));
141+
}
142+
fn assert_all_exact_sizes_iterator_equal(
143+
iterator: impl ExactSizeIterator,
144+
expected_size: usize,
145+
skip: usize,
146+
query_type: &'static str,
147+
) {
148+
let len = iterator.len();
149+
println!("len: {len}");
150+
assert_all_sizes_iterator_equal(iterator, expected_size, skip, query_type);
151+
assert_eq!(len, expected_size);
230152
}
231153
fn assert_all_sizes_iterator_equal(
232-
iterator: impl Iterator,
154+
mut iterator: impl Iterator,
233155
expected_size: usize,
156+
skip: usize,
234157
query_type: &'static str,
235158
) {
159+
let expected_size = expected_size.saturating_sub(skip);
160+
for _ in 0..skip {
161+
iterator.next();
162+
}
236163
let size_hint_0 = iterator.size_hint().0;
237164
let size_hint_1 = iterator.size_hint().1;
238165
// `count` tests that not only it is the expected value, but also
239166
// the value is accurate to what the query returns.
240167
let count = iterator.count();
241168
// This will show up when one of the asserts in this function fails
242169
println!(
243-
r#"query declared sizes:
244-
for query: {query_type}
245-
expected: {expected_size}
246-
size_hint().0: {size_hint_0}
247-
size_hint().1: {size_hint_1:?}
248-
count(): {count}"#
170+
"query declared sizes: \n\
171+
for query: {query_type} \n\
172+
expected: {expected_size} \n\
173+
size_hint().0: {size_hint_0} \n\
174+
size_hint().1: {size_hint_1:?} \n\
175+
count(): {count}"
249176
);
250177
assert_eq!(size_hint_0, expected_size);
251178
assert_eq!(size_hint_1, Some(expected_size));

0 commit comments

Comments
 (0)