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"
);
}
}
#[test]
fn get_honours_table_with_without_like_iter() {
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 });
let q = world.query::<(&Position, With<Velocity>)>().unwrap();
assert_eq!(q.iter().count(), 0, "iter should skip the Velocity-less entity");
assert!(q.get(e.id()).is_none(), "get() must honour With<Velocity>");
assert!(q.get_entity(e).is_none(), "get_entity() must honour With<Velocity>");
assert!(!q.contains(e.id()), "contains() must honour With<Velocity>");
let q2 = world.query::<(&Position, Without<Velocity>)>().unwrap();
assert_eq!(q2.iter().count(), 1, "iter should include the Velocity-less entity");
assert!(q2.get(e.id()).is_some(), "get() must include under Without<Velocity>");
assert!(q2.contains(e.id()), "contains() must include under Without<Velocity>");
}