1use alloc::rc::Rc;
7use alloc::vec::Vec;
8
9use crate::entity::EntityId;
10use crate::world::query::cached_source::QueryCachedSource;
11
12pub struct Query1<'w, 'c, T: 'static> {
14 pub(crate) world: &'w crate::world::World,
15 pub(crate) plan: Rc<crate::world::query::plan::ResolvedPlan>,
16 pub(crate) params_fingerprint: u64,
17 pub(crate) captured_now: crate::time::ChangeTick,
18 pub(crate) since: crate::time::ChangeTick,
19 pub(crate) cursor_committed: bool,
20 pub(crate) cursor: Option<&'c mut crate::query::QueryCursor>,
21 pub(crate) additional_covered_required: Option<usize>,
22 pub(crate) state: Query1State<'w, T>,
23}
24
25pub(crate) enum Query1State<'w, T: 'static> {
27 Sparse {
29 store: &'w crate::storage::TypedSparseStorage<T>,
30 index: usize,
31 },
32 Table {
34 archetypes: &'w [usize],
35 archetype_index: usize,
36 row: usize,
37 },
38 Exact { ids: Vec<EntityId>, index: usize },
40 Cached {
42 source: QueryCachedSource,
43 index: usize,
44 },
45 Borrowed {
47 ids: &'w [EntityId],
48 index: usize,
49 apply_temporal: bool,
50 },
51 Done,
53}
54
55pub struct Query2<'w, 'c, A: 'static, B: 'static> {
57 pub(crate) world: &'w crate::world::World,
58 pub(crate) plan: Rc<crate::world::query::plan::ResolvedPlan>,
59 pub(crate) params_fingerprint: u64,
60 pub(crate) captured_now: crate::time::ChangeTick,
61 pub(crate) since: crate::time::ChangeTick,
62 pub(crate) cursor_committed: bool,
63 pub(crate) cursor: Option<&'c mut crate::query::QueryCursor>,
64 pub(crate) state: Query2State<'w>,
65 pub(crate) second_index: usize,
66 pub(crate) second_is_table: bool,
67 pub(crate) marker: core::marker::PhantomData<fn() -> (A, B)>,
68}
69
70pub(crate) enum Query2State<'w> {
72 Sparse { slots: &'w [u32], index: usize },
74 Table {
76 archetypes: &'w [usize],
77 archetype_index: usize,
78 row: usize,
79 },
80 Exact { ids: Vec<EntityId>, index: usize },
82 Cached {
84 source: QueryCachedSource,
85 index: usize,
86 },
87 Borrowed {
89 ids: &'w [EntityId],
90 index: usize,
91 apply_temporal: bool,
92 },
93 Done,
95}
96
97impl<'w, 'c, T: 'static> Query1<'w, 'c, T> {
98 #[allow(clippy::too_many_arguments, dead_code)]
99 pub(crate) fn new(
100 world: &'w crate::world::World,
101 plan: Rc<crate::world::query::plan::ResolvedPlan>,
102 since: crate::time::ChangeTick,
103 captured_now: crate::time::ChangeTick,
104 cursor: Option<&'c mut crate::query::QueryCursor>,
105 cached: Option<QueryCachedSource>,
106 table_archetypes: Option<&'w [usize]>,
107 additional_covered_required: Option<usize>,
108 ) -> Result<Self, crate::query::QueryError> {
109 let state = world.query1_state::<T>(&plan, cached, table_archetypes)?;
110 Ok(Self {
111 world,
112 params_fingerprint: plan.fingerprint,
113 plan,
114 captured_now,
115 since,
116 cursor_committed: false,
117 cursor,
118 additional_covered_required,
119 state,
120 })
121 }
122
123 #[allow(clippy::too_many_arguments)]
124 pub(crate) fn new_prepared(
125 world: &'w crate::world::World,
126 plan: Rc<crate::world::query::plan::ResolvedPlan>,
127 since: crate::time::ChangeTick,
128 captured_now: crate::time::ChangeTick,
129 cursor: Option<&'c mut crate::query::QueryCursor>,
130 materialized: Option<(&'w [EntityId], bool)>,
131 table_archetypes: Option<&'w [usize]>,
132 ) -> Result<Self, crate::query::QueryError> {
133 let state = if let Some((ids, apply_temporal)) = materialized {
134 Query1State::Borrowed {
135 ids,
136 index: 0,
137 apply_temporal,
138 }
139 } else {
140 world.query1_state::<T>(&plan, None, table_archetypes)?
141 };
142 Ok(Self {
143 world,
144 params_fingerprint: plan.fingerprint,
145 plan,
146 captured_now,
147 since,
148 cursor_committed: false,
149 cursor,
150 additional_covered_required: None,
151 state,
152 })
153 }
154
155 fn commit_cursor_if_needed(&mut self) {
156 if self.cursor_committed {
157 return;
158 }
159 let world = self.world;
160 let fingerprint = self.params_fingerprint;
161 let cursor = self
162 .cursor
163 .as_mut()
164 .filter(|cursor| cursor.validate(world, fingerprint).is_ok());
165 if let Some(cursor) = cursor {
166 cursor.commit(self.captured_now);
167 }
168 self.cursor_committed = true;
169 }
170}
171
172impl<'w, 'c, T: 'static> Iterator for Query1<'w, 'c, T> {
173 type Item = (EntityId, &'w T);
174
175 fn next(&mut self) -> Option<Self::Item> {
176 loop {
177 match &mut self.state {
178 Query1State::Done => {
179 self.commit_cursor_if_needed();
180 return None;
181 }
182 Query1State::Sparse { store, index } => {
183 let slots = store.dense_slots();
184 while *index < slots.len() {
185 let dense_index = *index;
186 let slot = slots[dense_index];
187 *index += 1;
188 let entity = self.world.entity_from_slot(slot);
189 if let Some(additional) = self.additional_covered_required {
190 if !self.world.query1_accept_source_covered(
191 entity,
192 &self.plan,
193 self.since,
194 self.captured_now,
195 additional,
196 ) {
197 continue;
198 }
199 let value = store
200 .dense_value(dense_index)
201 .expect("sparse dense slot and value vectors stay aligned");
202 return Some((entity, value));
203 }
204 if let Some(value) = self.world.query1_match_sparse::<T>(
205 entity,
206 &self.plan,
207 self.since,
208 self.captured_now,
209 store,
210 ) {
211 return Some((entity, value));
212 }
213 }
214 self.state = Query1State::Done;
215 }
216 Query1State::Table {
217 archetypes,
218 archetype_index,
219 row,
220 } => {
221 while *archetype_index < archetypes.len() {
222 let archetype = archetypes[*archetype_index];
223 let slots = self.world.archetype_entity_slots(archetype);
224 while *row < slots.len() {
225 let slot = slots[*row];
226 *row += 1;
227 let entity = self.world.entity_from_slot(slot);
228 if let Some(value) = self.world.query1_match_table::<T>(
229 entity,
230 &self.plan,
231 self.since,
232 self.captured_now,
233 self.additional_covered_required,
234 ) {
235 return Some((entity, value));
236 }
237 }
238 *archetype_index += 1;
239 *row = 0;
240 }
241 self.state = Query1State::Done;
242 }
243 Query1State::Exact { ids, index } => {
244 while *index < ids.len() {
245 let entity = ids[*index];
246 *index += 1;
247 if let Some(value) = self.world.query1_match_any_storage::<T>(
248 entity,
249 &self.plan,
250 self.since,
251 self.captured_now,
252 ) {
253 return Some((entity, value));
254 }
255 }
256 self.state = Query1State::Done;
257 }
258 Query1State::Cached { source, index } => {
259 let ids = match self
260 .world
261 .cached_query_entities(source, self.params_fingerprint)
262 {
263 Ok(ids) => ids,
264 Err(_) => {
265 self.state = Query1State::Done;
266 continue;
267 }
268 };
269 while *index < ids.len() {
270 let entity = ids[*index];
271 *index += 1;
272 let value = if !self.plan.added_indices.is_empty()
273 || !self.plan.changed_indices.is_empty()
274 {
275 self.world.query1_match_any_storage::<T>(
276 entity,
277 &self.plan,
278 self.since,
279 self.captured_now,
280 )
281 } else {
282 self.world.query1_match_cached::<T>(entity, &self.plan)
283 };
284 let value = match value {
285 Some(value) => value,
286 None => continue,
287 };
288 return Some((entity, value));
289 }
290 self.state = Query1State::Done;
291 }
292 Query1State::Borrowed {
293 ids,
294 index,
295 apply_temporal,
296 } => {
297 while *index < ids.len() {
298 let entity = ids[*index];
299 *index += 1;
300 if *apply_temporal
301 && !crate::world::query::filter::entity_matches_temporal(
302 self.world,
303 entity,
304 &self.plan,
305 self.since,
306 self.captured_now,
307 )
308 {
309 continue;
310 }
311 let value = self.world.query1_match_cached::<T>(entity, &self.plan);
312 let value = match value {
313 Some(value) => value,
314 None => continue,
315 };
316 return Some((entity, value));
317 }
318 self.state = Query1State::Done;
319 }
320 }
321 }
322 }
323}
324
325impl<'w, 'c, T: 'static> Drop for Query1<'w, 'c, T> {
326 fn drop(&mut self) {
327 if matches!(self.state, Query1State::Done) {
328 self.commit_cursor_if_needed();
329 }
330 }
331}
332
333impl<'w, 'c, A: 'static, B: 'static> Query2<'w, 'c, A, B> {
334 #[allow(clippy::too_many_arguments, dead_code)]
335 pub(crate) fn new(
336 world: &'w crate::world::World,
337 plan: Rc<crate::world::query::plan::ResolvedPlan>,
338 since: crate::time::ChangeTick,
339 captured_now: crate::time::ChangeTick,
340 cursor: Option<&'c mut crate::query::QueryCursor>,
341 cached: Option<QueryCachedSource>,
342 table_archetypes: Option<&'w [usize]>,
343 second_index: usize,
344 second_is_table: bool,
345 ) -> Result<Self, crate::query::QueryError> {
346 let state = Self::state(world, &plan, cached, None, table_archetypes)?;
347 Ok(Self {
348 world,
349 params_fingerprint: plan.fingerprint,
350 plan,
351 captured_now,
352 since,
353 cursor_committed: false,
354 cursor,
355 state,
356 second_index,
357 second_is_table,
358 marker: core::marker::PhantomData,
359 })
360 }
361
362 #[allow(clippy::too_many_arguments)]
363 pub(crate) fn new_prepared(
364 world: &'w crate::world::World,
365 plan: Rc<crate::world::query::plan::ResolvedPlan>,
366 since: crate::time::ChangeTick,
367 captured_now: crate::time::ChangeTick,
368 cursor: Option<&'c mut crate::query::QueryCursor>,
369 materialized: Option<(&'w [EntityId], bool)>,
370 table_archetypes: Option<&'w [usize]>,
371 second_index: usize,
372 second_is_table: bool,
373 ) -> Result<Self, crate::query::QueryError> {
374 let state = Self::state(world, &plan, None, materialized, table_archetypes)?;
375 Ok(Self {
376 world,
377 params_fingerprint: plan.fingerprint,
378 plan,
379 captured_now,
380 since,
381 cursor_committed: false,
382 cursor,
383 state,
384 second_index,
385 second_is_table,
386 marker: core::marker::PhantomData,
387 })
388 }
389
390 fn state(
391 world: &'w crate::world::World,
392 plan: &crate::world::query::plan::ResolvedPlan,
393 cached: Option<QueryCachedSource>,
394 materialized: Option<(&'w [EntityId], bool)>,
395 table_archetypes: Option<&'w [usize]>,
396 ) -> Result<Query2State<'w>, crate::query::QueryError> {
397 if let Some((ids, apply_temporal)) = materialized {
398 return Ok(Query2State::Borrowed {
399 ids,
400 index: 0,
401 apply_temporal,
402 });
403 }
404 if let Some(source) = cached {
405 return Ok(Query2State::Cached { source, index: 0 });
406 }
407 match &plan.traversal {
408 crate::world::query::plan::TraversalSource::All => {
409 Err(crate::query::QueryError::WrongQuery {
410 detail: alloc::string::String::from(
411 "entity-only plan cannot back a typed query",
412 ),
413 })
414 }
415 crate::world::query::plan::TraversalSource::Sparse { component_index } => {
416 let slots = world.sparse_dense_slots(*component_index).ok_or_else(|| {
417 crate::query::QueryError::WrongStorageKind {
418 name: alloc::format!("component {component_index}"),
419 }
420 })?;
421 Ok(Query2State::Sparse { slots, index: 0 })
422 }
423 crate::world::query::plan::TraversalSource::Table { .. } => Ok(Query2State::Table {
424 archetypes: table_archetypes.expect("table archetypes prepared"),
425 archetype_index: 0,
426 row: 0,
427 }),
428 crate::world::query::plan::TraversalSource::Exact { ids } => Ok(Query2State::Exact {
429 ids: ids.clone(),
430 index: 0,
431 }),
432 }
433 }
434
435 fn commit_cursor_if_needed(&mut self) {
436 if self.cursor_committed {
437 return;
438 }
439 let world = self.world;
440 let fingerprint = self.params_fingerprint;
441 let cursor = self
442 .cursor
443 .as_mut()
444 .filter(|cursor| cursor.validate(world, fingerprint).is_ok());
445 if let Some(cursor) = cursor {
446 cursor.commit(self.captured_now);
447 }
448 self.cursor_committed = true;
449 }
450
451 fn match_entity(&self, entity: EntityId, filter: CandidateFilter) -> Option<(&'w A, &'w B)> {
452 let matches = match filter {
453 CandidateFilter::Full => crate::world::query::filter::entity_matches(
454 self.world,
455 entity,
456 &self.plan,
457 self.since,
458 self.captured_now,
459 ),
460 CandidateFilter::Temporal => crate::world::query::filter::entity_matches_temporal(
461 self.world,
462 entity,
463 &self.plan,
464 self.since,
465 self.captured_now,
466 ),
467 CandidateFilter::Trusted => true,
468 };
469 if !matches {
470 return None;
471 }
472 let first = self.world.query_component::<A>(
473 entity,
474 self.plan.primary_index,
475 self.plan.primary_is_table,
476 )?;
477 let second =
478 self.world
479 .query_component::<B>(entity, self.second_index, self.second_is_table)?;
480 Some((first, second))
481 }
482}
483
484impl<'w, 'c, A: 'static, B: 'static> Iterator for Query2<'w, 'c, A, B> {
485 type Item = (EntityId, &'w A, &'w B);
486
487 fn next(&mut self) -> Option<Self::Item> {
488 loop {
489 let candidate = match &mut self.state {
490 Query2State::Done => {
491 self.commit_cursor_if_needed();
492 return None;
493 }
494 Query2State::Sparse { slots, index } => {
495 let entity = slots
496 .get(*index)
497 .copied()
498 .map(|slot| self.world.entity_from_slot(slot));
499 *index += usize::from(entity.is_some());
500 entity.map(|entity| (entity, CandidateFilter::Full))
501 }
502 Query2State::Table {
503 archetypes,
504 archetype_index,
505 row,
506 } => {
507 let mut entity = None;
508 while *archetype_index < archetypes.len() && entity.is_none() {
509 let slots = self
510 .world
511 .archetype_entity_slots(archetypes[*archetype_index]);
512 if let Some(slot) = slots.get(*row).copied() {
513 *row += 1;
514 entity = Some(self.world.entity_from_slot(slot));
515 } else {
516 *archetype_index += 1;
517 *row = 0;
518 }
519 }
520 entity.map(|entity| (entity, CandidateFilter::Full))
521 }
522 Query2State::Exact { ids, index } => {
523 let entity = ids.get(*index).copied();
524 *index += usize::from(entity.is_some());
525 entity.map(|entity| (entity, CandidateFilter::Full))
526 }
527 Query2State::Cached { source, index } => {
528 let ids = match self
529 .world
530 .cached_query_entities(source, self.params_fingerprint)
531 {
532 Ok(ids) => ids,
533 Err(_) => {
534 self.state = Query2State::Done;
535 continue;
536 }
537 };
538 let entity = ids.get(*index).copied();
539 *index += usize::from(entity.is_some());
540 entity.map(|entity| (entity, CandidateFilter::Full))
541 }
542 Query2State::Borrowed {
543 ids,
544 index,
545 apply_temporal,
546 } => {
547 let entity = ids.get(*index).copied();
548 *index += usize::from(entity.is_some());
549 let filter = if *apply_temporal {
550 CandidateFilter::Temporal
551 } else {
552 CandidateFilter::Trusted
553 };
554 entity.map(|entity| (entity, filter))
555 }
556 };
557 let Some((entity, filter)) = candidate else {
558 self.state = Query2State::Done;
559 continue;
560 };
561 if let Some((first, second)) = self.match_entity(entity, filter) {
562 return Some((entity, first, second));
563 }
564 }
565 }
566}
567
568#[derive(Clone, Copy)]
569enum CandidateFilter {
570 Full,
571 Temporal,
572 Trusted,
573}
574
575impl<'w, 'c, A: 'static, B: 'static> Drop for Query2<'w, 'c, A, B> {
576 fn drop(&mut self) {
577 if let Query2State::Done = self.state {
578 self.commit_cursor_if_needed();
579 }
580 }
581}
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586 use crate::component::ComponentOptions;
587 use crate::query::{ExactIdPolicy, QueryError, QueryParams, QuerySpec};
588 use crate::world::query::plan::{ResolvedPlan, TraversalSource};
589 use crate::world::WorldBuilder;
590 use alloc::rc::Rc;
591
592 #[derive(Clone, Copy)]
593 struct Pos(i32);
594
595 #[derive(Clone, Copy)]
596 struct Vel(#[allow(dead_code)] i32);
597
598 fn sparse_world() -> crate::world::World {
599 let mut builder = WorldBuilder::new();
600 builder
601 .register_component::<Pos>(ComponentOptions::sparse())
602 .expect("pos");
603 builder
604 .register_component::<Vel>(ComponentOptions::sparse())
605 .expect("vel");
606 builder.build().expect("build")
607 }
608
609 #[test]
610 fn cached_iterator_stops_when_cache_lookup_fails_mid_iteration() {
611 use crate::world::query::cached_source::QueryCachedSource;
612
613 let mut world = sparse_world();
614 let entity = world.spawn().expect("spawn");
615 world.insert(entity, Pos(1)).expect("insert");
616 let plan = world
617 .resolve_query1_plan::<Pos>(&QuerySpec::new())
618 .expect("plan");
619 let cache = world
620 .build_query_cache::<Pos>(QuerySpec::new())
621 .expect("cache");
622 let stale = cache.clone();
623 world.invalidate_query_cache(&cache);
624 let mut iter = Query1::<Pos> {
625 world: &world,
626 plan: plan.clone(),
627 params_fingerprint: plan.fingerprint,
628 captured_now: world.change_tick(),
629 since: crate::time::ChangeTick::ZERO,
630 cursor_committed: false,
631 cursor: None,
632 additional_covered_required: None,
633 state: Query1State::Cached {
634 source: QueryCachedSource::Membership(stale),
635 index: 0,
636 },
637 };
638 assert!(iter.next().is_none());
639 assert!(matches!(iter.state, Query1State::Done));
640 }
641
642 #[test]
643 fn query2_new_propagates_query1_resolution_errors() {
644 let world = sparse_world();
645 let plan = Rc::new(crate::world::query::plan::ResolvedPlan {
646 fingerprint: 1,
647 primary_index: 0,
648 primary_is_table: false,
649 traversal: crate::world::query::plan::TraversalSource::Sparse {
650 component_index: 99,
651 },
652 required_indices: alloc::vec![99],
653 without_indices: alloc::vec![],
654 with_tag_indices: alloc::vec![],
655 without_tag_indices: alloc::vec![],
656 added_indices: alloc::vec![],
657 changed_indices: alloc::vec![],
658 exact_id_policy: None,
659 });
660 assert!(matches!(
661 Query2::<Pos, Vel>::new(
662 &world,
663 plan,
664 crate::time::ChangeTick::ZERO,
665 crate::time::ChangeTick::ZERO,
666 None,
667 None,
668 None,
669 1,
670 false,
671 ),
672 Err(QueryError::WrongStorageKind { .. })
673 ));
674 }
675
676 #[test]
677 fn query2_iterator_skips_entities_missing_second_component() {
678 let mut world = sparse_world();
679 let partial = world.spawn().expect("partial");
680 let matched = world.spawn().expect("matched");
681 world.insert(partial, Pos(1)).expect("partial");
682 world.insert(matched, Pos(2)).expect("matched pos");
683 world.insert(matched, Vel(9)).expect("matched vel");
684 let plan = Rc::new(ResolvedPlan {
685 fingerprint: 1,
686 primary_index: 0,
687 primary_is_table: false,
688 traversal: TraversalSource::Sparse { component_index: 0 },
689 required_indices: alloc::vec![0],
690 without_indices: alloc::vec![],
691 with_tag_indices: alloc::vec![],
692 without_tag_indices: alloc::vec![],
693 added_indices: alloc::vec![],
694 changed_indices: alloc::vec![],
695 exact_id_policy: None,
696 });
697 let mut iter = Query2::<Pos, Vel>::new(
698 &world,
699 plan,
700 crate::time::ChangeTick::ZERO,
701 world.change_tick(),
702 None,
703 None,
704 None,
705 1,
706 false,
707 )
708 .expect("query2");
709 assert_eq!(iter.next().map(|(_, pos, _)| pos.0), Some(2));
710 assert!(iter.next().is_none());
711 }
712
713 #[test]
714 fn query1_source_covered_path_applies_remaining_filters() {
715 let mut world = sparse_world();
716 let partial = world.spawn().expect("partial");
717 let matched = world.spawn().expect("matched");
718 world.insert(partial, Pos(1)).expect("partial pos");
719 world.insert(partial, Vel(3)).expect("partial vel");
720 world.insert(matched, Pos(2)).expect("matched pos");
721 let plan = world
722 .resolve_query1_plan::<Pos>(&QuerySpec::new().without::<Vel>())
723 .expect("plan");
724 let pos_index = world.component_index::<Pos>().expect("pos index");
725 let mut iter: Query1<'_, '_, Pos> = Query1::new(
726 &world,
727 plan,
728 crate::time::ChangeTick::ZERO,
729 world.change_tick(),
730 None,
731 None,
732 None,
733 Some(pos_index),
734 )
735 .expect("query1");
736
737 assert_eq!(
738 iter.next().map(|(entity, pos)| (entity, pos.0)),
739 Some((matched, 2))
740 );
741 assert!(iter.next().is_none());
742 }
743
744 #[test]
745 fn exact_query1_skips_ids_missing_the_component() {
746 let mut world = sparse_world();
747 let missing = world.spawn().expect("missing");
748 let matched = world.spawn().expect("matched");
749 world.insert(matched, Pos(4)).expect("matched pos");
750 let spec = QuerySpec::new().exact_ids(
751 alloc::vec![missing, matched],
752 ExactIdPolicy::SkipUnavailable,
753 );
754 let values: alloc::vec::Vec<_> = world
755 .query::<Pos>(&spec, QueryParams::new())
756 .expect("query")
757 .map(|(_, pos)| pos.0)
758 .collect();
759 assert_eq!(values, alloc::vec![4]);
760 }
761
762 #[test]
763 fn cached_query1_applies_temporal_filter_on_valid_membership() {
764 let mut world = sparse_world();
765 let entity = world.spawn().expect("entity");
766 world.insert(entity, Pos(1)).expect("pos");
767 let since = world.change_tick();
768 world.get_mut::<Pos>(entity).expect("get").expect("pos").0 = 2;
769 let spec = QuerySpec::new().changed::<Pos>();
770 let cache = world.build_query_cache::<Pos>(spec.clone()).expect("cache");
771
772 assert_eq!(
773 world
774 .query::<Pos>(
775 &spec,
776 QueryParams::new().since(since).membership_cache(&cache),
777 )
778 .expect("cached query")
779 .count(),
780 1
781 );
782 }
783
784 #[test]
785 fn cached_and_borrowed_query1_return_valid_members() {
786 use crate::world::query::cached_source::QueryCachedSource;
787
788 let mut world = sparse_world();
789 let entity = world.spawn().expect("entity");
790 let stale = world.spawn().expect("stale");
791 let missing = world.spawn().expect("missing");
792 world.insert(entity, Pos(7)).expect("pos");
793 world.insert(stale, Pos(8)).expect("stale pos");
794 let spec = QuerySpec::new();
795 let plan = world.resolve_query1_plan::<Pos>(&spec).expect("plan");
796 let cache = world.build_query_cache::<Pos>(spec).expect("cache");
797 world.remove::<Pos>(stale).expect("remove stale pos");
798 let mut cached: Query1<'_, '_, Pos> = Query1::new(
799 &world,
800 plan.clone(),
801 crate::time::ChangeTick::ZERO,
802 world.change_tick(),
803 None,
804 Some(QueryCachedSource::Membership(cache)),
805 None,
806 None,
807 )
808 .expect("cached");
809 assert_eq!(
810 cached.next().map(|(id, value)| (id, value.0)),
811 Some((entity, 7))
812 );
813 assert!(cached.next().is_none());
814 drop(cached);
815
816 let ids = [missing, entity];
817 let mut borrowed: Query1<'_, '_, Pos> = Query1::new_prepared(
818 &world,
819 plan,
820 crate::time::ChangeTick::ZERO,
821 world.change_tick(),
822 None,
823 Some((&ids, false)),
824 None,
825 )
826 .expect("borrowed");
827 assert_eq!(
828 borrowed.next().map(|(id, value)| (id, value.0)),
829 Some((entity, 7))
830 );
831 assert!(borrowed.next().is_none());
832 }
833
834 #[test]
835 fn exhausted_typed_iterators_commit_their_cursors_and_drop_done() {
836 let mut world = sparse_world();
837 let entity = world.spawn().expect("entity");
838 world.insert(entity, Pos(1)).expect("pos");
839 world.insert(entity, Vel(2)).expect("vel");
840
841 let q1_spec = QuerySpec::new().changed::<Pos>();
842 let mut q1_cursor = crate::query::QueryCursor::from_spec_start::<Pos>(&mut world, &q1_spec)
843 .expect("Q1 cursor");
844 let q1_before = q1_cursor.since();
845 {
846 let mut query = world
847 .query::<Pos>(&q1_spec, QueryParams::new().cursor(&mut q1_cursor))
848 .expect("Q1");
849 assert!(query.next().is_some());
850 assert!(query.next().is_none());
851 }
852 assert!(q1_cursor.since() > q1_before);
853
854 let q2_spec = QuerySpec::new().changed::<Pos>();
855 let mut q2_cursor =
856 crate::query::QueryCursor::from_spec2_start::<Pos, Vel>(&mut world, &q2_spec)
857 .expect("Q2 cursor");
858 let q2_before = q2_cursor.since();
859 {
860 let mut query = world
861 .query2::<Pos, Vel>(&q2_spec, QueryParams::new().cursor(&mut q2_cursor))
862 .expect("Q2");
863 assert!(query.next().is_some());
864 assert!(query.next().is_none());
865 }
866 assert!(q2_cursor.since() > q2_before);
867
868 let mut partial_cursor =
869 crate::query::QueryCursor::from_spec2_start::<Pos, Vel>(&mut world, &q2_spec)
870 .expect("partial Q2 cursor");
871 let partial_before = partial_cursor.since();
872 {
873 let mut query = world
874 .query2::<Pos, Vel>(&q2_spec, QueryParams::new().cursor(&mut partial_cursor))
875 .expect("partial Q2");
876 assert!(query.next().is_some());
877 }
878 assert_eq!(partial_cursor.since(), partial_before);
879 }
880
881 #[test]
882 fn query2_rejects_entity_only_plan_and_stale_cache() {
883 use crate::world::query::cached_source::QueryCachedSource;
884
885 let mut world = sparse_world();
886 let all_plan = Rc::new(ResolvedPlan {
887 fingerprint: 9,
888 primary_index: 0,
889 primary_is_table: false,
890 traversal: TraversalSource::All,
891 required_indices: alloc::vec![],
892 without_indices: alloc::vec![],
893 with_tag_indices: alloc::vec![],
894 without_tag_indices: alloc::vec![],
895 added_indices: alloc::vec![],
896 changed_indices: alloc::vec![],
897 exact_id_policy: None,
898 });
899 assert!(matches!(
900 Query2::<Pos, Vel>::new(
901 &world,
902 all_plan,
903 crate::time::ChangeTick::ZERO,
904 world.change_tick(),
905 None,
906 None,
907 None,
908 1,
909 false,
910 ),
911 Err(QueryError::WrongQuery { .. })
912 ));
913
914 let entity = world.spawn().expect("entity");
915 world.insert(entity, Pos(1)).expect("pos");
916 world.insert(entity, Vel(2)).expect("vel");
917 let spec = QuerySpec::new();
918 let (plan, second_index, second_is_table) =
919 world.resolve_query2_plan::<Pos, Vel>(&spec).expect("plan");
920 let cache = world.build_query2_cache::<Pos, Vel>(spec).expect("cache");
921 {
922 let mut cached = Query2::<Pos, Vel>::new(
923 &world,
924 plan.clone(),
925 crate::time::ChangeTick::ZERO,
926 world.change_tick(),
927 None,
928 Some(QueryCachedSource::Membership(cache.clone())),
929 None,
930 second_index,
931 second_is_table,
932 )
933 .expect("cached iterator");
934 assert_eq!(cached.next().map(|(id, _, _)| id), Some(entity));
935 assert!(cached.next().is_none());
936 }
937 let stale = cache.clone();
938 world.invalidate_query_cache(&cache);
939 let mut iter = Query2::<Pos, Vel>::new(
940 &world,
941 plan,
942 crate::time::ChangeTick::ZERO,
943 world.change_tick(),
944 None,
945 Some(QueryCachedSource::Membership(stale)),
946 None,
947 second_index,
948 second_is_table,
949 )
950 .expect("iterator");
951 assert!(iter.next().is_none());
952 assert!(matches!(iter.state, Query2State::Done));
953 }
954
955 #[test]
956 fn borrowed_query2_selects_temporal_and_trusted_filters() {
957 let mut world = sparse_world();
958 let matched = world.spawn().expect("matched");
959 world.insert(matched, Pos(1)).expect("pos");
960 world.insert(matched, Vel(2)).expect("vel");
961 let since = world.change_tick();
962 world.get_mut::<Pos>(matched).expect("get").expect("pos").0 = 3;
963 let spec = QuerySpec::new().changed::<Pos>();
964 let (plan, second_index, second_is_table) =
965 world.resolve_query2_plan::<Pos, Vel>(&spec).expect("plan");
966 let temporal_ids = [matched];
967 let mut temporal = Query2::<Pos, Vel>::new_prepared(
968 &world,
969 plan.clone(),
970 since,
971 world.change_tick(),
972 None,
973 Some((&temporal_ids, true)),
974 None,
975 second_index,
976 second_is_table,
977 )
978 .expect("temporal iterator");
979 assert_eq!(temporal.next().map(|(id, _, _)| id), Some(matched));
980 assert!(temporal.next().is_none());
981 drop(temporal);
982
983 let missing_primary = world.spawn().expect("missing primary");
984 world
985 .insert(missing_primary, Vel(4))
986 .expect("secondary only");
987 let trusted_ids = [missing_primary];
988 let mut trusted = Query2::<Pos, Vel>::new_prepared(
989 &world,
990 plan,
991 crate::time::ChangeTick::ZERO,
992 world.change_tick(),
993 None,
994 Some((&trusted_ids, false)),
995 None,
996 second_index,
997 second_is_table,
998 )
999 .expect("trusted iterator");
1000 assert!(trusted.next().is_none());
1001 }
1002}