1use crate::archetype::Archetype;
2use crate::entity::Entity;
3use crate::world::World;
4use std::any::TypeId;
5use std::marker::PhantomData;
6
7mod fetch;
8mod iter;
9
10pub use fetch::{FetchComponent, Mut};
11pub use iter::{QueryChunksIter, QueryIter};
12
13mod sealed {
23 pub trait SealedFetch {}
24 pub trait SealedQuery {}
25 pub trait SealedReadOnly {}
26}
27
28pub trait WorldQuery: sealed::SealedQuery {
33 type StaticType: 'static;
34 type Fetch<'w>: Copy;
35 type Item<'w>;
36 type Slice<'w>;
37
38 unsafe fn fetch_raw<'w>(world: &'w World, arch: &Archetype, system_tick: u32) -> Option<Self::Fetch<'w>>;
41 fn check_aliasing(types: &mut Vec<(TypeId, bool)>);
42 fn matches_archetype(arch: &Archetype) -> bool;
43
44 unsafe fn get_item<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32) -> Self::Item<'w>;
47
48 unsafe fn filter_row<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32, system_tick: u32) -> bool;
51
52 unsafe fn get_slice<'w>(fetch: Self::Fetch<'w>, len: usize) -> Self::Slice<'w>;
55
56 fn has_row_filter() -> bool {
64 false
65 }
66}
67
68pub trait ReadOnlyQuery: WorldQuery + sealed::SealedReadOnly {}
86
87pub struct Query<'w, Q: WorldQuery + ?Sized> {
92 world: &'w World,
93 matching_archetypes: Vec<usize>,
94 _marker: PhantomData<Q>,
95}
96
97impl<'w, Q: WorldQuery> Query<'w, Q> {
98 pub(crate) fn new(world: &'w World) -> Option<Self> {
99 let mut used_types = Vec::new();
100 Q::check_aliasing(&mut used_types);
101 let matching = world
102 .archetype_index
103 .matching_archetypes_readonly(Q::matches_archetype);
104 Some(Self {
105 world,
106 matching_archetypes: matching,
107 _marker: PhantomData,
108 })
109 }
110
111 pub(crate) fn new_cached(world: &'w mut World) -> Option<Self> {
112 let mut used_types = Vec::new();
113 Q::check_aliasing(&mut used_types);
114 let matching = world
115 .archetype_index
116 .matching_archetypes(TypeId::of::<Q::StaticType>(), Q::matches_archetype)
117 .to_vec();
118 Some(Self {
119 world,
120 matching_archetypes: matching,
121 _marker: PhantomData,
122 })
123 }
124
125 fn iter_inner<'a>(&'a self) -> QueryIter<'a, 'w, Q> {
133 QueryIter {
134 world: self.world,
135 archetype_indices: &self.matching_archetypes,
136 current_arch_idx: 0,
137 current_row: 0,
138 current_fetch: None,
139 _marker: PhantomData,
140 _marker_w: PhantomData,
141 }
142 }
143
144 fn iter_chunks_inner<'a>(&'a self) -> QueryChunksIter<'a, 'w, Q> {
145 assert!(
146 !Q::has_row_filter(),
147 "iter_chunks does not support per-row-filtered queries \
148 (sparse With/Without, Changed, Added, Or) — they need per-row narrowing that \
149 a contiguous chunk cannot express; use iter()/iter_mut() instead"
150 );
151 QueryChunksIter {
152 world: self.world,
153 archetype_indices: &self.matching_archetypes,
154 current_arch_idx: 0,
155 _marker: PhantomData,
156 }
157 }
158
159 #[inline]
160 fn get_inner<'a>(&'a self, entity_id: u32) -> Option<Q::Item<'a>> {
161 let loc = self.world.entity_location(entity_id);
162 if !loc.is_valid() {
163 return None;
164 }
165 let arch = &self.world.archetype_index.archetypes[loc.archetype_id as usize];
166 unsafe {
167 let fetch = Q::fetch_raw(self.world, arch, self.world.tick)?;
168 if !Q::filter_row(fetch, loc.row as usize, entity_id, self.world.change_ref_tick) {
169 return None;
170 }
171 Some(Q::get_item(fetch, loc.row as usize, entity_id))
172 }
173 }
174
175 fn par_inner<F>(&self, func: F)
176 where
177 F: Fn((u32, Q::Item<'_>)) + Send + Sync,
178 {
179 #[cfg(not(target_arch = "wasm32"))]
180 use rayon::prelude::*;
181 #[cfg(target_arch = "wasm32")]
182 use crate::parallel_compat::*;
183
184 #[derive(Copy, Clone)]
186 struct FetchWrapper<T>(T);
187 unsafe impl<T> Send for FetchWrapper<T> {}
188 unsafe impl<T> Sync for FetchWrapper<T> {}
189
190 impl<T: Copy> FetchWrapper<T> {
191 fn get(&self) -> T {
192 self.0
193 }
194 }
195
196 let tick = self.world.tick;
197 let ref_tick = self.world.change_ref_tick;
198 self.matching_archetypes.par_iter().for_each(|&arch_idx| {
199 let arch = &self.world.archetype_index.archetypes[arch_idx];
200 if let Some(fetch) = unsafe { Q::fetch_raw(self.world, arch, tick) } {
201 let len = arch.len();
202 let wrapped_fetch = FetchWrapper(fetch);
203 let entities_ptr = FetchWrapper(arch.entities().as_ptr());
204 let func_ref = &func;
205
206 (0..len)
209 .into_par_iter()
210 .with_min_len(512)
211 .for_each(move |row| unsafe {
212 let id = *entities_ptr.get().add(row);
213 if Q::filter_row(wrapped_fetch.get(), row, id, ref_tick) {
214 let item = Q::get_item(wrapped_fetch.get(), row, id);
215 func_ref((id, item));
216 }
217 });
218 }
219 });
220 }
221
222 pub fn iter_mut<'a>(&'a mut self) -> QueryIter<'a, 'w, Q> {
230 self.iter_inner()
231 }
232
233 pub fn iter_chunks_mut<'a>(&'a mut self) -> QueryChunksIter<'a, 'w, Q> {
244 self.iter_chunks_inner()
245 }
246
247 #[inline]
251 pub fn get_mut(&mut self, entity_id: u32) -> Option<Q::Item<'_>> {
252 self.get_inner(entity_id)
253 }
254
255 #[inline]
257 pub fn get_mut_entity(&mut self, entity: Entity) -> Option<Q::Item<'_>> {
258 if !self.world.is_alive(entity) {
259 return None;
260 }
261 self.get_inner(entity.id())
262 }
263
264 pub fn par_for_each_mut<F>(&mut self, func: F)
266 where
267 F: Fn((u32, Q::Item<'_>)) + Send + Sync,
268 {
269 self.par_inner(func);
270 }
271
272 #[inline]
275 pub fn entity_count(&self) -> usize {
276 self.matching_archetypes
277 .iter()
278 .map(|&idx| self.world.archetype_index.archetypes[idx].len())
279 .sum()
280 }
281
282 #[inline]
283 pub fn len(&self) -> usize {
284 self.entity_count()
285 }
286
287 #[inline]
288 pub fn is_empty(&self) -> bool {
289 self.entity_count() == 0
290 }
291}
292
293impl<'w, Q: ReadOnlyQuery> Query<'w, Q> {
297 pub fn iter<'a>(&'a self) -> QueryIter<'a, 'w, Q> {
298 self.iter_inner()
299 }
300
301 pub fn iter_chunks<'a>(&'a self) -> QueryChunksIter<'a, 'w, Q> {
311 self.iter_chunks_inner()
312 }
313
314 #[inline]
319 pub fn get(&self, entity_id: u32) -> Option<Q::Item<'_>> {
320 self.get_inner(entity_id)
321 }
322
323 #[inline]
327 pub fn get_entity(&self, entity: Entity) -> Option<Q::Item<'_>> {
328 if !self.world.is_alive(entity) {
329 return None;
330 }
331 self.get_inner(entity.id())
332 }
333
334 #[inline]
336 pub fn contains(&self, entity_id: u32) -> bool {
337 self.get_inner(entity_id).is_some()
338 }
339
340 pub fn entities<'a>(&'a self) -> impl Iterator<Item = u32> + 'a {
341 self.iter_inner().map(|(id, _)| id)
342 }
343
344 pub fn par_for_each<F>(&self, func: F)
346 where
347 F: Fn((u32, Q::Item<'_>)) + Send + Sync,
348 {
349 self.par_inner(func);
350 }
351}
352
353#[inline]
371fn check(tid: TypeId, is_mut: bool, types: &mut Vec<(TypeId, bool)>) {
372 for &(existing_tid, existing_mut) in types.iter() {
373 if existing_tid == tid && (existing_mut || is_mut) {
374 panic!(
375 "Query aliasing UB detected! Component TypeId {:?} is accessed mutably more than once \
376 in the same query. This would cause undefined behavior. \
377 Use separate queries for components of the same type that need independent mutable access.",
378 tid
379 );
380 }
381 }
382 types.push((tid, is_mut));
383}
384
385#[inline]
392fn arch_matches<T: crate::component::Component>(arch: &Archetype, want_present: bool) -> bool {
393 if T::storage_type() == crate::component::StorageType::SparseSet {
394 true
395 } else {
396 arch.has_component(TypeId::of::<T>()) == want_present
397 }
398}
399
400macro_rules! impl_tick_filter {
405 ($(#[$meta:meta])* $name:ident, $field:ident) => {
406 $(#[$meta])*
407 pub struct $name<T>(PhantomData<T>);
408
409 impl<T: crate::component::Component> sealed::SealedQuery for $name<T> {}
410 impl<T: crate::component::Component> sealed::SealedReadOnly for $name<T> {}
412 impl<T: crate::component::Component> ReadOnlyQuery for $name<T> {}
413 impl<T: crate::component::Component> WorldQuery for $name<T> {
414 type StaticType = $name<T>;
415 type Fetch<'w> = (
417 *const crate::archetype::ComponentTicks,
418 Option<*const crate::archetype::sparse_set::ComponentSparseSet>,
419 );
420 type Item<'w> = ();
421 type Slice<'w> = ();
422
423 unsafe fn fetch_raw<'w>(world: &'w World, arch: &Archetype, _tick: u32) -> Option<Self::Fetch<'w>> {
424 if T::storage_type() == crate::component::StorageType::SparseSet {
425 let set = world.sparse_sets.get(&TypeId::of::<T>())?;
426 Some((std::ptr::null(), Some(set as *const _)))
427 } else {
428 let col = arch.get_column(TypeId::of::<T>())?;
429 Some((col.ticks_ptr(), None))
430 }
431 }
432
433 fn check_aliasing(types: &mut Vec<(TypeId, bool)>) {
434 check(TypeId::of::<T>(), false, types);
438 }
439
440 fn matches_archetype(arch: &Archetype) -> bool {
441 arch_matches::<T>(arch, true)
442 }
443
444 unsafe fn filter_row<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32, tick: u32) -> bool {
445 if let Some(set_ptr) = fetch.1 {
447 (*set_ptr).ticks_for(entity_id).is_some_and(|t| t.$field > tick)
448 } else {
449 (*fetch.0.add(row)).$field > tick
450 }
451 }
452
453 unsafe fn get_item<'w>(_f: Self::Fetch<'w>, _r: usize, _e: u32) -> Self::Item<'w> {}
454 unsafe fn get_slice<'w>(_f: Self::Fetch<'w>, _l: usize) -> Self::Slice<'w> {}
455
456 fn has_row_filter() -> bool {
457 true }
459 }
460 };
461}
462
463macro_rules! impl_presence_filter {
467 ($(#[$meta:meta])* $name:ident, $present:expr) => {
468 $(#[$meta])*
469 pub struct $name<T>(PhantomData<T>);
470
471 impl<T: crate::component::Component> sealed::SealedQuery for $name<T> {}
472 impl<T: crate::component::Component> sealed::SealedReadOnly for $name<T> {}
474 impl<T: crate::component::Component> ReadOnlyQuery for $name<T> {}
475 impl<T: crate::component::Component> WorldQuery for $name<T> {
476 type StaticType = $name<T>;
477 type Fetch<'w> = (
479 bool,
480 Option<*const crate::archetype::sparse_set::ComponentSparseSet>,
481 );
482 type Item<'w> = ();
483 type Slice<'w> = ();
484
485 unsafe fn fetch_raw<'w>(world: &'w World, _arch: &Archetype, _tick: u32) -> Option<Self::Fetch<'w>> {
486 if T::storage_type() == crate::component::StorageType::SparseSet {
487 Some((true, world.sparse_sets.get(&TypeId::of::<T>()).map(|s| s as *const _)))
488 } else {
489 Some((false, None))
490 }
491 }
492
493 fn check_aliasing(_types: &mut Vec<(TypeId, bool)>) {}
494
495 fn matches_archetype(arch: &Archetype) -> bool {
496 arch_matches::<T>(arch, $present)
497 }
498
499 unsafe fn filter_row<'w>(fetch: Self::Fetch<'w>, _row: usize, entity_id: u32, _tick: u32) -> bool {
500 match fetch {
503 (false, _) => true,
504 (true, Some(set_ptr)) => (*set_ptr).contains(entity_id) == $present,
505 (true, None) => !$present, }
507 }
508
509 unsafe fn get_item<'w>(_f: Self::Fetch<'w>, _r: usize, _e: u32) -> Self::Item<'w> {}
510 unsafe fn get_slice<'w>(_f: Self::Fetch<'w>, _l: usize) -> Self::Slice<'w> {}
511
512 fn has_row_filter() -> bool {
513 T::storage_type() == crate::component::StorageType::SparseSet
515 }
516 }
517 };
518}
519
520impl<T0: FetchComponent> sealed::SealedQuery for T0 where T0::Component: crate::component::Component {}
521impl<T0: FetchComponent> WorldQuery for T0 where T0::Component: crate::component::Component {
522 type StaticType = T0::Component;
523 type Fetch<'w> = T0::Fetch<'w>;
524 type Item<'w> = T0::Item<'w>;
525 type Slice<'w> = T0::Slice<'w>;
526
527 unsafe fn fetch_raw<'w>(world: &'w World, arch: &Archetype, tick: u32) -> Option<Self::Fetch<'w>> {
528 T0::fetch_raw(world, arch, tick)
529 }
530 fn check_aliasing(types: &mut Vec<(TypeId, bool)>) {
531 check(TypeId::of::<T0::Component>(), T0::IS_MUT, types);
532 }
533 fn matches_archetype(arch: &Archetype) -> bool {
534 arch_matches::<T0::Component>(arch, true)
535 }
536
537 unsafe fn get_item<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32) -> Self::Item<'w> {
538 T0::get_item(fetch, row, entity_id)
539 }
540
541 unsafe fn filter_row<'w>(fetch: Self::Fetch<'w>, _row: usize, entity_id: u32, _tick: u32) -> bool {
542 T0::contains_entity(fetch, entity_id)
546 }
547
548 unsafe fn get_slice<'w>(fetch: Self::Fetch<'w>, len: usize) -> Self::Slice<'w> {
549 T0::get_slice(fetch, len)
550 }
551}
552
553impl<T: crate::component::Component> sealed::SealedReadOnly for &T {}
556impl<T: crate::component::Component> ReadOnlyQuery for &T {}
557
558impl_tick_filter!(
559 Changed,
562 changed
563);
564
565impl_tick_filter!(
566 Added,
568 added
569);
570
571macro_rules! impl_query_tuple {
572 ($($t:ident),*) => {
573 impl<$($t: WorldQuery),*> sealed::SealedQuery for ($($t,)*) {}
574 impl<$($t: ReadOnlyQuery),*> sealed::SealedReadOnly for ($($t,)*) {}
576 impl<$($t: ReadOnlyQuery),*> ReadOnlyQuery for ($($t,)*) {}
577 #[allow(non_snake_case)]
578 impl<$($t: WorldQuery),*> WorldQuery for ($($t,)*) {
579 type StaticType = ($($t::StaticType,)*);
580 type Fetch<'w> = ($($t::Fetch<'w>,)*);
581 type Item<'w> = ($($t::Item<'w>,)*);
582 type Slice<'w> = ($($t::Slice<'w>,)*);
583
584 unsafe fn fetch_raw<'w>(world: &'w World, arch: &Archetype, tick: u32) -> Option<Self::Fetch<'w>> {
585 Some(($($t::fetch_raw(world, arch, tick)?,)*))
586 }
587 fn check_aliasing(types: &mut Vec<(TypeId, bool)>) {
588 $($t::check_aliasing(types);)*
589 }
590 fn matches_archetype(arch: &Archetype) -> bool {
591 $($t::matches_archetype(arch) &&)* true
592 }
593 unsafe fn get_item<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32) -> Self::Item<'w> {
594 let ($($t,)*) = fetch;
595 ($($t::get_item($t, row, entity_id),)*)
596 }
597 unsafe fn filter_row<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32, tick: u32) -> bool {
598 let ($($t,)*) = fetch;
599 $($t::filter_row($t, row, entity_id, tick) &&)* true
600 }
601 unsafe fn get_slice<'w>(fetch: Self::Fetch<'w>, len: usize) -> Self::Slice<'w> {
602 let ($($t,)*) = fetch;
603 ($($t::get_slice($t, len),)*)
604 }
605 fn has_row_filter() -> bool {
606 $($t::has_row_filter() ||)* false
607 }
608 }
609 };
610}
611
612impl_query_tuple!(T0, T1);
613impl_query_tuple!(T0, T1, T2);
614impl_query_tuple!(T0, T1, T2, T3);
615impl_query_tuple!(T0, T1, T2, T3, T4);
616impl_query_tuple!(T0, T1, T2, T3, T4, T5);
617impl_query_tuple!(T0, T1, T2, T3, T4, T5, T6);
618impl_query_tuple!(T0, T1, T2, T3, T4, T5, T6, T7);
619impl_query_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
620impl_query_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
621impl_query_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
622impl_query_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
623
624impl_presence_filter!(
629 With,
631 true
632);
633
634impl_presence_filter!(
635 Without,
637 false
638);
639
640pub struct Or<T1, T2>(PhantomData<(T1, T2)>);
641
642impl<T1: WorldQuery, T2: WorldQuery> sealed::SealedQuery for Or<T1, T2> {}
643impl<T1: ReadOnlyQuery, T2: ReadOnlyQuery> sealed::SealedReadOnly for Or<T1, T2> {}
645impl<T1: ReadOnlyQuery, T2: ReadOnlyQuery> ReadOnlyQuery for Or<T1, T2> {}
646impl<T1: WorldQuery, T2: WorldQuery> WorldQuery for Or<T1, T2> {
647 type StaticType = Or<T1::StaticType, T2::StaticType>;
648 type Fetch<'w> = (Option<T1::Fetch<'w>>, Option<T2::Fetch<'w>>);
652 type Item<'w> = ();
653 type Slice<'w> = ();
654
655 unsafe fn fetch_raw<'w>(world: &'w World, arch: &Archetype, tick: u32) -> Option<Self::Fetch<'w>> {
656 let f1 = if T1::matches_archetype(arch) {
659 T1::fetch_raw(world, arch, tick)
660 } else {
661 None
662 };
663 let f2 = if T2::matches_archetype(arch) {
664 T2::fetch_raw(world, arch, tick)
665 } else {
666 None
667 };
668 Some((f1, f2))
669 }
670
671 fn check_aliasing(types: &mut Vec<(TypeId, bool)>) {
672 T1::check_aliasing(types);
675 T2::check_aliasing(types);
676 }
677
678 fn matches_archetype(arch: &Archetype) -> bool {
679 T1::matches_archetype(arch) || T2::matches_archetype(arch)
680 }
681
682 unsafe fn filter_row<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32, tick: u32) -> bool {
683 let a = fetch
687 .0
688 .is_some_and(|f| T1::filter_row(f, row, entity_id, tick));
689 let b = fetch
690 .1
691 .is_some_and(|f| T2::filter_row(f, row, entity_id, tick));
692 a || b
693 }
694 unsafe fn get_item<'w>(_fetch: Self::Fetch<'w>, _row: usize, _entity_id: u32) -> Self::Item<'w> {}
695 unsafe fn get_slice<'w>(_fetch: Self::Fetch<'w>, _len: usize) -> Self::Slice<'w> {}
696
697 fn has_row_filter() -> bool {
698 true
699 }
700}
701
702#[cfg(test)]
703mod tests {
704 use super::*;
705 use crate::impl_component;
706
707 #[derive(Debug, Clone, PartialEq)]
708 struct Position {
709 x: f32,
710 y: f32,
711 }
712 impl_component!(Position);
713
714 #[derive(Debug, Clone, PartialEq)]
715 struct Velocity {
716 x: f32,
717 y: f32,
718 }
719 impl_component!(Velocity);
720
721 #[test]
724 #[should_panic(expected = "Query aliasing UB detected")]
725 fn test_same_type_mut_mut_panics() {
726 let mut types = Vec::new();
727 check(TypeId::of::<Position>(), true, &mut types);
729 check(TypeId::of::<Position>(), true, &mut types);
731 }
732
733 #[test]
736 #[should_panic(expected = "Query aliasing UB detected")]
737 fn test_same_type_ref_mut_panics() {
738 let mut types = Vec::new();
739 check(TypeId::of::<Position>(), false, &mut types); check(TypeId::of::<Position>(), true, &mut types); }
742
743 #[test]
745 fn test_different_types_mut_mut_ok() {
746 let mut types = Vec::new();
747 check(TypeId::of::<Position>(), true, &mut types);
748 check(TypeId::of::<Velocity>(), true, &mut types);
749 assert_eq!(types.len(), 2);
750 }
751
752 #[test]
754 fn test_same_type_ref_ref_ok() {
755 let mut types = Vec::new();
756 check(TypeId::of::<Position>(), false, &mut types);
757 check(TypeId::of::<Position>(), false, &mut types);
758 assert_eq!(types.len(), 2);
759 }
760
761 #[test]
763 fn test_query_new_with_valid_types() {
764 let mut world = crate::World::new();
765 world.register_component_type::<Position>();
766 world.register_component_type::<Velocity>();
767 let e = world.spawn();
768 world.add_component(e, Position { x: 1.0, y: 2.0 });
769 world.add_component(e, Velocity { x: 0.0, y: 0.0 });
770
771 let q = world.query_mut::<(Mut<Position>, Mut<Velocity>)>();
773 assert!(q.is_some());
774 }
775
776 #[test]
779 fn change_detection_is_relative_to_ref_tick() {
780 let mut world = crate::World::new();
781 world.register_component_type::<Position>();
782 let e = world.spawn();
783 world.add_component(e, Position { x: 1.0, y: 2.0 });
784
785 world.begin_change_frame(0);
787 assert_eq!(world.query::<Changed<Position>>().unwrap().iter().count(), 1);
788 assert_eq!(world.query::<Added<Position>>().unwrap().iter().count(), 1);
789
790 let prev = world.tick;
792 world.begin_change_frame(prev);
793 assert_eq!(
794 world.query::<Changed<Position>>().unwrap().iter().count(),
795 0,
796 "değişiklik olmayan frame'de Changed boş olmalı (eski `==` davranışı her şeyi eşliyordu)"
797 );
798
799 {
801 let mut q = world.query_mut::<Mut<Position>>().unwrap();
802 for (_id, mut p) in q.iter_mut() {
803 p.x += 1.0;
804 }
805 }
806 assert_eq!(world.query::<Changed<Position>>().unwrap().iter().count(), 1);
807 }
808
809 #[test]
813 fn get_entity_rejects_stale_handle_after_despawn_reuse() {
814 let mut world = crate::World::new();
815 world.register_component_type::<Position>();
816
817 let e1 = world.spawn();
818 world.add_component(e1, Position { x: 1.0, y: 1.0 });
819 let stale = e1;
820
821 world.despawn(e1);
822
823 let e2 = world.spawn();
825 world.add_component(e2, Position { x: 2.0, y: 2.0 });
826 assert_eq!(e2.id(), stale.id(), "slot yeniden kullanılmalı (aynı id)");
827 assert_ne!(e2.generation(), stale.generation(), "generation artmalı");
828
829 let q = world.query::<&Position>().unwrap();
830 assert_eq!(q.get(stale.id()).map(|p| p.x), Some(2.0));
832 assert!(q.get_entity(stale).is_none(), "stale handle None dönmeli");
834 assert_eq!(q.get_entity(e2).map(|p| p.x), Some(2.0));
836 }
837
838 #[test]
841 fn iter_chunks_mut_triggers_change_detection() {
842 let mut world = crate::World::new();
843 world.register_component_type::<Position>();
844 let e = world.spawn();
845 world.add_component(e, Position { x: 1.0, y: 1.0 });
846
847 world.begin_change_frame(world.tick);
849 assert_eq!(world.query::<Changed<Position>>().unwrap().iter().count(), 0);
851
852 {
854 let mut q = world.query_mut::<Mut<Position>>().unwrap();
855 for (_ids, slice) in q.iter_chunks_mut() {
856 for p in slice.iter_mut() {
857 p.x += 10.0;
858 }
859 }
860 }
861
862 assert_eq!(world.query::<Changed<Position>>().unwrap().iter().count(), 1);
864 assert_eq!(world.query::<&Position>().unwrap().get(e.id()).map(|p| p.x), Some(11.0));
865 }
866
867 #[test]
870 fn sparse_set_change_detection_tracks_ticks() {
871 #[derive(Clone, Debug, PartialEq)]
872 struct SparseComp(i32);
873 impl crate::component::Component for SparseComp {
874 fn storage_type() -> crate::component::StorageType {
875 crate::component::StorageType::SparseSet
876 }
877 }
878
879 let mut world = crate::World::new();
880 world.register_component_type::<SparseComp>();
881 let e = world.spawn();
882 world.add_component(e, SparseComp(1));
883
884 world.begin_change_frame(0);
886 assert_eq!(world.query::<Added<SparseComp>>().unwrap().iter().count(), 1);
887 assert_eq!(world.query::<Changed<SparseComp>>().unwrap().iter().count(), 1);
888
889 let prev = world.tick;
891 world.begin_change_frame(prev);
892 assert_eq!(world.query::<Changed<SparseComp>>().unwrap().iter().count(), 0);
893 assert_eq!(world.query::<Added<SparseComp>>().unwrap().iter().count(), 0);
894
895 {
897 let mut q = world.query_mut::<Mut<SparseComp>>().unwrap();
898 for (_id, mut c) in q.iter_mut() {
899 c.0 += 10;
900 }
901 }
902 assert_eq!(world.query::<Changed<SparseComp>>().unwrap().iter().count(), 1);
903 assert_eq!(world.query::<&SparseComp>().unwrap().get(e.id()).map(|c| c.0), Some(11));
904 }
905
906 #[test]
913 fn sparse_query_mixed_presence_narrows_correctly() {
914 use crate::component::{Component, StorageType};
915 #[derive(Clone, Debug, PartialEq)]
916 struct TableC(i32);
917 impl Component for TableC {}
918 #[derive(Clone, Debug, PartialEq)]
919 struct SparseC(i32);
920 impl Component for SparseC {
921 fn storage_type() -> StorageType {
922 StorageType::SparseSet
923 }
924 }
925
926 let mut world = crate::World::new();
927 world.register_component_type::<TableC>();
928 world.register_component_type::<SparseC>();
929
930 for i in 0..3 {
932 let e = world.spawn();
933 world.add_component(e, TableC(i));
934 world.add_component(e, SparseC(i * 10));
935 }
936 let mut table_only = Vec::new();
937 for i in 3..5 {
938 let e = world.spawn();
939 world.add_component(e, TableC(i));
940 table_only.push(e);
941 }
942
943 {
945 let q = world.query::<&SparseC>().unwrap();
946 let mut vals: Vec<i32> = q.iter().map(|(_id, s)| s.0).collect();
947 vals.sort();
948 assert_eq!(vals, vec![0, 10, 20], "sparse query leaked/dropped rows under mixed presence");
949 }
950 assert_eq!(
952 world.query::<(&TableC, &SparseC)>().unwrap().iter().count(),
953 3,
954 "table+sparse tuple query miscounted"
955 );
956 assert_eq!(
958 world.query::<(&TableC, With<SparseC>)>().unwrap().iter().count(),
959 3,
960 "With<Sparse> miscounted"
961 );
962 assert_eq!(
963 world.query::<(&TableC, Without<SparseC>)>().unwrap().iter().count(),
964 2,
965 "Without<Sparse> miscounted"
966 );
967 for e in &table_only {
969 assert!(
970 world.query::<&SparseC>().unwrap().get(e.id()).is_none(),
971 "get() returned a sparse component for an entity that lacks it"
972 );
973 }
974 }
975}