#[cfg(test)]
mod tests {
#![allow(dead_code)]
#![allow(clippy::module_inception)]
use crate::{
CommandBuffer, Executor, Query, QueryState, Schedule, System, SystemAccess, World,
};
use crate::{EcsError, Event, Result};
use std::any::{Any, TypeId};
#[test]
fn test_basic_spawn_despawn() -> Result<()> {
let mut world = World::new();
#[allow(dead_code)]
#[derive(Debug)]
struct Position {
x: f32,
y: f32,
}
let entity = world.spawn_entity((Position { x: 1.0, y: 2.0 },));
assert!(world.get_entity_location(entity).is_some());
world.despawn(entity)?;
world.flush_removals()?; assert!(world.get_entity_location(entity).is_none());
Ok(())
}
#[test]
fn test_spawn_despawn_errors() -> Result<()> {
let mut world = World::new();
#[derive(Debug)]
struct Position {
x: f32,
y: f32,
}
let entity = world.spawn_entity((Position { x: 1.0, y: 2.0 },));
world.despawn(entity)?;
world.flush_removals()?;
assert!(world.despawn(entity).is_err());
Ok(())
}
#[test]
fn test_archetype_segregation() {
let mut world = World::new();
#[allow(dead_code)]
struct A;
#[allow(dead_code)]
struct B;
#[allow(dead_code)]
struct C;
world.spawn_entity((A, B));
world.spawn_entity((A, C));
world.spawn_entity((B, C));
world.spawn_entity((A, B, C));
assert!(world.archetype_count() >= 4);
}
#[test]
fn test_entity_location_tracking() {
let mut world = World::new();
#[allow(dead_code)]
struct Comp;
let entity = world.spawn_entity((Comp,));
let location = world.get_entity_location(entity).unwrap();
assert_eq!(location.archetype_id, 1); assert_eq!(location.archetype_row, 0); }
#[test]
fn test_entity_exists() -> Result<()> {
let mut world = World::new();
struct Comp;
let entity = world.spawn_entity((Comp,));
assert!(world.entity_exists(entity));
world.despawn(entity)?;
world.flush_removals()?; assert!(!world.entity_exists(entity));
Ok(())
}
#[test]
fn test_multiple_components() {
let mut world = World::new();
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
struct Position {
x: f32,
y: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
struct Velocity {
x: f32,
y: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
struct Health(u32);
let e1 = world.spawn_entity((
Position { x: 0.0, y: 0.0 },
Velocity { x: 1.0, y: 1.0 },
Health(100),
));
assert!(world.entity_exists(e1));
let e2 = world.spawn_entity((Position { x: 5.0, y: 5.0 }, Health(50)));
assert!(world.entity_exists(e2));
assert_eq!(world.archetype_count(), 3); }
#[test]
fn test_entity_count() -> Result<()> {
let mut world = World::new();
struct Comp;
assert_eq!(world.entity_count(), 0);
let entities: Vec<_> = (0..10).map(|_| world.spawn_entity((Comp,))).collect();
assert_eq!(world.entity_count(), 10);
for entity in entities {
world.despawn(entity)?;
}
world.flush_removals()?; assert_eq!(world.entity_count(), 0);
Ok(())
}
#[test]
fn test_recycled_entity_count() -> Result<()> {
let mut world = World::new();
struct Comp;
let e1 = world.spawn_entity((Comp,));
assert_eq!(world.recycled_entity_count(), 0);
world.despawn(e1)?;
world.flush_removals()?; assert_eq!(world.recycled_entity_count(), 1);
let e2 = world.spawn_entity((Comp,));
assert_eq!(world.recycled_entity_count(), 0);
assert!(world.entity_exists(e2));
Ok(())
}
#[test]
fn test_world_clear() {
let mut world = World::new();
struct Comp;
for _ in 0..100 {
let _ = world.spawn_entity((Comp,));
}
assert_eq!(world.entity_count(), 100);
world.clear();
assert_eq!(world.entity_count(), 0);
assert_eq!(world.archetype_count(), 1); }
#[test]
fn test_memory_stats() {
let mut world = World::new();
struct Comp;
for _ in 0..1000 {
let _ = world.spawn_entity((Comp,));
}
let stats = world.memory_stats();
assert!(stats.total_memory > 0);
assert!(stats.entity_index_memory > 0);
}
#[test]
fn test_query_state_creation() {
let mut world = World::new();
#[derive(Debug)]
struct Position {
x: f32,
}
#[allow(dead_code)]
#[derive(Debug)]
struct Velocity {
x: f32,
}
for i in 0..10 {
let _ = world.spawn_entity((Position { x: i as f32 }, Velocity { x: 1.0 }));
}
let state = QueryState::<(&Position, &Velocity)>::new(&world);
assert!(state.match_count() > 0);
}
#[test]
fn test_query_count() {
let mut world = World::new();
struct Comp;
for _ in 0..10 {
let _ = world.spawn_entity((Comp,));
}
let query = Query::<&Comp>::new(&world);
assert_eq!(query.count(), 10);
}
#[test]
fn test_command_buffer_capacity() {
let buffer = CommandBuffer::with_capacity(100);
assert!(buffer.is_empty());
}
#[test]
fn test_large_scale_spawn() {
let mut world = World::new();
#[derive(Debug)]
struct Position {
x: f32,
}
for i in 0..10_000 {
let _ = world.spawn_entity((Position { x: i as f32 },));
}
assert_eq!(world.entity_count(), 10_000);
}
#[test]
fn test_mixed_spawn_patterns() {
let mut world = World::new();
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
struct A(i32);
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
struct B(i32);
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
struct C(i32);
for i in 0..100 {
let _ = world.spawn_entity((A(i), B(i)));
let _ = world.spawn_entity((A(i), C(i)));
let _ = world.spawn_entity((B(i), C(i)));
let _ = world.spawn_entity((A(i), B(i), C(i)));
let _ = world.spawn_entity((A(i),));
}
assert!(world.archetype_count() >= 5);
}
#[test]
fn test_get_component_api() {
let mut world = World::new();
#[derive(Debug, PartialEq)]
struct Position {
x: f32,
y: f32,
}
let entity = world.spawn_entity((Position { x: 1.0, y: 2.0 },));
let pos = world
.get_component::<Position>(entity)
.expect("component should exist");
assert_eq!(pos.x, 1.0);
assert_eq!(pos.y, 2.0);
}
#[test]
fn test_get_components_tuple_api() {
let mut world = World::new();
#[derive(Debug, PartialEq)]
struct Position {
x: f32,
}
#[derive(Debug, PartialEq)]
struct Velocity {
x: f32,
}
let entity = world.spawn_entity((Position { x: 3.0 }, Velocity { x: 4.0 }));
let (pos, vel) = world
.get_components::<(&Position, &Velocity)>(entity)
.expect("components should exist");
assert_eq!(pos.x, 3.0);
assert_eq!(vel.x, 4.0);
}
#[test]
fn test_get_component_mut_api() {
let mut world = World::new();
#[derive(Debug, PartialEq)]
struct Position {
x: f32,
}
let entity = world.spawn_entity((Position { x: 0.0 },));
{
let pos = world
.get_component_mut::<Position>(entity)
.expect("component should exist");
pos.x = 42.0;
}
let pos = world
.get_component::<Position>(entity)
.expect("component should exist");
assert_eq!(pos.x, 42.0);
}
#[test]
fn test_get_components_mut_tuple_api() {
let mut world = World::new();
#[derive(Debug, PartialEq)]
struct Position(f32);
#[derive(Debug, PartialEq)]
struct Velocity(f32);
let entity = world.spawn_entity((Position(1.0), Velocity(2.0)));
{
let (pos, vel) = world
.get_components_mut::<(&mut Position, &mut Velocity)>(entity)
.expect("components should exist");
pos.0 += 1.0;
vel.0 += 2.0;
}
let (pos, vel) = world
.get_components::<(&Position, &Velocity)>(entity)
.expect("components should exist");
assert_eq!(pos.0, 2.0);
assert_eq!(vel.0, 4.0);
}
#[test]
fn test_query_mut_iteration() {
let mut world = World::new();
#[derive(Debug, PartialEq)]
struct Position(f32);
#[derive(Debug, PartialEq)]
struct Velocity(f32);
for i in 0..10 {
let _ = world.spawn_entity((Position(i as f32), Velocity(1.0)));
}
{
let mut query = world.query_mut::<(&mut Position, &mut Velocity)>();
for (pos, vel) in query.iter() {
pos.0 += vel.0;
vel.0 += 1.0;
}
}
let query = Query::<(&Position, &Velocity)>::new(&world);
assert!(query.count() > 0);
for (pos, vel) in query.iter() {
assert!(pos.0 >= 1.0);
assert!(vel.0 >= 2.0);
}
}
#[derive(Debug, Default, Clone)]
struct LogComponent {
entries: Vec<&'static str>,
}
struct LoggingSystem {
name: &'static str,
}
impl System for LoggingSystem {
fn accesses(&self) -> SystemAccess {
let mut access = SystemAccess::empty();
access
.writes
.push(crate::system::ComponentId::of::<LogComponent>());
access
}
fn name(&self) -> &'static str {
self.name
}
fn run(
&mut self,
world: &mut World,
_commands: &mut crate::command::CommandBuffer,
) -> Result<()> {
let mut query = world.query_mut::<&mut LogComponent>();
for log in query.iter() {
log.entries.push(self.name);
}
Ok(())
}
}
struct FailingSystem;
impl System for FailingSystem {
fn accesses(&self) -> SystemAccess {
SystemAccess::empty()
}
fn name(&self) -> &'static str {
"failing_system"
}
fn run(
&mut self,
_world: &mut World,
_commands: &mut crate::command::CommandBuffer,
) -> Result<()> {
Err(EcsError::ScheduleError("intentional failure".into()))
}
}
#[test]
fn test_executor_runs_systems_in_order() {
let mut world = World::new();
let entity = world.spawn_entity((LogComponent::default(),));
let mut schedule = Schedule::new()
.with_system(Box::new(LoggingSystem { name: "first" }))
.with_system(Box::new(LoggingSystem { name: "second" }))
.build()
.expect("build schedule");
let mut executor = Executor::new(&mut schedule);
executor
.execute_frame(&mut world)
.expect("executor should run");
let log = world
.get_component::<LogComponent>(entity)
.expect("log component exists");
assert_eq!(log.entries, vec!["first", "second"]);
let profile = executor
.last_profile
.as_ref()
.expect("profiling data available");
assert_eq!(profile.system_timings.len(), 2);
assert_eq!(profile.system_timings[0].name, "first");
}
#[test]
fn test_executor_propagates_errors_and_stops() {
let mut world = World::new();
let entity = world.spawn_entity((LogComponent::default(),));
let mut schedule = Schedule::new()
.with_system(Box::new(LoggingSystem { name: "first" }))
.with_system(Box::new(FailingSystem))
.with_system(Box::new(LoggingSystem { name: "second" }))
.build()
.expect("build schedule");
let mut executor = Executor::new(&mut schedule);
let result = executor.execute_frame(&mut world);
assert!(result.is_err(), "executor should propagate system error");
let log = world
.get_component::<LogComponent>(entity)
.expect("log component exists");
assert_eq!(log.entries, vec!["first"]);
}
#[test]
fn test_get_or_insert_with() {
let mut world = World::new();
#[derive(Debug, PartialEq)]
struct Time {
delta: f32,
}
let time1 = world.get_or_insert_with(|| Time { delta: 0.016 });
assert_eq!(time1.delta, 0.016);
{
let time2 = world.get_or_insert_with(|| Time { delta: 0.999 });
assert_eq!(time2.delta, 0.016); }
}
#[test]
fn test_init_resource() {
let mut world = World::new();
#[derive(Debug, PartialEq)]
struct Config {
value: u32,
}
world.init_resource(Config { value: 42 }).unwrap();
let result = world.init_resource(Config { value: 99 });
assert!(matches!(result, Err(EcsError::ResourceAlreadyExists(_))));
}
#[test]
fn test_spawn_batch_updates_component_tracker() {
let mut world = World::new();
let entities = world
.spawn_batch(vec![
((0.0f32, 0.0f32, 0.0f32), (1.0f32, 0.0f32, 0.0f32)),
((1.0f32, 0.0f32, 0.0f32), (0.0f32, 1.0f32, 0.0f32)),
])
.unwrap();
for entity in entities {
let tracked = world.component_tracker.get(&entity);
assert!(tracked.is_some(), "Entity should be in component tracker");
let tracked_set = tracked.unwrap();
println!(
"Entity {:?} tracks {} components: {:?}",
entity,
tracked_set.len(),
tracked_set
);
assert_eq!(
tracked_set.len(),
1,
"Should track 1 component type (both tuples are same type)"
);
}
}
#[test]
fn test_add_stage() {
let mut schedule = Schedule::new();
schedule.add_stage("Update").unwrap();
assert_eq!(schedule.stages.len(), 1);
assert_eq!(schedule.stages[0].name, "Update");
}
#[test]
fn test_stage_dependencies() {
let mut schedule = Schedule::new();
schedule.add_stage("Extract").unwrap();
schedule.add_stage("Prepare").unwrap();
schedule.add_stage_dependency("Prepare", "Extract").unwrap();
assert_eq!(schedule.stages[1].depends_on, vec!["Extract"]);
}
#[test]
fn test_circular_dependency_detection() {
let mut schedule = Schedule::new();
schedule.add_stage("A").unwrap();
schedule.add_stage("B").unwrap();
schedule.add_stage_dependency("A", "B").unwrap();
schedule.add_stage_dependency("B", "A").unwrap();
assert!(schedule.validate_stages().is_err());
}
#[test]
fn test_add_system_to_stage() {
let mut schedule = Schedule::new();
schedule.add_stage("Update").unwrap();
struct TestSystem;
impl System for TestSystem {
fn name(&self) -> &'static str {
"test"
}
fn accesses(&self) -> SystemAccess {
SystemAccess::new()
}
fn run(
&mut self,
_world: &mut World,
_commands: &mut crate::command::CommandBuffer,
) -> Result<()> {
Ok(())
}
}
schedule
.add_system_to_stage("Update", Box::new(TestSystem))
.unwrap();
assert_eq!(schedule.stages[0].systems.len(), 1);
assert_eq!(schedule.systems.len(), 1);
}
#[test]
fn test_define_event_macro() {
crate::define_event! {
pub struct TestEvent {
value: u32,
name: String,
}
}
let event = TestEvent {
value: 42,
name: "test".to_string(),
};
assert_eq!(event.event_name(), "TestEvent");
assert_eq!(event.event_type_id(), std::any::TypeId::of::<TestEvent>());
}
#[test]
fn test_define_event_unit_struct() {
crate::define_event! {
pub struct UnitEvent;
}
let event = UnitEvent;
assert_eq!(event.event_name(), "UnitEvent");
}
#[test]
fn test_define_event_with_doc_comment() {
crate::define_event! {
pub struct DocumentedEvent {
data: i32,
}
}
let event = DocumentedEvent { data: 100 };
assert_eq!(event.event_name(), "DocumentedEvent");
}
#[test]
fn test_simd_chunks() {
use crate::QueryState;
#[derive(Debug, Copy, Clone)]
struct Position {
x: f32,
y: f32,
z: f32,
}
let mut world = World::new();
for _ in 0..100 {
world.spawn_entity((Position {
x: 0.0,
y: 0.0,
z: 0.0,
},));
}
let mut query = QueryState::<&mut Position>::new(&world);
let chunks = query.iter_simd_chunks::<Position>(&mut world);
assert!(!chunks.is_empty());
println!("Found {} chunks", chunks.len());
}
}