use crate::archetype::Archetype;
use crate::entity::Entity;
use crate::world::World;
use std::any::TypeId;
use std::marker::PhantomData;
mod fetch;
mod iter;
pub use fetch::{FetchComponent, Mut};
pub use iter::{QueryChunksIter, QueryIter};
mod sealed {
pub trait SealedFetch {}
pub trait SealedQuery {}
pub trait SealedReadOnly {}
}
pub trait WorldQuery: sealed::SealedQuery {
type StaticType: 'static;
type Fetch<'w>: Copy;
type Item<'w>;
type Slice<'w>;
unsafe fn fetch_raw<'w>(world: &'w World, arch: &Archetype, system_tick: u32) -> Option<Self::Fetch<'w>>;
fn check_aliasing(types: &mut Vec<(TypeId, bool)>);
fn matches_archetype(arch: &Archetype) -> bool;
unsafe fn get_item<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32) -> Self::Item<'w>;
unsafe fn filter_row<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32, system_tick: u32) -> bool;
unsafe fn get_slice<'w>(fetch: Self::Fetch<'w>, len: usize) -> Self::Slice<'w>;
fn has_row_filter() -> bool {
false
}
}
pub trait ReadOnlyQuery: WorldQuery + sealed::SealedReadOnly {}
pub struct Query<'w, Q: WorldQuery + ?Sized> {
world: &'w World,
matching_archetypes: Vec<usize>,
_marker: PhantomData<Q>,
}
impl<'w, Q: WorldQuery> Query<'w, Q> {
pub(crate) fn new(world: &'w World) -> Option<Self> {
let mut used_types = Vec::new();
Q::check_aliasing(&mut used_types);
let matching = world
.archetype_index
.matching_archetypes_readonly(Q::matches_archetype);
Some(Self {
world,
matching_archetypes: matching,
_marker: PhantomData,
})
}
pub(crate) fn new_cached(world: &'w mut World) -> Option<Self> {
let mut used_types = Vec::new();
Q::check_aliasing(&mut used_types);
let matching = world
.archetype_index
.matching_archetypes(TypeId::of::<Q::StaticType>(), Q::matches_archetype)
.to_vec();
Some(Self {
world,
matching_archetypes: matching,
_marker: PhantomData,
})
}
fn iter_inner<'a>(&'a self) -> QueryIter<'a, 'w, Q> {
QueryIter {
world: self.world,
archetype_indices: &self.matching_archetypes,
current_arch_idx: 0,
current_row: 0,
current_fetch: None,
_marker: PhantomData,
_marker_w: PhantomData,
}
}
fn iter_chunks_inner<'a>(&'a self) -> QueryChunksIter<'a, 'w, Q> {
assert!(
!Q::has_row_filter(),
"iter_chunks does not support per-row-filtered queries \
(sparse With/Without, Changed, Added, Or) — they need per-row narrowing that \
a contiguous chunk cannot express; use iter()/iter_mut() instead"
);
QueryChunksIter {
world: self.world,
archetype_indices: &self.matching_archetypes,
current_arch_idx: 0,
_marker: PhantomData,
}
}
#[inline]
fn get_inner<'a>(&'a self, entity_id: u32) -> Option<Q::Item<'a>> {
let loc = self.world.entity_location(entity_id);
if !loc.is_valid() {
return None;
}
let arch = &self.world.archetype_index.archetypes[loc.archetype_id as usize];
unsafe {
let fetch = Q::fetch_raw(self.world, arch, self.world.tick)?;
if !Q::filter_row(fetch, loc.row as usize, entity_id, self.world.change_ref_tick) {
return None;
}
Some(Q::get_item(fetch, loc.row as usize, entity_id))
}
}
fn par_inner<F>(&self, func: F)
where
F: Fn((u32, Q::Item<'_>)) + Send + Sync,
{
#[cfg(not(target_arch = "wasm32"))]
use rayon::prelude::*;
#[cfg(target_arch = "wasm32")]
use crate::parallel_compat::*;
#[derive(Copy, Clone)]
struct FetchWrapper<T>(T);
unsafe impl<T> Send for FetchWrapper<T> {}
unsafe impl<T> Sync for FetchWrapper<T> {}
impl<T: Copy> FetchWrapper<T> {
fn get(&self) -> T {
self.0
}
}
let tick = self.world.tick;
let ref_tick = self.world.change_ref_tick;
self.matching_archetypes.par_iter().for_each(|&arch_idx| {
let arch = &self.world.archetype_index.archetypes[arch_idx];
if let Some(fetch) = unsafe { Q::fetch_raw(self.world, arch, tick) } {
let len = arch.len();
let wrapped_fetch = FetchWrapper(fetch);
let entities_ptr = FetchWrapper(arch.entities().as_ptr());
let func_ref = &func;
(0..len)
.into_par_iter()
.with_min_len(512)
.for_each(move |row| unsafe {
let id = *entities_ptr.get().add(row);
if Q::filter_row(wrapped_fetch.get(), row, id, ref_tick) {
let item = Q::get_item(wrapped_fetch.get(), row, id);
func_ref((id, item));
}
});
}
});
}
pub fn iter_mut<'a>(&'a mut self) -> QueryIter<'a, 'w, Q> {
self.iter_inner()
}
pub fn iter_chunks_mut<'a>(&'a mut self) -> QueryChunksIter<'a, 'w, Q> {
self.iter_chunks_inner()
}
#[inline]
pub fn get_mut(&mut self, entity_id: u32) -> Option<Q::Item<'_>> {
self.get_inner(entity_id)
}
#[inline]
pub fn get_mut_entity(&mut self, entity: Entity) -> Option<Q::Item<'_>> {
if !self.world.is_alive(entity) {
return None;
}
self.get_inner(entity.id())
}
pub fn par_for_each_mut<F>(&mut self, func: F)
where
F: Fn((u32, Q::Item<'_>)) + Send + Sync,
{
self.par_inner(func);
}
#[inline]
pub fn entity_count(&self) -> usize {
self.matching_archetypes
.iter()
.map(|&idx| self.world.archetype_index.archetypes[idx].len())
.sum()
}
#[inline]
pub fn len(&self) -> usize {
self.entity_count()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.entity_count() == 0
}
}
impl<'w, Q: ReadOnlyQuery> Query<'w, Q> {
pub fn iter<'a>(&'a self) -> QueryIter<'a, 'w, Q> {
self.iter_inner()
}
pub fn iter_chunks<'a>(&'a self) -> QueryChunksIter<'a, 'w, Q> {
self.iter_chunks_inner()
}
#[inline]
pub fn get(&self, entity_id: u32) -> Option<Q::Item<'_>> {
self.get_inner(entity_id)
}
#[inline]
pub fn get_entity(&self, entity: Entity) -> Option<Q::Item<'_>> {
if !self.world.is_alive(entity) {
return None;
}
self.get_inner(entity.id())
}
#[inline]
pub fn contains(&self, entity_id: u32) -> bool {
self.get_inner(entity_id).is_some()
}
pub fn entities<'a>(&'a self) -> impl Iterator<Item = u32> + 'a {
self.iter_inner().map(|(id, _)| id)
}
pub fn par_for_each<F>(&self, func: F)
where
F: Fn((u32, Q::Item<'_>)) + Send + Sync,
{
self.par_inner(func);
}
}
#[inline]
fn check(tid: TypeId, is_mut: bool, types: &mut Vec<(TypeId, bool)>) {
for &(existing_tid, existing_mut) in types.iter() {
if existing_tid == tid && (existing_mut || is_mut) {
panic!(
"Query aliasing UB detected! Component TypeId {:?} is accessed mutably more than once \
in the same query. This would cause undefined behavior. \
Use separate queries for components of the same type that need independent mutable access.",
tid
);
}
}
types.push((tid, is_mut));
}
#[inline]
fn arch_matches<T: crate::component::Component>(arch: &Archetype, want_present: bool) -> bool {
if T::storage_type() == crate::component::StorageType::SparseSet {
true
} else {
arch.has_component(TypeId::of::<T>()) == want_present
}
}
macro_rules! impl_tick_filter {
($(#[$meta:meta])* $name:ident, $field:ident) => {
$(#[$meta])*
pub struct $name<T>(PhantomData<T>);
impl<T: crate::component::Component> sealed::SealedQuery for $name<T> {}
impl<T: crate::component::Component> sealed::SealedReadOnly for $name<T> {}
impl<T: crate::component::Component> ReadOnlyQuery for $name<T> {}
impl<T: crate::component::Component> WorldQuery for $name<T> {
type StaticType = $name<T>;
type Fetch<'w> = (
*const crate::archetype::ComponentTicks,
Option<*const crate::archetype::sparse_set::ComponentSparseSet>,
);
type Item<'w> = ();
type Slice<'w> = ();
unsafe fn fetch_raw<'w>(world: &'w World, arch: &Archetype, _tick: u32) -> Option<Self::Fetch<'w>> {
if T::storage_type() == crate::component::StorageType::SparseSet {
let set = world.sparse_sets.get(&TypeId::of::<T>())?;
Some((std::ptr::null(), Some(set as *const _)))
} else {
let col = arch.get_column(TypeId::of::<T>())?;
Some((col.ticks_ptr(), None))
}
}
fn check_aliasing(types: &mut Vec<(TypeId, bool)>) {
check(TypeId::of::<T>(), false, types);
}
fn matches_archetype(arch: &Archetype) -> bool {
arch_matches::<T>(arch, true)
}
unsafe fn filter_row<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32, tick: u32) -> bool {
if let Some(set_ptr) = fetch.1 {
(*set_ptr).ticks_for(entity_id).is_some_and(|t| t.$field > tick)
} else {
(*fetch.0.add(row)).$field > tick
}
}
unsafe fn get_item<'w>(_f: Self::Fetch<'w>, _r: usize, _e: u32) -> Self::Item<'w> {}
unsafe fn get_slice<'w>(_f: Self::Fetch<'w>, _l: usize) -> Self::Slice<'w> {}
fn has_row_filter() -> bool {
true }
}
};
}
macro_rules! impl_presence_filter {
($(#[$meta:meta])* $name:ident, $present:expr) => {
$(#[$meta])*
pub struct $name<T>(PhantomData<T>);
impl<T: crate::component::Component> sealed::SealedQuery for $name<T> {}
impl<T: crate::component::Component> sealed::SealedReadOnly for $name<T> {}
impl<T: crate::component::Component> ReadOnlyQuery for $name<T> {}
impl<T: crate::component::Component> WorldQuery for $name<T> {
type StaticType = $name<T>;
type Fetch<'w> = (
bool,
Option<*const crate::archetype::sparse_set::ComponentSparseSet>,
);
type Item<'w> = ();
type Slice<'w> = ();
unsafe fn fetch_raw<'w>(world: &'w World, _arch: &Archetype, _tick: u32) -> Option<Self::Fetch<'w>> {
if T::storage_type() == crate::component::StorageType::SparseSet {
Some((true, world.sparse_sets.get(&TypeId::of::<T>()).map(|s| s as *const _)))
} else {
Some((false, None))
}
}
fn check_aliasing(_types: &mut Vec<(TypeId, bool)>) {}
fn matches_archetype(arch: &Archetype) -> bool {
arch_matches::<T>(arch, $present)
}
unsafe fn filter_row<'w>(fetch: Self::Fetch<'w>, _row: usize, entity_id: u32, _tick: u32) -> bool {
match fetch {
(false, _) => true,
(true, Some(set_ptr)) => (*set_ptr).contains(entity_id) == $present,
(true, None) => !$present, }
}
unsafe fn get_item<'w>(_f: Self::Fetch<'w>, _r: usize, _e: u32) -> Self::Item<'w> {}
unsafe fn get_slice<'w>(_f: Self::Fetch<'w>, _l: usize) -> Self::Slice<'w> {}
fn has_row_filter() -> bool {
T::storage_type() == crate::component::StorageType::SparseSet
}
}
};
}
impl<T0: FetchComponent> sealed::SealedQuery for T0 where T0::Component: crate::component::Component {}
impl<T0: FetchComponent> WorldQuery for T0 where T0::Component: crate::component::Component {
type StaticType = T0::Component;
type Fetch<'w> = T0::Fetch<'w>;
type Item<'w> = T0::Item<'w>;
type Slice<'w> = T0::Slice<'w>;
unsafe fn fetch_raw<'w>(world: &'w World, arch: &Archetype, tick: u32) -> Option<Self::Fetch<'w>> {
T0::fetch_raw(world, arch, tick)
}
fn check_aliasing(types: &mut Vec<(TypeId, bool)>) {
check(TypeId::of::<T0::Component>(), T0::IS_MUT, types);
}
fn matches_archetype(arch: &Archetype) -> bool {
arch_matches::<T0::Component>(arch, true)
}
unsafe fn get_item<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32) -> Self::Item<'w> {
T0::get_item(fetch, row, entity_id)
}
unsafe fn filter_row<'w>(fetch: Self::Fetch<'w>, _row: usize, entity_id: u32, _tick: u32) -> bool {
T0::contains_entity(fetch, entity_id)
}
unsafe fn get_slice<'w>(fetch: Self::Fetch<'w>, len: usize) -> Self::Slice<'w> {
T0::get_slice(fetch, len)
}
}
impl<T: crate::component::Component> sealed::SealedReadOnly for &T {}
impl<T: crate::component::Component> ReadOnlyQuery for &T {}
impl_tick_filter!(
Changed,
changed
);
impl_tick_filter!(
Added,
added
);
macro_rules! impl_query_tuple {
($($t:ident),*) => {
impl<$($t: WorldQuery),*> sealed::SealedQuery for ($($t,)*) {}
impl<$($t: ReadOnlyQuery),*> sealed::SealedReadOnly for ($($t,)*) {}
impl<$($t: ReadOnlyQuery),*> ReadOnlyQuery for ($($t,)*) {}
#[allow(non_snake_case)]
impl<$($t: WorldQuery),*> WorldQuery for ($($t,)*) {
type StaticType = ($($t::StaticType,)*);
type Fetch<'w> = ($($t::Fetch<'w>,)*);
type Item<'w> = ($($t::Item<'w>,)*);
type Slice<'w> = ($($t::Slice<'w>,)*);
unsafe fn fetch_raw<'w>(world: &'w World, arch: &Archetype, tick: u32) -> Option<Self::Fetch<'w>> {
Some(($($t::fetch_raw(world, arch, tick)?,)*))
}
fn check_aliasing(types: &mut Vec<(TypeId, bool)>) {
$($t::check_aliasing(types);)*
}
fn matches_archetype(arch: &Archetype) -> bool {
$($t::matches_archetype(arch) &&)* true
}
unsafe fn get_item<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32) -> Self::Item<'w> {
let ($($t,)*) = fetch;
($($t::get_item($t, row, entity_id),)*)
}
unsafe fn filter_row<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32, tick: u32) -> bool {
let ($($t,)*) = fetch;
$($t::filter_row($t, row, entity_id, tick) &&)* true
}
unsafe fn get_slice<'w>(fetch: Self::Fetch<'w>, len: usize) -> Self::Slice<'w> {
let ($($t,)*) = fetch;
($($t::get_slice($t, len),)*)
}
fn has_row_filter() -> bool {
$($t::has_row_filter() ||)* false
}
}
};
}
impl_query_tuple!(T0, T1);
impl_query_tuple!(T0, T1, T2);
impl_query_tuple!(T0, T1, T2, T3);
impl_query_tuple!(T0, T1, T2, T3, T4);
impl_query_tuple!(T0, T1, T2, T3, T4, T5);
impl_query_tuple!(T0, T1, T2, T3, T4, T5, T6);
impl_query_tuple!(T0, T1, T2, T3, T4, T5, T6, T7);
impl_query_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
impl_query_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
impl_query_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
impl_query_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
impl_presence_filter!(
With,
true
);
impl_presence_filter!(
Without,
false
);
pub struct Or<T1, T2>(PhantomData<(T1, T2)>);
impl<T1: WorldQuery, T2: WorldQuery> sealed::SealedQuery for Or<T1, T2> {}
impl<T1: ReadOnlyQuery, T2: ReadOnlyQuery> sealed::SealedReadOnly for Or<T1, T2> {}
impl<T1: ReadOnlyQuery, T2: ReadOnlyQuery> ReadOnlyQuery for Or<T1, T2> {}
impl<T1: WorldQuery, T2: WorldQuery> WorldQuery for Or<T1, T2> {
type StaticType = Or<T1::StaticType, T2::StaticType>;
type Fetch<'w> = (Option<T1::Fetch<'w>>, Option<T2::Fetch<'w>>);
type Item<'w> = ();
type Slice<'w> = ();
unsafe fn fetch_raw<'w>(world: &'w World, arch: &Archetype, tick: u32) -> Option<Self::Fetch<'w>> {
let f1 = if T1::matches_archetype(arch) {
T1::fetch_raw(world, arch, tick)
} else {
None
};
let f2 = if T2::matches_archetype(arch) {
T2::fetch_raw(world, arch, tick)
} else {
None
};
Some((f1, f2))
}
fn check_aliasing(types: &mut Vec<(TypeId, bool)>) {
T1::check_aliasing(types);
T2::check_aliasing(types);
}
fn matches_archetype(arch: &Archetype) -> bool {
T1::matches_archetype(arch) || T2::matches_archetype(arch)
}
unsafe fn filter_row<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32, tick: u32) -> bool {
let a = fetch
.0
.is_some_and(|f| T1::filter_row(f, row, entity_id, tick));
let b = fetch
.1
.is_some_and(|f| T2::filter_row(f, row, entity_id, tick));
a || b
}
unsafe fn get_item<'w>(_fetch: Self::Fetch<'w>, _row: usize, _entity_id: u32) -> Self::Item<'w> {}
unsafe fn get_slice<'w>(_fetch: Self::Fetch<'w>, _len: usize) -> Self::Slice<'w> {}
fn has_row_filter() -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::impl_component;
#[derive(Debug, Clone, PartialEq)]
struct Position {
x: f32,
y: f32,
}
impl_component!(Position);
#[derive(Debug, Clone, PartialEq)]
struct Velocity {
x: f32,
y: f32,
}
impl_component!(Velocity);
#[test]
#[should_panic(expected = "Query aliasing UB detected")]
fn test_same_type_mut_mut_panics() {
let mut types = Vec::new();
check(TypeId::of::<Position>(), true, &mut types);
check(TypeId::of::<Position>(), true, &mut types);
}
#[test]
#[should_panic(expected = "Query aliasing UB detected")]
fn test_same_type_ref_mut_panics() {
let mut types = Vec::new();
check(TypeId::of::<Position>(), false, &mut types); check(TypeId::of::<Position>(), true, &mut types); }
#[test]
fn test_different_types_mut_mut_ok() {
let mut types = Vec::new();
check(TypeId::of::<Position>(), true, &mut types);
check(TypeId::of::<Velocity>(), true, &mut types);
assert_eq!(types.len(), 2);
}
#[test]
fn test_same_type_ref_ref_ok() {
let mut types = Vec::new();
check(TypeId::of::<Position>(), false, &mut types);
check(TypeId::of::<Position>(), false, &mut types);
assert_eq!(types.len(), 2);
}
#[test]
fn test_query_new_with_valid_types() {
let mut world = crate::World::new();
world.register_component_type::<Position>();
world.register_component_type::<Velocity>();
let e = world.spawn();
world.add_component(e, Position { x: 1.0, y: 2.0 });
world.add_component(e, Velocity { x: 0.0, y: 0.0 });
let q = world.query_mut::<(Mut<Position>, Mut<Velocity>)>();
assert!(q.is_some());
}
#[test]
fn change_detection_is_relative_to_ref_tick() {
let mut world = crate::World::new();
world.register_component_type::<Position>();
let e = world.spawn();
world.add_component(e, Position { x: 1.0, y: 2.0 });
world.begin_change_frame(0);
assert_eq!(world.query::<Changed<Position>>().unwrap().iter().count(), 1);
assert_eq!(world.query::<Added<Position>>().unwrap().iter().count(), 1);
let prev = world.tick;
world.begin_change_frame(prev);
assert_eq!(
world.query::<Changed<Position>>().unwrap().iter().count(),
0,
"değişiklik olmayan frame'de Changed boş olmalı (eski `==` davranışı her şeyi eşliyordu)"
);
{
let mut q = world.query_mut::<Mut<Position>>().unwrap();
for (_id, mut p) in q.iter_mut() {
p.x += 1.0;
}
}
assert_eq!(world.query::<Changed<Position>>().unwrap().iter().count(), 1);
}
#[test]
fn get_entity_rejects_stale_handle_after_despawn_reuse() {
let mut world = crate::World::new();
world.register_component_type::<Position>();
let e1 = world.spawn();
world.add_component(e1, Position { x: 1.0, y: 1.0 });
let stale = e1;
world.despawn(e1);
let e2 = world.spawn();
world.add_component(e2, Position { x: 2.0, y: 2.0 });
assert_eq!(e2.id(), stale.id(), "slot yeniden kullanılmalı (aynı id)");
assert_ne!(e2.generation(), stale.generation(), "generation artmalı");
let q = world.query::<&Position>().unwrap();
assert_eq!(q.get(stale.id()).map(|p| p.x), Some(2.0));
assert!(q.get_entity(stale).is_none(), "stale handle None dönmeli");
assert_eq!(q.get_entity(e2).map(|p| p.x), Some(2.0));
}
#[test]
fn iter_chunks_mut_triggers_change_detection() {
let mut world = crate::World::new();
world.register_component_type::<Position>();
let e = world.spawn();
world.add_component(e, Position { x: 1.0, y: 1.0 });
world.begin_change_frame(world.tick);
assert_eq!(world.query::<Changed<Position>>().unwrap().iter().count(), 0);
{
let mut q = world.query_mut::<Mut<Position>>().unwrap();
for (_ids, slice) in q.iter_chunks_mut() {
for p in slice.iter_mut() {
p.x += 10.0;
}
}
}
assert_eq!(world.query::<Changed<Position>>().unwrap().iter().count(), 1);
assert_eq!(world.query::<&Position>().unwrap().get(e.id()).map(|p| p.x), Some(11.0));
}
#[test]
fn sparse_set_change_detection_tracks_ticks() {
#[derive(Clone, Debug, PartialEq)]
struct SparseComp(i32);
impl crate::component::Component for SparseComp {
fn storage_type() -> crate::component::StorageType {
crate::component::StorageType::SparseSet
}
}
let mut world = crate::World::new();
world.register_component_type::<SparseComp>();
let e = world.spawn();
world.add_component(e, SparseComp(1));
world.begin_change_frame(0);
assert_eq!(world.query::<Added<SparseComp>>().unwrap().iter().count(), 1);
assert_eq!(world.query::<Changed<SparseComp>>().unwrap().iter().count(), 1);
let prev = world.tick;
world.begin_change_frame(prev);
assert_eq!(world.query::<Changed<SparseComp>>().unwrap().iter().count(), 0);
assert_eq!(world.query::<Added<SparseComp>>().unwrap().iter().count(), 0);
{
let mut q = world.query_mut::<Mut<SparseComp>>().unwrap();
for (_id, mut c) in q.iter_mut() {
c.0 += 10;
}
}
assert_eq!(world.query::<Changed<SparseComp>>().unwrap().iter().count(), 1);
assert_eq!(world.query::<&SparseComp>().unwrap().get(e.id()).map(|c| c.0), Some(11));
}
#[test]
fn sparse_query_mixed_presence_narrows_correctly() {
use crate::component::{Component, StorageType};
#[derive(Clone, Debug, PartialEq)]
struct TableC(i32);
impl Component for TableC {}
#[derive(Clone, Debug, PartialEq)]
struct SparseC(i32);
impl Component for SparseC {
fn storage_type() -> StorageType {
StorageType::SparseSet
}
}
let mut world = crate::World::new();
world.register_component_type::<TableC>();
world.register_component_type::<SparseC>();
for i in 0..3 {
let e = world.spawn();
world.add_component(e, TableC(i));
world.add_component(e, SparseC(i * 10));
}
let mut table_only = Vec::new();
for i in 3..5 {
let e = world.spawn();
world.add_component(e, TableC(i));
table_only.push(e);
}
{
let q = world.query::<&SparseC>().unwrap();
let mut vals: Vec<i32> = q.iter().map(|(_id, s)| s.0).collect();
vals.sort();
assert_eq!(vals, vec![0, 10, 20], "sparse query leaked/dropped rows under mixed presence");
}
assert_eq!(
world.query::<(&TableC, &SparseC)>().unwrap().iter().count(),
3,
"table+sparse tuple query miscounted"
);
assert_eq!(
world.query::<(&TableC, With<SparseC>)>().unwrap().iter().count(),
3,
"With<Sparse> miscounted"
);
assert_eq!(
world.query::<(&TableC, Without<SparseC>)>().unwrap().iter().count(),
2,
"Without<Sparse> miscounted"
);
for e in &table_only {
assert!(
world.query::<&SparseC>().unwrap().get(e.id()).is_none(),
"get() returned a sparse component for an entity that lacks it"
);
}
}
}