Skip to main content

moirai/query/
prepared.rs

1//! Reusable prepared queries with optional membership or result materialization.
2//!
3//! **Semantic map**
4//! - [`QueryPolicy`]: plan-only, membership, delta membership, or full result materialization.
5//! - [`QueryWindow`]: per-execution added/changed window (`All`, `Since`, [`QueryCursor`]).
6//! - [`PreparedQuery1`] / [`PreparedQuery2`]: owner-scoped plans with refresh and mutation entry points.
7
8use alloc::rc::Rc;
9use alloc::vec::Vec;
10use core::marker::PhantomData;
11
12use crate::entity::EntityId;
13use crate::query::{Query1, Query2, QueryCursor, QueryEffects, QueryError, QuerySpec};
14use crate::time::ChangeTick;
15use crate::world::query::cache::QueryTopologySnapshot;
16use crate::world::query::collect::{collect_query1_entities, collect_query1_structural_members};
17use crate::world::query::filter::validate_exact_ids;
18use crate::world::query::plan::{ResolvedPlan, TraversalSource};
19use crate::world::{World, WorldOwner};
20
21/// How a prepared query caches structural membership or full results between executions.
22#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
23#[non_exhaustive]
24pub enum QueryPolicy {
25    /// Reuse only the resolved plan and traverse sparse or table storage each execution.
26    #[default]
27    Prepared,
28    /// Keep structural membership and apply added/changed windows during traversal.
29    Membership,
30    /// Maintain membership incrementally from the world's query delta log.
31    DeltaMembership,
32    /// Materialize the full result set once; added/changed windows are rejected.
33    Result,
34}
35
36/// Added/changed window for one prepared-query execution.
37pub enum QueryWindow<'a> {
38    /// Traverse from the start of change history.
39    All,
40    /// Half-open window `(tick, captured_now]`.
41    Since(ChangeTick),
42    /// Reusable cursor bound to this prepared query's fingerprint.
43    Cursor(&'a mut QueryCursor),
44}
45
46impl<'a> QueryWindow<'a> {
47    /// Window from the start of change history.
48    pub const fn all() -> Self {
49        Self::All
50    }
51
52    /// Explicit half-open added/changed window.
53    pub const fn since(tick: ChangeTick) -> Self {
54        Self::Since(tick)
55    }
56
57    /// Cursor-backed added/changed window for repeated traversals.
58    pub fn cursor(cursor: &mut QueryCursor) -> QueryWindow<'_> {
59        QueryWindow::Cursor(cursor)
60    }
61
62    pub(crate) fn into_parts(
63        self,
64        world: &World,
65        fingerprint: u64,
66    ) -> Result<(ChangeTick, Option<&'a mut QueryCursor>), QueryError> {
67        match self {
68            Self::All => Ok((ChangeTick::ZERO, None)),
69            Self::Since(tick) => Ok((tick, None)),
70            Self::Cursor(cursor) => {
71                cursor.validate(world, fingerprint)?;
72                Ok((cursor.since(), Some(cursor)))
73            }
74        }
75    }
76}
77
78/// Owner-scoped, reusable single-component query with optional materialized membership.
79pub struct PreparedQuery1<T: 'static> {
80    pub(crate) owner: WorldOwner,
81    pub(crate) plan: Rc<ResolvedPlan>,
82    materialization: Materialization,
83    mutation_scratch: Vec<EntityId>,
84    marker: PhantomData<fn() -> T>,
85}
86
87/// Owner-scoped, reusable two-component query with driver reselection and optional materialization.
88pub struct PreparedQuery2<A: 'static, B: 'static> {
89    pub(crate) owner: WorldOwner,
90    pub(crate) plan: Rc<ResolvedPlan>,
91    pub(crate) second_index: usize,
92    pub(crate) second_is_table: bool,
93    driver_revisions: (u64, u64),
94    materialization: Materialization,
95    mutation_scratch: Vec<EntityId>,
96    marker: PhantomData<fn() -> (A, B)>,
97}
98
99enum Materialization {
100    Prepared,
101    Membership(MaterializedSet),
102    DeltaMembership(DeltaSet),
103    Result(MaterializedSet),
104}
105
106struct MaterializedSet {
107    topology: QueryTopologySnapshot,
108    ids: Vec<EntityId>,
109}
110
111struct DeltaSet {
112    topology: QueryTopologySnapshot,
113    ids: Vec<EntityId>,
114    reverse: Vec<Option<usize>>,
115    changed: Vec<EntityId>,
116    changed_reverse: Vec<Option<usize>>,
117    cursor: Rc<core::cell::Cell<u64>>,
118}
119
120impl Materialization {
121    fn build(
122        world: &mut World,
123        plan: &ResolvedPlan,
124        policy: QueryPolicy,
125    ) -> Result<Self, QueryError> {
126        if !matches!(policy, QueryPolicy::Prepared)
127            && matches!(plan.traversal, TraversalSource::Exact { .. })
128        {
129            return Err(QueryError::UnsupportedCachePolicy {
130                detail: alloc::string::String::from(
131                    "materialized policies do not support exact-id query order",
132                ),
133            });
134        }
135        if matches!(policy, QueryPolicy::Result)
136            && (!plan.added_indices.is_empty() || !plan.changed_indices.is_empty())
137        {
138            return Err(QueryError::MovingChangeWindow);
139        }
140
141        let topology = || QueryTopologySnapshot::capture(world, plan);
142        Ok(match policy {
143            QueryPolicy::Prepared => Self::Prepared,
144            QueryPolicy::Membership => Self::Membership(MaterializedSet {
145                topology: topology(),
146                ids: collect_query1_structural_members(world, plan),
147            }),
148            QueryPolicy::DeltaMembership => {
149                let ids = collect_query1_structural_members(world, plan);
150                let reverse = build_reverse(&ids);
151                Self::DeltaMembership(DeltaSet {
152                    topology: topology(),
153                    ids,
154                    reverse,
155                    changed: Vec::new(),
156                    changed_reverse: Vec::new(),
157                    cursor: world.register_query_delta_cursor(),
158                })
159            }
160            QueryPolicy::Result => Self::Result(MaterializedSet {
161                topology: topology(),
162                ids: collect_query1_entities(world, plan, ChangeTick::ZERO, world.change_tick()),
163            }),
164        })
165    }
166
167    fn refresh(&mut self, world: &World, plan: &ResolvedPlan) {
168        match self {
169            Self::Prepared => {}
170            Self::Membership(set) => {
171                if topology_changed(&mut set.topology, world) {
172                    set.ids = collect_query1_structural_members(world, plan);
173                    set.topology = QueryTopologySnapshot::capture(world, plan);
174                }
175            }
176            Self::DeltaMembership(set) => {
177                if set.topology.observed_global_revision() != world.query_topology_revision() {
178                    world.collect_query_delta_entities(
179                        &set.cursor,
180                        plan,
181                        &mut set.changed,
182                        &mut set.changed_reverse,
183                    );
184                    for index in 0..set.changed.len() {
185                        let entity = set.changed[index];
186                        update_delta_entity(set, world, plan, entity);
187                    }
188                    set.topology = QueryTopologySnapshot::capture(world, plan);
189                }
190            }
191            Self::Result(set) => {
192                if topology_changed(&mut set.topology, world) {
193                    set.ids =
194                        collect_query1_entities(world, plan, ChangeTick::ZERO, world.change_tick());
195                    set.topology = QueryTopologySnapshot::capture(world, plan);
196                }
197            }
198        }
199    }
200
201    fn ids_and_temporal_filter(&self, plan: &ResolvedPlan) -> Option<(&[EntityId], bool)> {
202        let apply_temporal = !plan.added_indices.is_empty() || !plan.changed_indices.is_empty();
203        match self {
204            Self::Prepared => None,
205            Self::Membership(set) => Some((&set.ids, apply_temporal)),
206            Self::DeltaMembership(set) => Some((&set.ids, apply_temporal)),
207            Self::Result(set) => Some((&set.ids, false)),
208        }
209    }
210}
211
212fn topology_changed(topology: &mut QueryTopologySnapshot, world: &World) -> bool {
213    let revision = world.query_topology_revision();
214    if topology.observed_global_revision() == revision {
215        return false;
216    }
217    if topology.dependencies_are_current(world) {
218        topology.observe_global_revision(revision);
219        false
220    } else {
221        true
222    }
223}
224
225fn build_reverse(ids: &[EntityId]) -> Vec<Option<usize>> {
226    let Some(max_slot) = ids.iter().map(|entity| entity.slot() as usize).max() else {
227        return Vec::new();
228    };
229    let mut reverse = alloc::vec![None; max_slot + 1];
230    for (index, entity) in ids.iter().enumerate() {
231        reverse[entity.slot() as usize] = Some(index);
232    }
233    reverse
234}
235
236fn update_delta_entity(set: &mut DeltaSet, world: &World, plan: &ResolvedPlan, entity: EntityId) {
237    let slot = entity.slot() as usize;
238    let existing = set.reverse.get(slot).and_then(|index| *index);
239    let matches = crate::world::query::filter::entity_matches_structural(world, entity, plan);
240
241    if let Some(index) = existing {
242        if set.ids.get(index) == Some(&entity) && matches {
243            return;
244        }
245        remove_delta_index(set, index);
246    }
247    if matches {
248        if set.reverse.len() <= slot {
249            set.reverse.resize(slot + 1, None);
250        }
251        let index = set.ids.len();
252        set.ids.push(entity);
253        set.reverse[slot] = Some(index);
254    }
255}
256
257fn remove_delta_index(set: &mut DeltaSet, index: usize) {
258    let removed = set.ids.swap_remove(index);
259    set.reverse[removed.slot() as usize] = None;
260    if let Some(&moved) = set.ids.get(index) {
261        set.reverse[moved.slot() as usize] = Some(index);
262    }
263}
264
265impl World {
266    pub fn prepare_query1<T: 'static>(
267        &mut self,
268        spec: QuerySpec,
269        policy: QueryPolicy,
270    ) -> Result<PreparedQuery1<T>, QueryError> {
271        let plan = self.resolve_query1_plan::<T>(&spec)?;
272        validate_exact_ids(self, &plan)?;
273        let materialization = Materialization::build(self, &plan, policy)?;
274        Ok(PreparedQuery1 {
275            owner: self.owner_token(),
276            plan,
277            materialization,
278            mutation_scratch: Vec::new(),
279            marker: PhantomData,
280        })
281    }
282
283    pub fn prepare_query2<A: 'static, B: 'static>(
284        &mut self,
285        spec: QuerySpec,
286        policy: QueryPolicy,
287    ) -> Result<PreparedQuery2<A, B>, QueryError> {
288        let (plan, second_index, second_is_table) = self.resolve_query2_plan::<A, B>(&spec)?;
289        validate_exact_ids(self, &plan)?;
290        let materialization = Materialization::build(self, &plan, policy)?;
291        Ok(PreparedQuery2 {
292            owner: self.owner_token(),
293            plan,
294            second_index,
295            second_is_table,
296            // Force the cached semantic plan's physical driver to be selected
297            // from the current populations, even if the plan cache was filled
298            // before a cardinality crossover.
299            driver_revisions: (u64::MAX, u64::MAX),
300            materialization,
301            mutation_scratch: Vec::new(),
302            marker: PhantomData,
303        })
304    }
305}
306
307impl<T: 'static> PreparedQuery1<T> {
308    /// Read iterator over matches for the chosen [`QueryWindow`].
309    pub fn iter<'w, 'c>(
310        &'w mut self,
311        world: &'w mut World,
312        window: QueryWindow<'c>,
313    ) -> Result<Query1<'w, 'c, T>, QueryError> {
314        self.validate_world(world)?;
315        validate_exact_ids(world, &self.plan)?;
316        let captured_now = world.change_tick();
317        let (since, cursor) = window.into_parts(world, self.plan.fingerprint)?;
318        self.materialization.refresh(world, &self.plan);
319        let materialized = self.materialization.ids_and_temporal_filter(&self.plan);
320
321        let table_component = match self.plan.traversal {
322            TraversalSource::Table { component_index } => Some(component_index),
323            _ => None,
324        };
325        if let Some(index) = table_component {
326            world.ensure_table_archetypes(index);
327        }
328        let table_archetypes = table_component.map(|index| {
329            world
330                .table_archetypes(index)
331                .expect("table archetypes prepared")
332        });
333        Query1::new_prepared(
334            world,
335            self.plan.clone(),
336            since,
337            captured_now,
338            cursor,
339            materialized,
340            table_archetypes,
341        )
342    }
343
344    /// Mutable traversal with callback-scoped borrows over the chosen window.
345    pub fn for_each_mut(
346        &mut self,
347        world: &mut World,
348        window: QueryWindow<'_>,
349        f: impl FnMut(EntityId, &mut T) -> Result<(), QueryError>,
350    ) -> Result<(), QueryError> {
351        self.for_each_mut_inner(world, window, f)
352    }
353
354    /// Mutable traversal that may enqueue commands or emit declared events.
355    pub fn for_each_mut_with_effects(
356        &mut self,
357        world: &mut World,
358        window: QueryWindow<'_>,
359        f: impl FnMut(EntityId, &mut T, &mut QueryEffects<'_>) -> Result<(), QueryError>,
360    ) -> Result<(), QueryError> {
361        self.for_each_mut_effects_inner(world, window, f)
362    }
363
364    fn for_each_mut_inner(
365        &mut self,
366        world: &mut World,
367        window: QueryWindow<'_>,
368        mut f: impl FnMut(EntityId, &mut T) -> Result<(), QueryError>,
369    ) -> Result<(), QueryError> {
370        self.for_each_mut_effects_inner(world, window, |entity, value, _| f(entity, value))
371    }
372
373    fn for_each_mut_effects_inner(
374        &mut self,
375        world: &mut World,
376        window: QueryWindow<'_>,
377        f: impl FnMut(EntityId, &mut T, &mut QueryEffects<'_>) -> Result<(), QueryError>,
378    ) -> Result<(), QueryError> {
379        self.validate_world(world)?;
380        validate_exact_ids(world, &self.plan)?;
381        let captured_now = world.change_tick();
382        let (since, mut cursor) = window.into_parts(world, self.plan.fingerprint)?;
383        self.materialization.refresh(world, &self.plan);
384        world.for_each_mut_resolved(
385            &self.plan,
386            self.materialization.ids_and_temporal_filter(&self.plan),
387            &mut self.mutation_scratch,
388            since,
389            captured_now,
390            f,
391        )?;
392        if let Some(cursor) = cursor.as_mut() {
393            cursor.commit(captured_now);
394        }
395        Ok(())
396    }
397
398    fn validate_world(&self, world: &World) -> Result<(), QueryError> {
399        if self.owner.same(&world.owner_token()) {
400            Ok(())
401        } else {
402            Err(QueryError::WrongOwner)
403        }
404    }
405}
406
407impl<A: 'static, B: 'static> PreparedQuery2<A, B> {
408    /// Read iterator over matches for the chosen [`QueryWindow`].
409    pub fn iter<'w, 'c>(
410        &'w mut self,
411        world: &'w mut World,
412        window: QueryWindow<'c>,
413    ) -> Result<Query2<'w, 'c, A, B>, QueryError> {
414        self.validate_world(world)?;
415        self.refresh_physical_plan(world);
416        validate_exact_ids(world, &self.plan)?;
417        let captured_now = world.change_tick();
418        let (since, cursor) = window.into_parts(world, self.plan.fingerprint)?;
419        self.materialization.refresh(world, &self.plan);
420        let materialized = self.materialization.ids_and_temporal_filter(&self.plan);
421
422        let table_component = match self.plan.traversal {
423            TraversalSource::Table { component_index } => Some(component_index),
424            _ => None,
425        };
426        if let Some(index) = table_component {
427            world.ensure_table_archetypes(index);
428        }
429        let table_archetypes = table_component.map(|index| {
430            world
431                .table_archetypes(index)
432                .expect("table archetypes prepared")
433        });
434        Query2::new_prepared(
435            world,
436            self.plan.clone(),
437            since,
438            captured_now,
439            cursor,
440            materialized,
441            table_archetypes,
442            self.second_index,
443            self.second_is_table,
444        )
445    }
446
447    /// Mutable traversal borrowing both components for each match.
448    pub fn for_each_mut_mut(
449        &mut self,
450        world: &mut World,
451        window: QueryWindow<'_>,
452        mut f: impl FnMut(EntityId, &mut A, &mut B) -> Result<(), QueryError>,
453    ) -> Result<(), QueryError> {
454        self.for_each_mut_mut_with_effects(world, window, |entity, a, b, _| f(entity, a, b))
455    }
456
457    /// Mutable traversal over both components with optional [`QueryEffects`].
458    pub fn for_each_mut_mut_with_effects(
459        &mut self,
460        world: &mut World,
461        window: QueryWindow<'_>,
462        f: impl FnMut(EntityId, &mut A, &mut B, &mut QueryEffects<'_>) -> Result<(), QueryError>,
463    ) -> Result<(), QueryError> {
464        self.execute_mut(world, window, f)
465    }
466
467    /// Mutable traversal over the first component while borrowing the second immutably.
468    pub fn for_each_mut_read(
469        &mut self,
470        world: &mut World,
471        window: QueryWindow<'_>,
472        mut f: impl FnMut(EntityId, &mut A, &B) -> Result<(), QueryError>,
473    ) -> Result<(), QueryError> {
474        self.for_each_mut_read_with_effects(world, window, |entity, a, b, _| f(entity, a, b))
475    }
476
477    /// Mixed mut/read traversal with optional [`QueryEffects`].
478    pub fn for_each_mut_read_with_effects(
479        &mut self,
480        world: &mut World,
481        window: QueryWindow<'_>,
482        f: impl FnMut(EntityId, &mut A, &B, &mut QueryEffects<'_>) -> Result<(), QueryError>,
483    ) -> Result<(), QueryError> {
484        self.execute_mut_read(world, window, f)
485    }
486
487    fn execute_mut(
488        &mut self,
489        world: &mut World,
490        window: QueryWindow<'_>,
491        f: impl FnMut(EntityId, &mut A, &mut B, &mut QueryEffects<'_>) -> Result<(), QueryError>,
492    ) -> Result<(), QueryError> {
493        self.validate_world(world)?;
494        self.refresh_physical_plan(world);
495        validate_exact_ids(world, &self.plan)?;
496        let captured_now = world.change_tick();
497        let (since, mut cursor) = window.into_parts(world, self.plan.fingerprint)?;
498        self.materialization.refresh(world, &self.plan);
499        world.for_each2_mut_resolved(
500            &self.plan,
501            self.second_index,
502            self.second_is_table,
503            self.materialization.ids_and_temporal_filter(&self.plan),
504            &mut self.mutation_scratch,
505            since,
506            captured_now,
507            f,
508        )?;
509        if let Some(cursor) = cursor.as_mut() {
510            cursor.commit(captured_now);
511        }
512        Ok(())
513    }
514
515    fn execute_mut_read(
516        &mut self,
517        world: &mut World,
518        window: QueryWindow<'_>,
519        f: impl FnMut(EntityId, &mut A, &B, &mut QueryEffects<'_>) -> Result<(), QueryError>,
520    ) -> Result<(), QueryError> {
521        self.validate_world(world)?;
522        self.refresh_physical_plan(world);
523        validate_exact_ids(world, &self.plan)?;
524        let captured_now = world.change_tick();
525        let (since, mut cursor) = window.into_parts(world, self.plan.fingerprint)?;
526        self.materialization.refresh(world, &self.plan);
527        world.for_each2_mut_read_resolved(
528            &self.plan,
529            self.second_index,
530            self.second_is_table,
531            self.materialization.ids_and_temporal_filter(&self.plan),
532            &mut self.mutation_scratch,
533            since,
534            captured_now,
535            f,
536        )?;
537        if let Some(cursor) = cursor.as_mut() {
538            cursor.commit(captured_now);
539        }
540        Ok(())
541    }
542
543    fn validate_world(&self, world: &World) -> Result<(), QueryError> {
544        if self.owner.same(&world.owner_token()) {
545            Ok(())
546        } else {
547            Err(QueryError::WrongOwner)
548        }
549    }
550
551    fn refresh_physical_plan(&mut self, world: &World) {
552        if matches!(self.plan.traversal, TraversalSource::Exact { .. }) {
553            return;
554        }
555
556        let revisions = (
557            world.query_component_topology_revision(self.plan.primary_index),
558            world.query_component_topology_revision(self.second_index),
559        );
560        if self.driver_revisions == revisions {
561            return;
562        }
563
564        let primary_len =
565            world.query_component_population(self.plan.primary_index, self.plan.primary_is_table);
566        let second_len = world.query_component_population(self.second_index, self.second_is_table);
567        let (component_index, is_table) = if second_len < primary_len {
568            (self.second_index, self.second_is_table)
569        } else {
570            (self.plan.primary_index, self.plan.primary_is_table)
571        };
572        let traversal = if is_table {
573            TraversalSource::Table { component_index }
574        } else {
575            TraversalSource::Sparse { component_index }
576        };
577
578        if !same_traversal(&self.plan.traversal, &traversal) {
579            let mut plan = (*self.plan).clone();
580            plan.traversal = traversal;
581            self.plan = Rc::new(plan);
582        }
583        self.driver_revisions = revisions;
584    }
585}
586
587fn same_traversal(left: &TraversalSource, right: &TraversalSource) -> bool {
588    match (left, right) {
589        (TraversalSource::All, TraversalSource::All) => true,
590        (
591            TraversalSource::Sparse {
592                component_index: left,
593            },
594            TraversalSource::Sparse {
595                component_index: right,
596            },
597        )
598        | (
599            TraversalSource::Table {
600                component_index: left,
601            },
602            TraversalSource::Table {
603                component_index: right,
604            },
605        ) => left == right,
606        (TraversalSource::Exact { ids: left }, TraversalSource::Exact { ids: right }) => {
607            left == right
608        }
609        _ => false,
610    }
611}
612
613#[cfg(test)]
614mod tests {
615    use super::*;
616    use crate::component::ComponentOptions;
617    use crate::world::WorldBuilder;
618
619    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
620    struct A(i32);
621
622    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
623    struct B(i32);
624
625    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
626    struct C(i32);
627
628    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
629    struct D(i32);
630
631    fn world() -> World {
632        let mut builder = WorldBuilder::new();
633        builder
634            .register_component::<A>(ComponentOptions::sparse())
635            .expect("A");
636        builder
637            .register_component::<B>(ComponentOptions::table())
638            .expect("B");
639        builder
640            .register_component::<C>(ComponentOptions::sparse())
641            .expect("C");
642        builder
643            .register_component::<D>(ComponentOptions::table())
644            .expect("D");
645        builder.build().expect("world")
646    }
647
648    fn delta_state(world: &mut World, plan: &ResolvedPlan) -> DeltaSet {
649        let ids = collect_query1_structural_members(world, plan);
650        let reverse = build_reverse(&ids);
651        let topology = QueryTopologySnapshot::capture(world, plan);
652        let cursor = world.register_query_delta_cursor();
653        DeltaSet {
654            topology,
655            ids,
656            reverse,
657            changed: Vec::new(),
658            changed_reverse: Vec::new(),
659            cursor,
660        }
661    }
662
663    #[test]
664    fn reverse_indexed_delta_handles_add_remove_and_slot_reuse() {
665        let mut world = world();
666        let first = world.spawn().expect("first");
667        world.insert(first, A(1)).expect("A");
668        let plan = world
669            .resolve_query1_plan::<A>(&QuerySpec::new())
670            .expect("plan");
671        let mut state = delta_state(&mut world, &plan);
672
673        let second = world.spawn().expect("second");
674        world.insert(second, A(2)).expect("A2");
675        let third = world.spawn().expect("third");
676        world.insert(third, A(3)).expect("A3");
677        world.collect_query_delta_entities(
678            &state.cursor,
679            &plan,
680            &mut state.changed,
681            &mut state.changed_reverse,
682        );
683        let changed = state.changed.clone();
684        assert_eq!(changed, alloc::vec![second, third]);
685        for entity in changed {
686            update_delta_entity(&mut state, &world, &plan, entity);
687        }
688        assert_eq!(state.ids, alloc::vec![first, second, third]);
689        assert_eq!(state.reverse[second.slot() as usize], Some(1));
690
691        world.remove::<A>(first).expect("remove");
692        world.collect_query_delta_entities(
693            &state.cursor,
694            &plan,
695            &mut state.changed,
696            &mut state.changed_reverse,
697        );
698        let changed = state.changed.clone();
699        assert_eq!(changed, alloc::vec![first]);
700        for entity in changed {
701            update_delta_entity(&mut state, &world, &plan, entity);
702        }
703        assert_eq!(state.ids.len(), 2);
704        assert!(state.ids.contains(&second));
705        assert!(state.ids.contains(&third));
706        assert_eq!(state.reverse[first.slot() as usize], None);
707        assert_eq!(
708            state.reverse[second.slot() as usize],
709            state.ids.iter().position(|&entity| entity == second)
710        );
711
712        world.despawn(second).expect("despawn");
713        let replacement = world.spawn().expect("replacement");
714        assert_eq!(replacement.slot(), second.slot());
715        world.insert(replacement, A(4)).expect("replacement A");
716        world.collect_query_delta_entities(
717            &state.cursor,
718            &plan,
719            &mut state.changed,
720            &mut state.changed_reverse,
721        );
722        let changed = state.changed.clone();
723        // Changes are deduplicated by slot and retain the newest generation.
724        assert_eq!(changed, alloc::vec![replacement]);
725        for entity in changed {
726            update_delta_entity(&mut state, &world, &plan, entity);
727        }
728        assert_eq!(state.ids.len(), 2);
729        assert!(state.ids.contains(&third));
730        assert!(state.ids.contains(&replacement));
731        assert_eq!(
732            state.reverse[third.slot() as usize],
733            state.ids.iter().position(|&entity| entity == third)
734        );
735        assert_eq!(
736            state.reverse[replacement.slot() as usize],
737            state.ids.iter().position(|&entity| entity == replacement)
738        );
739    }
740
741    #[test]
742    fn materialized_policies_track_structural_changes() {
743        for policy in [
744            QueryPolicy::Membership,
745            QueryPolicy::DeltaMembership,
746            QueryPolicy::Result,
747        ] {
748            let mut world = world();
749            let mut query = world
750                .prepare_query1::<A>(QuerySpec::new(), policy)
751                .expect("prepare");
752            let entity = world.spawn().expect("spawn");
753            world.insert(entity, A(3)).expect("insert");
754            assert_eq!(
755                query
756                    .iter(&mut world, QueryWindow::All)
757                    .expect("iter")
758                    .count(),
759                1
760            );
761            world.remove::<A>(entity).expect("remove");
762            assert_eq!(
763                query
764                    .iter(&mut world, QueryWindow::All)
765                    .expect("iter")
766                    .count(),
767                0
768            );
769        }
770    }
771
772    #[test]
773    fn current_delta_query_skips_prefix_retained_for_lagging_query() {
774        let mut world = world();
775        let entity = world.spawn().expect("spawn");
776        let mut current = world
777            .prepare_query1::<A>(QuerySpec::new(), QueryPolicy::DeltaMembership)
778            .expect("current");
779        let mut lagging = world
780            .prepare_query1::<A>(QuerySpec::new(), QueryPolicy::DeltaMembership)
781            .expect("lagging");
782
783        for value in 0..32 {
784            if value % 2 == 0 {
785                world.insert(entity, A(value)).expect("insert");
786            } else {
787                world.remove::<A>(entity).expect("remove");
788            }
789            let expected = usize::from(value % 2 == 0);
790            assert_eq!(
791                current
792                    .iter(&mut world, QueryWindow::All)
793                    .expect("current refresh")
794                    .count(),
795                expected
796            );
797        }
798
799        // The lagging cursor pins the full log, but both materializations still
800        // converge on the same final membership when it eventually catches up.
801        assert_eq!(world.query_delta_log_len_for_test(), 32);
802        assert_eq!(
803            lagging
804                .iter(&mut world, QueryWindow::All)
805                .expect("lagging refresh")
806                .count(),
807            0
808        );
809        assert_eq!(
810            current
811                .iter(&mut world, QueryWindow::All)
812                .expect("current remains current")
813                .count(),
814            0
815        );
816    }
817
818    #[test]
819    fn delta_sequence_exhaustion_rebases_retained_log_and_cursors() {
820        let mut world = world();
821        let entity = world.spawn().expect("spawn");
822        let plan = world
823            .resolve_query1_plan::<A>(&QuerySpec::new())
824            .expect("plan");
825        let cursor = world.register_query_delta_cursor();
826        world.seed_query_delta_exhaustion_for_test(&cursor, entity, plan.primary_index);
827
828        world.insert(entity, A(1)).expect("insert after rebase");
829        assert_eq!(cursor.get(), 0);
830        assert_eq!(world.query_delta_sequences_for_test(), alloc::vec![0, 1]);
831
832        let mut changed = Vec::new();
833        let mut reverse = Vec::new();
834        world.collect_query_delta_entities(&cursor, &plan, &mut changed, &mut reverse);
835        assert_eq!(changed, alloc::vec![entity]);
836        assert_eq!(cursor.get(), 2);
837    }
838
839    #[test]
840    fn delta_cursor_offsets_clamp_before_and_after_retained_range() {
841        let mut world = world();
842        let entity = world.spawn().expect("spawn");
843        let plan = world
844            .resolve_query1_plan::<A>(&QuerySpec::new())
845            .expect("plan");
846        let cursor = world.register_query_delta_cursor();
847        world.insert(entity, A(1)).expect("insert one");
848        world.remove::<A>(entity).expect("remove one");
849        world.insert(entity, A(2)).expect("insert two");
850
851        cursor.set(2);
852        world.remove::<A>(entity).expect("remove two");
853        assert_eq!(world.query_delta_sequences_for_test(), alloc::vec![2, 3]);
854
855        let mut changed = Vec::new();
856        let mut reverse = Vec::new();
857        cursor.set(0);
858        world.collect_query_delta_entities(&cursor, &plan, &mut changed, &mut reverse);
859        assert_eq!(changed, alloc::vec![entity]);
860        assert_eq!(cursor.get(), 4);
861
862        cursor.set(99);
863        world.collect_query_delta_entities(&cursor, &plan, &mut changed, &mut reverse);
864        assert!(changed.is_empty());
865        assert_eq!(cursor.get(), 4);
866    }
867
868    #[test]
869    fn mixed_mut_read_updates_only_a() {
870        let mut world = world();
871        let entity = world.spawn().expect("spawn");
872        world.insert(entity, A(2)).expect("A");
873        world.insert(entity, B(5)).expect("B");
874        let mut query = world
875            .prepare_query2::<A, B>(QuerySpec::new(), QueryPolicy::Prepared)
876            .expect("prepare");
877        let before_b =
878            world.component_changed_tick(entity, world.component_index::<B>().expect("B index"));
879        query
880            .for_each_mut_read(&mut world, QueryWindow::All, |_, a, b| {
881                a.0 += b.0;
882                Ok(())
883            })
884            .expect("execute");
885        assert_eq!(world.get::<A>(entity).expect("get").expect("A"), &A(7));
886        assert_eq!(world.get::<B>(entity).expect("get").expect("B"), &B(5));
887        assert_eq!(
888            world.component_changed_tick(entity, world.component_index::<B>().expect("B index"),),
889            before_b
890        );
891    }
892
893    #[test]
894    fn query2_exact_ids_preserve_order_independent_of_driver() {
895        let mut world = world();
896        let first = world.spawn().expect("first");
897        let second = world.spawn().expect("second");
898        for (entity, value) in [(first, 1), (second, 2)] {
899            world.insert(entity, A(value)).expect("A");
900            world.insert(entity, B(value)).expect("B");
901        }
902        let spec = QuerySpec::new().exact_ids(
903            alloc::vec![second, first],
904            crate::query::ExactIdPolicy::SkipUnavailable,
905        );
906        let mut query = world
907            .prepare_query2::<A, B>(spec, QueryPolicy::Prepared)
908            .expect("prepare");
909        let ids: Vec<_> = query
910            .iter(&mut world, QueryWindow::All)
911            .expect("iter")
912            .map(|(entity, _, _)| entity)
913            .collect();
914        assert_eq!(ids, alloc::vec![second, first]);
915    }
916
917    #[test]
918    fn query2_reselects_driver_after_cardinality_crossover_with_stable_cursors() {
919        // sparse/sparse
920        {
921            let mut world = world();
922            let first = world.spawn().expect("first");
923            for entity in [
924                first,
925                world.spawn().expect("a2"),
926                world.spawn().expect("a3"),
927            ] {
928                world.insert(entity, A(1)).expect("A");
929            }
930            world.insert(first, C(1)).expect("C");
931            let spec = QuerySpec::new();
932            let mut query = world
933                .prepare_query2::<A, C>(spec.clone(), QueryPolicy::Prepared)
934                .expect("prepare sparse/sparse");
935            let c_index = world.component_index::<C>().expect("C index");
936            assert!(matches!(
937                query.plan.traversal,
938                TraversalSource::Sparse { component_index } if component_index == c_index
939            ));
940            let mut before =
941                QueryCursor::from_spec2_start::<A, C>(&mut world, &spec).expect("before cursor");
942            for _ in 0..3 {
943                let entity = world.spawn().expect("C-only");
944                world.insert(entity, C(2)).expect("C-only insert");
945            }
946            assert_eq!(
947                query
948                    .iter(&mut world, QueryWindow::Cursor(&mut before))
949                    .expect("sparse/sparse before cursor")
950                    .count(),
951                1
952            );
953            let a_index = world.component_index::<A>().expect("A index");
954            assert!(matches!(
955                query.plan.traversal,
956                TraversalSource::Sparse { component_index } if component_index == a_index
957            ));
958            let mut after =
959                QueryCursor::from_spec2_start::<A, C>(&mut world, &spec).expect("after cursor");
960            assert_eq!(
961                query
962                    .iter(&mut world, QueryWindow::Cursor(&mut after))
963                    .expect("sparse/sparse after cursor")
964                    .count(),
965                1
966            );
967        }
968
969        // table/table
970        {
971            let mut world = world();
972            let first = world.spawn().expect("first");
973            for entity in [
974                first,
975                world.spawn().expect("b2"),
976                world.spawn().expect("b3"),
977            ] {
978                world.insert(entity, B(1)).expect("B");
979            }
980            world.insert(first, D(1)).expect("D");
981            let spec = QuerySpec::new();
982            let mut query = world
983                .prepare_query2::<B, D>(spec.clone(), QueryPolicy::Prepared)
984                .expect("prepare table/table");
985            let d_index = world.component_index::<D>().expect("D index");
986            assert!(matches!(
987                query.plan.traversal,
988                TraversalSource::Table { component_index } if component_index == d_index
989            ));
990            let mut before =
991                QueryCursor::from_spec2_start::<B, D>(&mut world, &spec).expect("before cursor");
992            for _ in 0..3 {
993                let entity = world.spawn().expect("D-only");
994                world.insert(entity, D(2)).expect("D-only insert");
995            }
996            assert_eq!(
997                query
998                    .iter(&mut world, QueryWindow::Cursor(&mut before))
999                    .expect("table/table before cursor")
1000                    .count(),
1001                1
1002            );
1003            let b_index = world.component_index::<B>().expect("B index");
1004            assert!(matches!(
1005                query.plan.traversal,
1006                TraversalSource::Table { component_index } if component_index == b_index
1007            ));
1008            let mut after =
1009                QueryCursor::from_spec2_start::<B, D>(&mut world, &spec).expect("after cursor");
1010            assert_eq!(
1011                query
1012                    .iter(&mut world, QueryWindow::Cursor(&mut after))
1013                    .expect("table/table after cursor")
1014                    .count(),
1015                1
1016            );
1017        }
1018
1019        // sparse/table
1020        {
1021            let mut world = world();
1022            let first = world.spawn().expect("first");
1023            for entity in [
1024                first,
1025                world.spawn().expect("a2"),
1026                world.spawn().expect("a3"),
1027            ] {
1028                world.insert(entity, A(1)).expect("A");
1029            }
1030            world.insert(first, B(1)).expect("B");
1031            let spec = QuerySpec::new();
1032            let mut query = world
1033                .prepare_query2::<A, B>(spec.clone(), QueryPolicy::Prepared)
1034                .expect("prepare sparse/table");
1035            let b_index = world.component_index::<B>().expect("B index");
1036            assert!(matches!(
1037                query.plan.traversal,
1038                TraversalSource::Table { component_index } if component_index == b_index
1039            ));
1040            let mut before =
1041                QueryCursor::from_spec2_start::<A, B>(&mut world, &spec).expect("before cursor");
1042            for _ in 0..3 {
1043                let entity = world.spawn().expect("B-only");
1044                world.insert(entity, B(2)).expect("B-only insert");
1045            }
1046            assert_eq!(
1047                query
1048                    .iter(&mut world, QueryWindow::Cursor(&mut before))
1049                    .expect("sparse/table before cursor")
1050                    .count(),
1051                1
1052            );
1053            let a_index = world.component_index::<A>().expect("A index");
1054            assert!(matches!(
1055                query.plan.traversal,
1056                TraversalSource::Sparse { component_index } if component_index == a_index
1057            ));
1058            let mut after =
1059                QueryCursor::from_spec2_start::<A, B>(&mut world, &spec).expect("after cursor");
1060            assert_eq!(
1061                query
1062                    .iter(&mut world, QueryWindow::Cursor(&mut after))
1063                    .expect("sparse/table after cursor")
1064                    .count(),
1065                1
1066            );
1067        }
1068    }
1069
1070    #[test]
1071    fn mixed_mut_read_covers_all_storage_pairs() {
1072        let mut world = world();
1073        let entity = world.spawn().expect("spawn");
1074        world.insert(entity, A(1)).expect("A");
1075        world.insert(entity, B(2)).expect("B");
1076        world.insert(entity, C(3)).expect("C");
1077        world.insert(entity, D(4)).expect("D");
1078
1079        let mut sparse_sparse = world
1080            .prepare_query2::<A, C>(QuerySpec::new(), QueryPolicy::Prepared)
1081            .expect("sparse/sparse");
1082        sparse_sparse
1083            .for_each_mut_read(&mut world, QueryWindow::All, |_, a, c| {
1084                a.0 += c.0;
1085                Ok(())
1086            })
1087            .expect("sparse/sparse execute");
1088
1089        let mut table_sparse = world
1090            .prepare_query2::<D, C>(QuerySpec::new(), QueryPolicy::Prepared)
1091            .expect("table/sparse");
1092        table_sparse
1093            .for_each_mut_read(&mut world, QueryWindow::All, |_, d, c| {
1094                d.0 += c.0;
1095                Ok(())
1096            })
1097            .expect("table/sparse execute");
1098
1099        let mut table_table = world
1100            .prepare_query2::<D, B>(QuerySpec::new(), QueryPolicy::Prepared)
1101            .expect("table/table");
1102        table_table
1103            .for_each_mut_read(&mut world, QueryWindow::All, |_, d, b| {
1104                d.0 += b.0;
1105                Ok(())
1106            })
1107            .expect("table/table execute");
1108
1109        assert_eq!(world.get::<A>(entity).expect("get").expect("A"), &A(4));
1110        assert_eq!(world.get::<B>(entity).expect("get").expect("B"), &B(2));
1111        assert_eq!(world.get::<C>(entity).expect("get").expect("C"), &C(3));
1112        assert_eq!(world.get::<D>(entity).expect("get").expect("D"), &D(9));
1113    }
1114
1115    #[test]
1116    fn cursor_commits_only_after_full_iteration() {
1117        let mut world = world();
1118        let entity = world.spawn().expect("spawn");
1119        world.insert(entity, A(1)).expect("A");
1120        let spec = QuerySpec::new().changed::<A>();
1121        let mut query = world
1122            .prepare_query1::<A>(spec.clone(), QueryPolicy::Prepared)
1123            .expect("prepare");
1124        let mut cursor = QueryCursor::from_spec_start::<A>(&mut world, &spec).expect("cursor");
1125        let before = cursor.since();
1126        {
1127            let mut iter = query
1128                .iter(&mut world, QueryWindow::Cursor(&mut cursor))
1129                .expect("iter");
1130            assert!(iter.next().is_some());
1131        }
1132        assert_eq!(cursor.since(), before);
1133
1134        query
1135            .iter(&mut world, QueryWindow::Cursor(&mut cursor))
1136            .expect("iter")
1137            .for_each(drop);
1138        assert!(cursor.since() > before);
1139    }
1140
1141    #[test]
1142    fn result_policy_rejects_moving_windows() {
1143        let mut world = world();
1144        assert!(matches!(
1145            world.prepare_query1::<A>(QuerySpec::new().changed::<A>(), QueryPolicy::Result,),
1146            Err(QueryError::MovingChangeWindow)
1147        ));
1148    }
1149
1150    #[test]
1151    fn query2_cursor_matches_prepared_fingerprint_and_commits() {
1152        let mut world = world();
1153        let entity = world.spawn().expect("spawn");
1154        world.insert(entity, A(1)).expect("A");
1155        world.insert(entity, B(2)).expect("B");
1156        let spec = QuerySpec::new().changed::<A>();
1157        let mut query = world
1158            .prepare_query2::<A, B>(spec.clone(), QueryPolicy::Prepared)
1159            .expect("prepare");
1160        let mut cursor = QueryCursor::from_spec2_start::<A, B>(&mut world, &spec).expect("cursor");
1161        let before = cursor.since();
1162        assert_eq!(
1163            query
1164                .iter(&mut world, QueryWindow::Cursor(&mut cursor))
1165                .expect("iter")
1166                .count(),
1167            1
1168        );
1169        assert!(cursor.since() > before);
1170
1171        let mut wrong = QueryCursor::from_spec_start::<A>(&mut world, &spec).expect("Q1 cursor");
1172        assert!(matches!(
1173            query.iter(&mut world, QueryWindow::Cursor(&mut wrong)),
1174            Err(QueryError::WrongQuery { .. })
1175        ));
1176    }
1177
1178    #[test]
1179    fn query_window_constructors_resolve_the_requested_window() {
1180        let mut world = world();
1181        let spec = QuerySpec::new();
1182        let plan = world.resolve_query1_plan::<A>(&spec).expect("plan");
1183
1184        let (since, cursor) = QueryWindow::all()
1185            .into_parts(&world, plan.fingerprint)
1186            .expect("all");
1187        assert_eq!(since, ChangeTick::ZERO);
1188        assert!(cursor.is_none());
1189
1190        let requested = ChangeTick::from_raw(17);
1191        let (since, cursor) = QueryWindow::since(requested)
1192            .into_parts(&world, plan.fingerprint)
1193            .expect("since");
1194        assert_eq!(since, requested);
1195        assert!(cursor.is_none());
1196
1197        let mut query_cursor =
1198            QueryCursor::from_spec_start::<A>(&mut world, &spec).expect("cursor");
1199        let expected = query_cursor.since();
1200        let (since, cursor) = QueryWindow::cursor(&mut query_cursor)
1201            .into_parts(&world, plan.fingerprint)
1202            .expect("cursor window");
1203        assert_eq!(since, expected);
1204        assert!(cursor.is_some());
1205    }
1206
1207    #[test]
1208    fn delta_update_keeps_an_existing_matching_entity_in_place() {
1209        let mut world = world();
1210        let entity = world.spawn().expect("spawn");
1211        world.insert(entity, A(1)).expect("A");
1212        let plan = world
1213            .resolve_query1_plan::<A>(&QuerySpec::new())
1214            .expect("plan");
1215        let mut state = delta_state(&mut world, &plan);
1216        let before = state.ids.clone();
1217
1218        update_delta_entity(&mut state, &world, &plan, entity);
1219
1220        assert_eq!(state.ids, before);
1221        assert_eq!(state.reverse[entity.slot() as usize], Some(0));
1222    }
1223
1224    #[test]
1225    fn mutation_executors_commit_query_cursors() {
1226        let mut world = world();
1227        let entity = world.spawn().expect("spawn");
1228        world.insert(entity, A(1)).expect("A");
1229        world.insert(entity, B(2)).expect("B");
1230
1231        let q1_spec = QuerySpec::new().changed::<A>();
1232        let mut query1 = world
1233            .prepare_query1::<A>(q1_spec.clone(), QueryPolicy::Prepared)
1234            .expect("prepare Q1");
1235        let mut cursor1 =
1236            QueryCursor::from_spec_start::<A>(&mut world, &q1_spec).expect("Q1 cursor");
1237        let before1 = cursor1.since();
1238        query1
1239            .for_each_mut(&mut world, QueryWindow::cursor(&mut cursor1), |_, a| {
1240                a.0 += 1;
1241                Ok(())
1242            })
1243            .expect("Q1 mutation");
1244        assert!(cursor1.since() > before1);
1245
1246        let q2_spec = QuerySpec::new().changed::<A>();
1247        let mut query2 = world
1248            .prepare_query2::<A, B>(q2_spec.clone(), QueryPolicy::Prepared)
1249            .expect("prepare Q2");
1250        let mut mut_mut_cursor =
1251            QueryCursor::from_spec2_start::<A, B>(&mut world, &q2_spec).expect("mut/mut cursor");
1252        let mut mut_read_cursor =
1253            QueryCursor::from_spec2_start::<A, B>(&mut world, &q2_spec).expect("mut/read cursor");
1254        let before_mut_mut = mut_mut_cursor.since();
1255        let before_mut_read = mut_read_cursor.since();
1256        query2
1257            .for_each_mut_mut(
1258                &mut world,
1259                QueryWindow::cursor(&mut mut_mut_cursor),
1260                |_, a, b| {
1261                    a.0 += 1;
1262                    b.0 += 1;
1263                    Ok(())
1264                },
1265            )
1266            .expect("Q2 mut/mut");
1267        query2
1268            .for_each_mut_read(
1269                &mut world,
1270                QueryWindow::cursor(&mut mut_read_cursor),
1271                |_, a, b| {
1272                    a.0 += b.0;
1273                    Ok(())
1274                },
1275            )
1276            .expect("Q2 mut/read");
1277        assert!(mut_mut_cursor.since() > before_mut_mut);
1278        assert!(mut_read_cursor.since() > before_mut_read);
1279    }
1280
1281    #[test]
1282    fn mut_read_propagates_callback_errors_without_committing_cursor() {
1283        let mut world = world();
1284        let entity = world.spawn().expect("spawn");
1285        world.insert(entity, A(1)).expect("A");
1286        world.insert(entity, B(2)).expect("B");
1287        let spec = QuerySpec::new().changed::<A>();
1288        let mut query = world
1289            .prepare_query2::<A, B>(spec.clone(), QueryPolicy::Prepared)
1290            .expect("prepare");
1291        let mut cursor = QueryCursor::from_spec2_start::<A, B>(&mut world, &spec).expect("cursor");
1292        let before = cursor.since();
1293
1294        let error = query
1295            .for_each_mut_read(&mut world, QueryWindow::cursor(&mut cursor), |_, _, _| {
1296                Err(QueryError::WrongOwner)
1297            })
1298            .expect_err("callback error");
1299
1300        assert!(matches!(error, QueryError::WrongOwner));
1301        assert_eq!(cursor.since(), before);
1302    }
1303
1304    #[test]
1305    fn prepared_queries_reject_a_foreign_world() {
1306        let mut owner = world();
1307        let mut query1 = owner
1308            .prepare_query1::<A>(QuerySpec::new(), QueryPolicy::Prepared)
1309            .expect("Q1");
1310        let mut query2 = owner
1311            .prepare_query2::<A, B>(QuerySpec::new(), QueryPolicy::Prepared)
1312            .expect("Q2");
1313        let mut foreign = world();
1314
1315        assert!(matches!(
1316            query1.iter(&mut foreign, QueryWindow::All),
1317            Err(QueryError::WrongOwner)
1318        ));
1319        assert!(matches!(
1320            query2.iter(&mut foreign, QueryWindow::All),
1321            Err(QueryError::WrongOwner)
1322        ));
1323    }
1324
1325    #[test]
1326    fn traversal_equality_is_variant_and_payload_sensitive() {
1327        let first = EntityId::from_parts(1, 1);
1328        let second = EntityId::from_parts(2, 1);
1329
1330        assert!(same_traversal(&TraversalSource::All, &TraversalSource::All));
1331        assert!(same_traversal(
1332            &TraversalSource::Sparse { component_index: 3 },
1333            &TraversalSource::Sparse { component_index: 3 }
1334        ));
1335        assert!(!same_traversal(
1336            &TraversalSource::Sparse { component_index: 3 },
1337            &TraversalSource::Sparse { component_index: 4 }
1338        ));
1339        assert!(same_traversal(
1340            &TraversalSource::Table { component_index: 5 },
1341            &TraversalSource::Table { component_index: 5 }
1342        ));
1343        assert!(!same_traversal(
1344            &TraversalSource::Table { component_index: 5 },
1345            &TraversalSource::Table { component_index: 6 }
1346        ));
1347        assert!(same_traversal(
1348            &TraversalSource::Exact {
1349                ids: alloc::vec![first, second]
1350            },
1351            &TraversalSource::Exact {
1352                ids: alloc::vec![first, second]
1353            }
1354        ));
1355        assert!(!same_traversal(
1356            &TraversalSource::Exact {
1357                ids: alloc::vec![first]
1358            },
1359            &TraversalSource::Exact {
1360                ids: alloc::vec![second]
1361            }
1362        ));
1363        assert!(!same_traversal(
1364            &TraversalSource::All,
1365            &TraversalSource::Sparse { component_index: 0 }
1366        ));
1367    }
1368}