#include #define CATCH_CONFIG_MAIN #include #include "../system_scheduler.hpp" #include "../zecsy.hpp" using namespace zecsy; TEST_CASE("Create a single entity and verify its existence") { world w; auto e = w.make_entity(); REQUIRE(w.is_alive(e)); } TEST_CASE("Destroy an entity and ensure it no longer exists in the world") { world w; auto e = w.make_entity(); w.destroy_entity(e); REQUIRE_FALSE(w.is_alive(e)); } TEST_CASE("Entity #0 should be reserved and never used") { world w; auto e = w.make_entity(); REQUIRE(e != 0); REQUIRE_FALSE(w.is_alive(0)); } struct ChoosenOne { }; TEST_CASE("Entity shouldn't have a component that wasn't attached to it") { world w; auto e = w.make_entity(); REQUIRE_FALSE(w.has(e)); } TEST_CASE("Attempt of getting non-owned component should throw") { world w; auto e = w.make_entity(); REQUIRE_THROWS(w.get(e)); } TEST_CASE("Attach a simple component to an entity and verify it is correctly " "associated") { world w; auto e1 = w.make_entity(); w.set(e1, ChoosenOne{}); REQUIRE(w.has(e1)); auto e2 = w.make_entity(); w.set(e2, ChoosenOne{}); REQUIRE(w.has(e2)); } struct Comp { int v = 0; }; TEST_CASE("Retrieve a component from an entity and verify its data matches " "what was set") { world w; auto e = w.make_entity(); w.set(e, Comp()); REQUIRE(w.get(e).v == 0); w.get(e).v = 77; REQUIRE(w.get(e).v == 77); } TEST_CASE( "Remove a component from an entity and verify it is no longer attached") { world w; auto e = w.make_entity(); w.set(e, ChoosenOne{}); REQUIRE_NOTHROW(w.remove(e)); REQUIRE_FALSE(w.has(e)); w.set(e, ChoosenOne{}); REQUIRE_NOTHROW(w.remove(e)); REQUIRE_FALSE(w.has(e)); } TEST_CASE("Addresses of removed components should be reused") { world w; std::vector entities; std::vector addr; const int N = 4; for(int i = 0; i < 2; ++i) { for(int j = 0; j < N; ++j) { entities.emplace_back(w.make_entity()); w.set(entities.back()); } if(addr.empty()) { for(int j = 0; j < N; ++j) { addr.emplace_back(&w.get(entities[j])); } } else { /* * Gotta reverse it because now we reuse ids in LIFO order */ std::reverse(addr.begin(), addr.end()); for(int j = 0; j < N; ++j) { REQUIRE(&w.get(entities[j]) == addr[j]); } } for(auto e: entities) { w.remove(e); } entities.clear(); } } TEST_CASE("Attach multiple components to an entity and verify all are " "correctly stored and retrievable") { world w; auto e = w.make_entity(); w.set(e, ChoosenOne{}, Comp{}); REQUIRE(w.has(e)); w.remove(e); REQUIRE_FALSE(w.has(e)); REQUIRE_FALSE(w.has(e)); REQUIRE_FALSE(w.has(e)); } TEST_CASE("Create a simple system that processes entities with a specific " "component and verify it executes correctly") { struct Component { int value = 0; }; world w; auto e0 = w.make_entity(), e1 = w.make_entity(); w.set(e0); // or e0.set(Component{}) w.set(e1, Component{20}); REQUIRE(w.get(e0).value == 0); REQUIRE(w.get(e1).value == 20); /* * Really wanna deduce it to w.query([](Component&){}), * but I have some troubles with it */ w.query([](entity_id e, Component& c) { c.value++; }); REQUIRE(w.get(e0).value == 1); REQUIRE(w.get(e1).value == 21); } TEST_CASE("Test a systems ability to query and process only entities with a " "specific combination of components") { struct C0 { int value = 0; }; struct C1 { int value = 10; }; world w; auto e0 = w.make_entity(); w.set(e0, C0{}, C1{}); auto e1 = w.make_entity(); w.set(e1, C0{}); auto e2 = w.make_entity(); w.set(e2, C1{}); REQUIRE(w.get(e0).value == 0); REQUIRE(w.get(e0).value == 10); w.query([e0](entity_id e, C0& c0, C1& c1) { REQUIRE(e == e0); c0.value++; c1.value++; }); REQUIRE(w.get(e0).value == 1); REQUIRE(w.get(e0).value == 11); REQUIRE(w.get(e1).value == 0); REQUIRE(w.get(e2).value == 10); REQUIRE_FALSE(w.has(e1)); REQUIRE_FALSE(w.has(e2)); } TEST_CASE("Systems execute at correct frequencies") { world w; system_scheduler scheduler; int fast_count = 0; int slow_count = 0; // Add a fast system (60 Hz) scheduler.add_system(60, [&](float dt) { fast_count++; }); // Add a slow system (1 Hz) scheduler.add_system(1, [&](float dt) { slow_count++; }); // Simulate 2 seconds of updates at 120 FPS for(int i = 0; i < 240; ++i) { scheduler.update(1.0f / 120.0f); } // Verify counts REQUIRE(fast_count == 120); // 60 Hz system should execute 60 times REQUIRE(slow_count == 2); // 1 Hz system should execute 1 time } TEST_CASE("Systems handle zero-frequency gracefully") { world w; system_scheduler scheduler; int zero_count = 0; // Add a zero-frequency system (should never execute) scheduler.add_system(0, [&](float dt) { zero_count++; }); // Simulate 1 second of updates at 60 FPS for(int i = 0; i < 60; ++i) { scheduler.update(1.0f / 60.0f); } // Verify zero-frequency system never executes REQUIRE(zero_count == 0); } TEST_CASE("Systems handle varying update rates") { world w; system_scheduler scheduler; int varying_count = 0; // Add a system with varying frequency (10 Hz) scheduler.add_system(10, [&](float dt) { varying_count++; }); // Simulate 1 second of updates at 30 FPS for(int i = 0; i < 30; ++i) { scheduler.update(1.0f / 30.0f); } // Verify varying-frequency system executes 10 times REQUIRE(varying_count == 10); } TEST_CASE("Systems handle large time steps") { world w; system_scheduler scheduler; int large_step_count = 0; // Add a system (1 Hz) scheduler.add_system(1, [&](float dt) { large_step_count++; }); // Simulate a large time step (2 seconds) scheduler.update(2.0f); // Verify system executes twice (accumulator handles large steps) REQUIRE(large_step_count == 2); } TEST_CASE("Systems handle multiple frequencies") { world w; system_scheduler scheduler; int fast_count = 0; int medium_count = 0; int slow_count = 0; // Add systems with different frequencies scheduler.add_system(60, [&](float dt) { fast_count++; }); scheduler.add_system(30, [&](float dt) { medium_count++; }); scheduler.add_system(1, [&](float dt) { slow_count++; }); // Simulate 1 second of updates at 120 FPS for(int i = 0; i < 120; ++i) { scheduler.update(1.0f / 120.0f); } // Verify counts REQUIRE(fast_count == 60); // 60 Hz system REQUIRE(medium_count == 30); // 30 Hz system REQUIRE(slow_count == 1); // 1 Hz system } TEST_CASE("Systems handle fractional frequencies") { world w; system_scheduler scheduler; int fractional_count = 0; // Add a system with fractional frequency (0.5 Hz) scheduler.add_system(0.5f, [&](float dt) { fractional_count++; }); // Simulate 4 seconds of updates at 60 FPS for(int i = 0; i < 240; ++i) { scheduler.update(1.0f / 60.0f); } // Verify fractional-frequency system executes twice (0.5 Hz = 2 times in 4 // seconds) REQUIRE(fractional_count == 2); } TEST_CASE("Systems handle zero delta time") { world w; system_scheduler scheduler; int zero_dt_count = 0; // Add a system (1 Hz) scheduler.add_system(1, [&](float dt) { zero_dt_count++; }); // Simulate zero delta time scheduler.update(0.0f); // Verify system does not execute REQUIRE(zero_dt_count == 0); } TEST_CASE("Systems handle negative delta time") { world w; system_scheduler scheduler; int count = 0; // Add a system (1 Hz) scheduler.add_system(1, [&](float dt) { count++; }); // Simulate negative delta time scheduler.update(-1.0f); // Verify system does not execute REQUIRE(count == 0); scheduler.update(2.0f); REQUIRE(count == 2); } TEST_CASE("Entity count tracking") { world w; REQUIRE(w.entity_count() == 0); const auto e1 = w.make_entity(); const auto e2 = w.make_entity(); REQUIRE(w.entity_count() == 2); w.destroy_entity(e1); REQUIRE(w.entity_count() == 1); } TEST_CASE("Component counting mechanisms") { struct Health { int value; }; struct Position { float x, y; }; world w; auto e = w.make_entity(); REQUIRE(w.component_count() == 0); REQUIRE(w.total_component_count() == 0); w.set(e); REQUIRE(w.component_count() == 1); REQUIRE(w.total_component_count() == 1); w.set(e); REQUIRE(w.component_count() == 1); REQUIRE(w.total_component_count() == 2); w.remove(e); REQUIRE(w.component_count() == 0); REQUIRE(w.total_component_count() == 1); } TEST_CASE("Archetype signature management") { struct A { }; struct B { }; struct C { }; world w; // Initial state: empty archetype REQUIRE(w.archetype_count() == 0); auto e = w.make_entity(); REQUIRE(w.archetype_count() == 1); // Add first component w.set(e); REQUIRE(w.archetype_count() == 1); } TEST_CASE("Component distribution across archetypes") { struct A { }; struct B { }; world w; // Create 10 entities in different configurations for(int i = 0; i < 5; ++i) { auto e = w.make_entity(); w.set(e); } for(int i = 0; i < 3; ++i) { auto e = w.make_entity(); w.set(e); } for(int i = 0; i < 2; ++i) { auto e = w.make_entity(); w.set(e); } // Verify distribution REQUIRE(w.entity_count() == 10); REQUIRE(w.component_count() == 8); REQUIRE(w.component_count() == 5); REQUIRE(w.archetype_count() == 3); //, , } TEST_CASE("Entity inspection") { struct Transform { float x, y; }; struct Renderable { }; world w; auto e = w.make_entity(); REQUIRE(w.components_in_entity(e) == 0); w.set(e); REQUIRE(w.components_in_entity(e) == 1); w.set(e); REQUIRE(w.components_in_entity(e) == 2); w.remove(e); REQUIRE(w.components_in_entity(e) == 1); }