#include #include #include #include #include #include #include #include #include #include #include "Common.h" #include "Systems.h" using namespace engine; // Cache optimized system for testing namespace engine { template class CacheOptimizedSystem { public: using UpdateFunction = std::function; CacheOptimizedSystem(EntityManager* entityManager, UpdateFunction updateFunction) : m_EntityManager(entityManager), m_UpdateFunction(updateFunction) {} void Update(float deltaTime) { auto componentArrays = GetComponentArrays(); size_t minSize = GetMinArraySize(componentArrays); if (minSize == 0) return; auto rawArrays = GetRawArrays(componentArrays); m_UpdateFunction(deltaTime, minSize, std::get(rawArrays)...); } private: EntityManager* m_EntityManager; UpdateFunction m_UpdateFunction; std::tuple*...> GetComponentArrays() { return std::make_tuple(m_EntityManager->GetComponentArray()...); } template size_t GetMinArraySizeImpl(const std::tuple*...>& arrays, std::index_sequence) { std::array sizes = {std::get(arrays)->GetSize()...}; return *std::min_element(sizes.begin(), sizes.end()); } size_t GetMinArraySize(const std::tuple*...>& arrays) { return GetMinArraySizeImpl(arrays, std::make_index_sequence{}); } std::tuple GetRawArrays(const std::tuple*...>& arrays) { return GetRawArraysImpl(arrays, std::make_index_sequence{}); } template std::tuple GetRawArraysImpl( const std::tuple*...>& arrays, std::index_sequence) { return std::make_tuple(std::get(arrays)->GetArray()...); } }; // SIMD optimized movement system for testing class SIMDOptimizedMovementSystem { public: SIMDOptimizedMovementSystem(EntityManager* entityManager) : m_EntityManager(entityManager) {} void Update(float deltaTime) { auto* positionArray = m_EntityManager->GetComponentArray(); auto* velocityArray = m_EntityManager->GetComponentArray(); if (!positionArray || !velocityArray) return; size_t size = std::min(positionArray->GetSize(), velocityArray->GetSize()); PositionComponent* positions = positionArray->GetArray(); VelocityComponent* velocities = velocityArray->GetArray(); // Simple SIMD-like optimization (manual loop unrolling) size_t i = 0; for (; i + 3 < size; i += 4) { // Process 4 components at once positions[i].x += velocities[i].vx * deltaTime; positions[i].y += velocities[i].vy * deltaTime; positions[i].z += velocities[i].vz * deltaTime; velocities[i].vx *= 0.95f; velocities[i].vy *= 0.95f; velocities[i].vz *= 0.95f; positions[i+1].x += velocities[i+1].vx * deltaTime; positions[i+1].y += velocities[i+1].vy * deltaTime; positions[i+1].z += velocities[i+1].vz * deltaTime; velocities[i+1].vx *= 0.95f; velocities[i+1].vy *= 0.95f; velocities[i+1].vz *= 0.95f; positions[i+2].x += velocities[i+2].vx * deltaTime; positions[i+2].y += velocities[i+2].vy * deltaTime; positions[i+2].z += velocities[i+2].vz * deltaTime; velocities[i+2].vx *= 0.95f; velocities[i+2].vy *= 0.95f; velocities[i+2].vz *= 0.95f; positions[i+3].x += velocities[i+3].vx * deltaTime; positions[i+3].y += velocities[i+3].vy * deltaTime; positions[i+3].z += velocities[i+3].vz * deltaTime; velocities[i+3].vx *= 0.95f; velocities[i+3].vy *= 0.95f; velocities[i+3].vz *= 0.95f; } // Handle remaining components for (; i < size; ++i) { positions[i].x += velocities[i].vx * deltaTime; positions[i].y += velocities[i].vy * deltaTime; positions[i].z += velocities[i].vz * deltaTime; velocities[i].vx *= 0.95f; velocities[i].vy *= 0.95f; velocities[i].vz *= 0.95f; } } private: EntityManager* m_EntityManager; }; } // ECS test class class ECSTest : public ::testing::Test { public: void SetUp() override { // Setup before each test systemManager = std::make_unique(); auto& entityManager = systemManager->GetEntityManager(); // Register all component types entityManager.RegisterComponent(); entityManager.RegisterComponent(); entityManager.RegisterComponent(); entityManager.RegisterComponent(); entityManager.RegisterComponent(); entityManager.RegisterComponent(); entityManager.RegisterComponent(); entityManager.RegisterComponent(); entityManager.RegisterComponent(); entityManager.RegisterComponent(); entityManager.RegisterComponent(); } void TearDown() override { // Cleanup after each test systemManager.reset(); } std::unique_ptr systemManager; }; // Basic ECS constructor test TEST_F(ECSTest, Constructor) { EXPECT_NO_THROW({ SystemManager sm; }); } // Entity creation test TEST_F(ECSTest, CreateEntity) { auto& entityManager = systemManager->GetEntityManager(); EntityID entity1 = entityManager.CreateEntity(); EntityID entity2 = entityManager.CreateEntity(); EXPECT_NE(entity1, entity2); EXPECT_GT(entity2, entity1); } // Component addition test TEST_F(ECSTest, AddComponent) { auto& entityManager = systemManager->GetEntityManager(); EntityID entity = entityManager.CreateEntity(); PositionComponent pos{10.0f, 20.0f, 30.0f}; entityManager.AddComponent(entity, pos); EXPECT_TRUE(entityManager.HasComponent(entity)); auto& retrievedPos = entityManager.GetComponent(entity); EXPECT_FLOAT_EQ(retrievedPos.x, 10.0f); EXPECT_FLOAT_EQ(retrievedPos.y, 20.0f); EXPECT_FLOAT_EQ(retrievedPos.z, 30.0f); } // Component removal test TEST_F(ECSTest, RemoveComponent) { auto& entityManager = systemManager->GetEntityManager(); EntityID entity = entityManager.CreateEntity(); PositionComponent pos{10.0f, 20.0f, 30.0f}; entityManager.AddComponent(entity, pos); EXPECT_TRUE(entityManager.HasComponent(entity)); entityManager.RemoveComponent(entity); EXPECT_FALSE(entityManager.HasComponent(entity)); } // Entity destruction test TEST_F(ECSTest, DestroyEntity) { auto& entityManager = systemManager->GetEntityManager(); EntityID entity = entityManager.CreateEntity(); PositionComponent pos{10.0f, 20.0f, 30.0f}; VelocityComponent vel{1.0f, 2.0f, 3.0f}; entityManager.AddComponent(entity, pos); entityManager.AddComponent(entity, vel); EXPECT_TRUE(entityManager.HasComponent(entity)); EXPECT_TRUE(entityManager.HasComponent(entity)); entityManager.DestroyEntity(entity); EXPECT_FALSE(entityManager.HasComponent(entity)); EXPECT_FALSE(entityManager.HasComponent(entity)); } // Component array access test TEST_F(ECSTest, ComponentArrayAccess) { auto& entityManager = systemManager->GetEntityManager(); // Create multiple entities std::vector entities; for (int i = 0; i < 5; ++i) { EntityID entity = entityManager.CreateEntity(); entityManager.AddComponent(entity, PositionComponent{static_cast(i), 0.0f, 0.0f}); entityManager.AddComponent(entity, VelocityComponent{static_cast(i), 0.0f, 0.0f}); entities.push_back(entity); } auto* positionArray = entityManager.GetComponentArray(); auto* velocityArray = entityManager.GetComponentArray(); EXPECT_EQ(positionArray->GetSize(), 5); EXPECT_EQ(velocityArray->GetSize(), 5); // Direct array access test PositionComponent* positions = positionArray->GetArray(); VelocityComponent* velocities = velocityArray->GetArray(); for (size_t i = 0; i < 5; ++i) { EXPECT_FLOAT_EQ(positions[i].x, static_cast(i)); EXPECT_FLOAT_EQ(velocities[i].vx, static_cast(i)); } } // Memory layout optimization verification test TEST_F(ECSTest, MemoryLayoutOptimization) { auto& entityManager = systemManager->GetEntityManager(); const int entityCount = 10000; std::cout << "\n=== Memory Layout Analysis ===" << std::endl; // Component size information std::cout << "Component sizes:" << std::endl; std::cout << " PositionComponent: " << sizeof(PositionComponent) << " bytes" << std::endl; std::cout << " VelocityComponent: " << sizeof(VelocityComponent) << " bytes" << std::endl; std::cout << " HealthComponent: " << sizeof(HealthComponent) << " bytes" << std::endl; // Cache line size (typically 64 bytes) const size_t cacheLineSize = 64; size_t positionsPerCacheLine = cacheLineSize / sizeof(PositionComponent); size_t velocitiesPerCacheLine = cacheLineSize / sizeof(VelocityComponent); std::cout << "Cache line optimization:" << std::endl; std::cout << " Cache line size: " << cacheLineSize << " bytes" << std::endl; std::cout << " Positions per cache line: " << positionsPerCacheLine << std::endl; std::cout << " Velocities per cache line: " << velocitiesPerCacheLine << std::endl; // Create entities and analyze memory addresses std::vector entities; for (int i = 0; i < entityCount; ++i) { EntityID entity = entityManager.CreateEntity(); entityManager.AddComponent(entity, PositionComponent{0.0f, 0.0f, 0.0f}); entityManager.AddComponent(entity, VelocityComponent{0.0f, 0.0f, 0.0f}); entities.push_back(entity); } auto* positionArray = entityManager.GetComponentArray(); auto* velocityArray = entityManager.GetComponentArray(); PositionComponent* positions = positionArray->GetArray(); VelocityComponent* velocities = velocityArray->GetArray(); // Memory contiguity verification bool isPositionContiguous = true; bool isVelocityContiguous = true; for (size_t i = 1; i < entityCount && i < 100; ++i) { ptrdiff_t positionDiff = reinterpret_cast(&positions[i]) - reinterpret_cast(&positions[i-1]); ptrdiff_t velocityDiff = reinterpret_cast(&velocities[i]) - reinterpret_cast(&velocities[i-1]); if (positionDiff != sizeof(PositionComponent)) { isPositionContiguous = false; } if (velocityDiff != sizeof(VelocityComponent)) { isVelocityContiguous = false; } } std::cout << "Memory contiguity:" << std::endl; std::cout << " Position components contiguous: " << (isPositionContiguous ? "Yes" : "No") << std::endl; std::cout << " Velocity components contiguous: " << (isVelocityContiguous ? "Yes" : "No") << std::endl; // Memory alignment verification uintptr_t positionAlignment = reinterpret_cast(positions) % alignof(PositionComponent); uintptr_t velocityAlignment = reinterpret_cast(velocities) % alignof(VelocityComponent); std::cout << "Memory alignment:" << std::endl; std::cout << " Position array aligned: " << (positionAlignment == 0 ? "Yes" : "No") << std::endl; std::cout << " Velocity array aligned: " << (velocityAlignment == 0 ? "Yes" : "No") << std::endl; // Memory usage size_t totalMemory = entityCount * (sizeof(PositionComponent) + sizeof(VelocityComponent)); size_t cacheLines = (totalMemory + cacheLineSize - 1) / cacheLineSize; std::cout << "Memory usage:" << std::endl; std::cout << " Total memory: " << totalMemory << " bytes (" << (totalMemory / 1024) << " KB)" << std::endl; std::cout << " Cache lines used: " << cacheLines << std::endl; // Verification EXPECT_TRUE(isPositionContiguous); EXPECT_TRUE(isVelocityContiguous); EXPECT_EQ(positionAlignment, 0); EXPECT_EQ(velocityAlignment, 0); // Cleanup for (auto entity : entities) { entityManager.DestroyEntity(entity); } } // System registration and execution test TEST_F(ECSTest, SystemRegistrationAndExecution) { auto& entityManager = systemManager->GetEntityManager(); // Create test entities EntityID entity = entityManager.CreateEntity(); entityManager.AddComponent(entity, PositionComponent{0.0f, 0.0f, 0.0f}); entityManager.AddComponent(entity, VelocityComponent{1.0f, 2.0f, 3.0f}); // Register system bool systemExecuted = false; systemManager->RegisterSystem( [&systemExecuted](float deltaTime, PositionComponent& position, VelocityComponent& velocity) { systemExecuted = true; // Simple movement update position.x += velocity.vx * deltaTime; position.y += velocity.vy * deltaTime; position.z += velocity.vz * deltaTime; }); // Execute system systemManager->Update(1.0f); EXPECT_TRUE(systemExecuted); // Check if position was updated auto& position = entityManager.GetComponent(entity); EXPECT_FLOAT_EQ(position.x, 1.0f); EXPECT_FLOAT_EQ(position.y, 2.0f); EXPECT_FLOAT_EQ(position.z, 3.0f); } // MovementSystem test TEST_F(ECSTest, MovementSystem) { auto& entityManager = systemManager->GetEntityManager(); EntityID entity = entityManager.CreateEntity(); entityManager.AddComponent(entity, PositionComponent{0.0f, 0.0f, 0.0f}); entityManager.AddComponent(entity, VelocityComponent{5.0f, 10.0f, 15.0f}); systemManager->RegisterSystem( [](float deltaTime, PositionComponent& position, VelocityComponent& velocity) { MovementSystem::Update(deltaTime, position, velocity); }); systemManager->Update(1.0f); auto& position = entityManager.GetComponent(entity); auto& velocity = entityManager.GetComponent(entity); // Check position update EXPECT_FLOAT_EQ(position.x, 5.0f); EXPECT_FLOAT_EQ(position.y, 10.0f); EXPECT_FLOAT_EQ(position.z, 15.0f); // Check friction application EXPECT_FLOAT_EQ(velocity.vx, 5.0f * 0.95f); EXPECT_FLOAT_EQ(velocity.vy, 10.0f * 0.95f); EXPECT_FLOAT_EQ(velocity.vz, 15.0f * 0.95f); } // PhysicsSystem test TEST_F(ECSTest, PhysicsSystem) { auto& entityManager = systemManager->GetEntityManager(); EntityID entity = entityManager.CreateEntity(); entityManager.AddComponent(entity, PositionComponent{0.0f, 10.0f, 0.0f}); entityManager.AddComponent(entity, VelocityComponent{0.0f, 0.0f, 0.0f}); entityManager.AddComponent(entity, PhysicsComponent{1.0f, 0.1f, 0.5f, false, true}); systemManager->RegisterSystem( [](float deltaTime, PositionComponent& position, VelocityComponent& velocity, PhysicsComponent& physics) { PhysicsSystem::Update(deltaTime, position, velocity, physics); }); // Test gravity application systemManager->Update(1.0f); auto& position = entityManager.GetComponent(entity); auto& velocity = entityManager.GetComponent(entity); // Check gravity application (g = -9.81) EXPECT_FLOAT_EQ(velocity.vy, -9.81f); EXPECT_FLOAT_EQ(position.y, 10.0f - 9.81f); } // HealthSystem test TEST_F(ECSTest, HealthSystem) { auto& entityManager = systemManager->GetEntityManager(); EntityID entity = entityManager.CreateEntity(); entityManager.AddComponent(entity, HealthComponent{50.0f, 100.0f, true}); systemManager->RegisterSystem( [](float deltaTime, HealthComponent& health) { HealthSystem::Update(deltaTime, health); }); systemManager->Update(1.0f); auto& health = entityManager.GetComponent(entity); // Check health regeneration (5 HP per second) EXPECT_FLOAT_EQ(health.currentHealth, 55.0f); EXPECT_TRUE(health.isAlive); } // AISystem test TEST_F(ECSTest, AISystem) { auto& entityManager = systemManager->GetEntityManager(); EntityID entity = entityManager.CreateEntity(); entityManager.AddComponent(entity, AIComponent{0, 0.0f, 1.0f}); systemManager->RegisterSystem( [](float deltaTime, AIComponent& ai) { AISystem::Update(deltaTime, ai); }); auto& ai = entityManager.GetComponent(entity); uint32_t initialState = ai.aiState; systemManager->Update(1.0f); // Check if AI state changed EXPECT_NE(ai.aiState, initialState); EXPECT_FLOAT_EQ(ai.aiTimer, 0.0f); } // CollisionSystem test TEST_F(ECSTest, CollisionSystem) { auto& entityManager = systemManager->GetEntityManager(); EntityID entity = entityManager.CreateEntity(); entityManager.AddComponent(entity, PositionComponent{0.0f, -5.0f, 0.0f}); entityManager.AddComponent(entity, CollisionComponent{2.0f, true, 1}); systemManager->RegisterSystem( [](float deltaTime, PositionComponent& position, CollisionComponent& collision) { CollisionSystem::Update(deltaTime, position, collision); }); systemManager->Update(1.0f); auto& position = entityManager.GetComponent(entity); // Check ground collision handling EXPECT_FLOAT_EQ(position.y, 2.0f); // Adjusted to radius value } // Component combination query test TEST_F(ECSTest, ComponentQuery) { auto& entityManager = systemManager->GetEntityManager(); // Create entities with different component combinations EntityID entity1 = entityManager.CreateEntity(); entityManager.AddComponent(entity1, PositionComponent{0.0f, 0.0f, 0.0f}); entityManager.AddComponent(entity1, VelocityComponent{1.0f, 0.0f, 0.0f}); entityManager.AddComponent(entity1, HealthComponent{100.0f, 100.0f, true}); EntityID entity2 = entityManager.CreateEntity(); entityManager.AddComponent(entity2, PositionComponent{10.0f, 0.0f, 0.0f}); entityManager.AddComponent(entity2, VelocityComponent{0.0f, 1.0f, 0.0f}); EntityID entity3 = entityManager.CreateEntity(); entityManager.AddComponent(entity3, HealthComponent{50.0f, 100.0f, true}); // Query entities with both Position and Velocity auto movingEntities = entityManager.GetEntitiesWithComponents(); EXPECT_EQ(movingEntities.size(), 2); // Query entities with Position, Velocity, and Health auto fullEntities = entityManager.GetEntitiesWithComponents(); EXPECT_EQ(fullEntities.size(), 1); EXPECT_EQ(fullEntities[0], entity1); }