#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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { world w; auto e = w.make_entity(); REQUIRE_FALSE(w.has(e)); } TEST_CASE("Attempt of getting non-owned component should throw", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { 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", "[test]") { struct A { }; struct B { }; struct C { }; world w; // Initial state: empty archetype REQUIRE(w.archetype_count() == 0); auto e0 = w.make_entity(); REQUIRE(w.archetype_count() == 1); //<> // Add first component w.set(e0); REQUIRE(w.archetype_count() == 1); // w.set(e0); REQUIRE(w.archetype_count() == 1); // w.set(e0); REQUIRE(w.archetype_count() == 1); // w.remove(e0); REQUIRE(w.archetype_count() == 1); // auto e1 = w.make_entity(); w.set(e1); REQUIRE(w.archetype_count() == 2); //, w.remove(e0); REQUIRE(w.archetype_count() == 2); //<>, w.set(e0); REQUIRE(w.archetype_count() == 2); //, w.set(e0); REQUIRE(w.archetype_count() == 1); // w.destroy_entity(e0); REQUIRE(w.archetype_count() == 1); // w.destroy_entity(e1); REQUIRE(w.archetype_count() == 0); } TEST_CASE("Component distribution across archetypes", "[test]") { 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", "[test]") { 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); } namespace { // Benchmark components struct Position { float x, y; }; struct Velocity { float dx, dy; }; struct Health { int value; }; struct BigData { char buffer[4096]; }; // Benchmark entity counts constexpr int SMALL = 1'000; constexpr int MEDIUM = 10'000; constexpr int LARGE = 50'000; } // namespace TEST_CASE("Core operations benchmarks", "[benchmark]") { BENCHMARK_ADVANCED("Create entities [1000]")( Catch::Benchmark::Chronometer meter) { meter.measure( [&] { world w; for(int i = 0; i < SMALL; ++i) { w.make_entity(); } return w.entity_count(); }); }; BENCHMARK_ADVANCED("Create entities [10000]")( Catch::Benchmark::Chronometer meter) { meter.measure( [&] { world w; for(int i = 0; i < MEDIUM; ++i) { w.make_entity(); } return w.entity_count(); }); }; BENCHMARK_ADVANCED("Create entities [50000]")( Catch::Benchmark::Chronometer meter) { meter.measure( [&] { world w; for(int i = 0; i < LARGE; ++i) { w.make_entity(); } return w.entity_count(); }); }; BENCHMARK_ADVANCED("Create entities with components [1000]")( Catch::Benchmark::Chronometer meter) { meter.measure( [&] { world w; for(int i = 0; i < SMALL; ++i) { auto e = w.make_entity(); w.set(e, {1.0f, 2.0f}); w.set(e, {3.0f, 4.0f}); } return w.total_component_count(); }); }; BENCHMARK_ADVANCED("Create entities with components [10000]")( Catch::Benchmark::Chronometer meter) { meter.measure( [&] { world w; for(int i = 0; i < MEDIUM; ++i) { auto e = w.make_entity(); w.set(e, {1.0f, 2.0f}); w.set(e, {3.0f, 4.0f}); } return w.total_component_count(); }); }; BENCHMARK_ADVANCED("Create entities with components [50000]")( Catch::Benchmark::Chronometer meter) { meter.measure( [&] { world w; for(int i = 0; i < LARGE; ++i) { auto e = w.make_entity(); w.set(e, {1.0f, 2.0f}); w.set(e, {3.0f, 4.0f}); } return w.total_component_count(); }); }; } TEST_CASE("Component operations benchmarks", "[benchmark]") { BENCHMARK_ADVANCED("Add component to existing entities [1000]")( Catch::Benchmark::Chronometer meter) { world w; std::vector entities; for(int i = 0; i < SMALL; ++i) { entities.push_back(w.make_entity()); } meter.measure( [&] { for(auto e: entities) { w.set(e, {100}); } return w.component_count(); }); }; BENCHMARK_ADVANCED("Add component to existing entities [10000]")( Catch::Benchmark::Chronometer meter) { world w; std::vector entities; for(int i = 0; i < MEDIUM; ++i) { entities.push_back(w.make_entity()); } meter.measure( [&] { for(auto e: entities) { w.set(e, {100}); } return w.component_count(); }); }; BENCHMARK_ADVANCED("Add component to existing entities [50000]")( Catch::Benchmark::Chronometer meter) { world w; std::vector entities; for(int i = 0; i < LARGE; ++i) { entities.push_back(w.make_entity()); } meter.measure( [&] { for(auto e: entities) { w.set(e, {100}); } return w.component_count(); }); }; BENCHMARK_ADVANCED("Remove component from entities [1000]")( Catch::Benchmark::Chronometer meter) { world w; std::vector entities; for(int i = 0; i < SMALL; ++i) { auto e = w.make_entity(); w.set(e, {100}); entities.push_back(e); } meter.measure( [&] { for(auto e: entities) { w.remove(e); } return w.component_count(); }); }; BENCHMARK_ADVANCED("Remove component from entities [10000]")( Catch::Benchmark::Chronometer meter) { world w; std::vector entities; for(int i = 0; i < MEDIUM; ++i) { auto e = w.make_entity(); w.set(e, {100}); entities.push_back(e); } meter.measure( [&] { for(auto e: entities) { w.remove(e); } return w.component_count(); }); }; BENCHMARK_ADVANCED("Remove component from entities [50000]")( Catch::Benchmark::Chronometer meter) { world w; std::vector entities; for(int i = 0; i < LARGE; ++i) { auto e = w.make_entity(); w.set(e, {100}); entities.push_back(e); } meter.measure( [&] { for(auto e: entities) { w.remove(e); } return w.component_count(); }); }; } TEST_CASE("Query performance benchmarks", "[benchmark]") { BENCHMARK_ADVANCED("Dense query (90% match)")( Catch::Benchmark::Chronometer meter) { world w; for(int i = 0; i < LARGE; ++i) { auto e = w.make_entity(); w.set(e); if(i % 10 != 0) w.set(e); // 90% match } meter.measure( [&] { int count = 0; w.query([&](auto...) { count++; }); return count; }); }; BENCHMARK_ADVANCED("Sparse query (10% match)")( Catch::Benchmark::Chronometer meter) { world w; for(int i = 0; i < LARGE; ++i) { auto e = w.make_entity(); w.set(e); if(i % 10 == 0) w.set(e); // 10% match } meter.measure( [&] { int count = 0; w.query([&](auto...) { count++; }); return count; }); }; } TEST_CASE("Memory intensive benchmarks", "[benchmark]") { BENCHMARK_ADVANCED("Large component allocation")( Catch::Benchmark::Chronometer meter) { meter.measure( [&] { world w; for(int i = 0; i < SMALL; ++i) { auto e = w.make_entity(); w.set(e); } return w.total_component_count(); }); }; BENCHMARK_ADVANCED("Component memory reuse")( Catch::Benchmark::Chronometer meter) { world w; std::vector entities; for(int i = 0; i < SMALL; ++i) { auto e = w.make_entity(); w.set(e); entities.push_back(e); } meter.measure( [&] { // Remove and re-add components for(auto e: entities) { w.remove(e); w.set(e); } return w.component_count(); }); }; } TEST_CASE("Archetype transition benchmarks", "[benchmark]") { BENCHMARK_ADVANCED("Single component addition")( Catch::Benchmark::Chronometer meter) { world w; std::vector entities; for(int i = 0; i < MEDIUM; ++i) { entities.push_back(w.make_entity()); } meter.measure( [&] { for(auto e: entities) { w.set(e); } return w.archetype_count(); }); }; BENCHMARK_ADVANCED("Multi-component transition")( Catch::Benchmark::Chronometer meter) { world w; std::vector entities; for(int i = 0; i < MEDIUM; ++i) { auto e = w.make_entity(); w.set(e); entities.push_back(e); } meter.measure( [&] { for(auto e: entities) { w.set(e); w.set(e); } return w.archetype_count(); }); }; }