LCOV - code coverage report
Current view: top level - include/ecs - World.hpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 95.9 % 73 70
Test Date: 2026-04-10 19:03:25 Functions: 84.6 % 52 44

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2025 aeml
       3              :  *
       4              :  * This program is free software: you can redistribute it and/or modify
       5              :  * it under the terms of the GNU General Public License as published by
       6              :  * the Free Software Foundation, either version 3 of the License, or
       7              :  * (at your option) any later version.
       8              :  *
       9              :  * This program is distributed in the hope that it will be useful,
      10              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12              :  * GNU General Public License for more details.
      13              :  *
      14              :  * You should have received a copy of the GNU General Public License
      15              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      16              :  */
      17              : 
      18              : #pragma once
      19              : 
      20              : #include <functional>
      21              : #include <memory>
      22              : #include <unordered_map>
      23              : #include <vector>
      24              : #include <typeindex>
      25              : #include <utility>
      26              : #include <tuple>
      27              : #include <optional>
      28              : 
      29              : #include "ecs/ComponentStorage.hpp"
      30              : 
      31              : #include <cstdint>
      32              : 
      33              : namespace ecs
      34              : {
      35              :     using EntityId = std::uint32_t;
      36              : 
      37              :     class ISystem;
      38              : 
      39              :     class World
      40              :     {
      41              :     public:
      42           51 :         World() = default;
      43              : 
      44              :         EntityId CreateEntity();
      45              :         void     DestroyEntity(EntityId id);
      46              : 
      47              :         template <typename TComponent, typename... Args>
      48        10876 :         TComponent& AddComponent(EntityId id, Args&&... args)
      49              :         {
      50        10876 :             const std::type_index key{typeid(TComponent)};
      51        10876 :             auto it = m_componentStores.find(key);
      52        10876 :             if (it == m_componentStores.end())
      53              :             {
      54          147 :                 auto wrapper = std::make_unique<StorageWrapper<TComponent>>();
      55          147 :                 it = m_componentStores.emplace(key, std::move(wrapper)).first;
      56          147 :             }
      57        10876 :             auto* storage = static_cast<StorageWrapper<TComponent>*>(it->second.get());
      58        10876 :             TComponent value(std::forward<Args>(args)...);
      59        21752 :             return storage->storage.Add(id, value);
      60              :         }
      61              : 
      62              :         template <typename TComponent>
      63       116045 :         TComponent* GetComponent(EntityId id)
      64              :         {
      65       116045 :             const std::type_index key{typeid(TComponent)};
      66       116045 :             auto it = m_componentStores.find(key);
      67       116045 :             if (it == m_componentStores.end())
      68              :             {
      69            1 :                 return nullptr;
      70              :             }
      71       116044 :             auto* storage = static_cast<StorageWrapper<TComponent>*>(it->second.get());
      72       116044 :             return storage->storage.Get(id);
      73              :         }
      74              : 
      75              :         template <typename TComponent>
      76              :         const TComponent* GetComponent(EntityId id) const
      77              :         {
      78              :             const std::type_index key{typeid(TComponent)};
      79              :             auto it = m_componentStores.find(key);
      80              :             if (it == m_componentStores.end())
      81              :             {
      82              :                 return nullptr;
      83              :             }
      84              :             const auto* storage = static_cast<const StorageWrapper<TComponent>*>(it->second.get());
      85              :             return storage->storage.Get(id);
      86              :         }
      87              : 
      88              :         template <typename TComponent>
      89       599947 :         ComponentStorage<TComponent>* GetStorage()
      90              :         {
      91       599947 :             const std::type_index key{typeid(TComponent)};
      92       599947 :             auto it = m_componentStores.find(key);
      93       599947 :             if (it == m_componentStores.end())
      94              :             {
      95        29310 :                 return nullptr;
      96              :             }
      97       570637 :             auto* storage = static_cast<StorageWrapper<TComponent>*>(it->second.get());
      98       570637 :             return &storage->storage;
      99              :         }
     100              : 
     101              :         template <typename TComponent>
     102         7371 :         const ComponentStorage<TComponent>* GetStorage() const
     103              :         {
     104         7371 :             const std::type_index key{typeid(TComponent)};
     105         7371 :             auto it = m_componentStores.find(key);
     106         7371 :             if (it == m_componentStores.end())
     107              :             {
     108         1126 :                 return nullptr;
     109              :             }
     110         6245 :             const auto* storage = static_cast<const StorageWrapper<TComponent>*>(it->second.get());
     111         6245 :             return &storage->storage;
     112              :         }
     113              : 
     114              :         void AddSystem(std::unique_ptr<ISystem> system);
     115              :         void Update(float dt);
     116              : 
     117              :         template <typename TSystem>
     118           22 :         TSystem* FindSystem()
     119              :         {
     120           22 :             for (auto& system : m_systems)
     121              :             {
     122           22 :                 if (auto* typed = dynamic_cast<TSystem*>(system.get()))
     123              :                 {
     124           22 :                     return typed;
     125              :                 }
     126              :             }
     127            0 :             return nullptr;
     128              :         }
     129              : 
     130              :         template <typename TSystem>
     131              :         const TSystem* FindSystem() const
     132              :         {
     133              :             for (const auto& system : m_systems)
     134              :             {
     135              :                 if (auto* typed = dynamic_cast<const TSystem*>(system.get()))
     136              :                 {
     137              :                     return typed;
     138              :                 }
     139              :             }
     140              :             return nullptr;
     141              :         }
     142              : 
     143              :         template <typename TComponent, typename Fn>
     144         2148 :         void ForEach(Fn&& fn)
     145              :         {
     146         2148 :             const std::type_index key{typeid(TComponent)};
     147         2148 :             auto it = m_componentStores.find(key);
     148         2148 :             if (it == m_componentStores.end())
     149              :             {
     150            0 :                 return;
     151              :             }
     152         2148 :             auto* storage = static_cast<StorageWrapper<TComponent>*>(it->second.get());
     153         2148 :             storage->storage.ForEach(std::forward<Fn>(fn));
     154              :         }
     155              : 
     156              :         // Multi-component View
     157              :         // Usage: world.View<Transform, Velocity>([&](EntityId id, Transform& t, Velocity& v) { ... });
     158              :         template <typename T1, typename T2, typename... TRest, typename Fn>
     159          180 :         void View(Fn&& fn)
     160              :         {
     161              :             // 1. Find all storages
     162          180 :             ComponentStorage<T1>* s1 = GetStorage<T1>();
     163          180 :             ComponentStorage<T2>* s2 = GetStorage<T2>();
     164              :             std::tuple<ComponentStorage<TRest>*...> restStores = std::make_tuple(GetStorage<TRest>()...);
     165              : 
     166          180 :             if (!s1 || !s2 || ((!std::get<ComponentStorage<TRest>*>(restStores)) || ...)) return;
     167              : 
     168              :             // 2. Find the smallest storage to iterate
     169          180 :             size_t minSize = s1->Size();
     170          180 :             int smallestIndex = 0; // 0 for s1, 1 for s2, 2+ for TRest
     171              : 
     172          180 :             if (s2->Size() < minSize) {
     173          180 :                 minSize = s2->Size();
     174          180 :                 smallestIndex = 1;
     175              :             }
     176              : 
     177              :             // Check rest (compile-time loop would be better, but runtime is fine for setup)
     178          180 :             int idx = 2;
     179          180 :             auto checkSize = [&](auto* s) {
     180              :                 if (s->Size() < minSize) {
     181              :                     minSize = s->Size();
     182              :                     smallestIndex = idx;
     183              :                 }
     184              :                 idx++;
     185              :             };
     186          180 :             std::apply([&](auto*... s) { (checkSize(s), ...); }, restStores);
     187              : 
     188              :             // 3. Iterate the smallest
     189         2880 :             auto process = [&](EntityId id) {
     190              :                 // We have the ID, now fetch all components.
     191              :                 // We know they exist in the "smallest" storage, but we must check existence in others.
     192              :                 // However, since we are iterating the intersection, we need to check if 'id' exists in ALL others.
     193              :                 
     194         2880 :                 T1* c1 = s1->Get(id);
     195         2880 :                 T2* c2 = s2->Get(id);
     196         2880 :                 if (c1 && c2) {
     197         2880 :                     if (auto tuple = GetComponentsTuple<TRest...>(id)) {
     198         2880 :                          std::apply([&](auto&... args) {
     199         2880 :                             fn(id, *c1, *c2, args...);
     200              :                         }, *tuple);
     201              :                     }
     202              :                 }
     203              :             };
     204              : 
     205          180 :             if (smallestIndex == 0) s1->ForEach([&](EntityId id, T1&) { process(id); });
     206         3060 :             else if (smallestIndex == 1) s2->ForEach([&](EntityId id, T2&) { process(id); });
     207              :             else {
     208              :                 // Iterate the correct storage from tuple
     209              :                 // This is tricky with runtime index. For simplicity in this version, 
     210              :                 // we'll just default to s1 if it's not s2, or improve logic later.
     211              :                 // To keep it simple and robust for now:
     212            0 :                 s1->ForEach([&](EntityId id, T1&) { process(id); });
     213              :             }
     214              :         }
     215              : 
     216              :     private:
     217              :         // Helper to get a tuple of pointers to components, or nullopt if any are missing
     218              :         template <typename... Ts>
     219         2880 :         std::optional<std::tuple<Ts&...>> GetComponentsTuple(EntityId id)
     220              :         {
     221              :             if constexpr (sizeof...(Ts) == 0)
     222              :             {
     223              :                 (void)id;
     224         2880 :                 return std::tuple<>{};
     225              :             }
     226              :             else
     227              :             {
     228              :                 std::tuple<Ts*...> ptrs = std::make_tuple(GetComponent<Ts>(id)...);
     229              :                 bool allFound = ((std::get<Ts*>(ptrs) != nullptr) && ...);
     230              :                 if (!allFound) return std::nullopt;
     231              : 
     232              :                 return std::tuple<Ts&...>(*std::get<Ts*>(ptrs)...);
     233              :             }
     234              :         }
     235              : 
     236              :         EntityId                                      m_nextEntity{1};
     237              :         std::vector<EntityId>                         m_entities;
     238              :         std::vector<std::unique_ptr<ISystem>>         m_systems;
     239              :         struct IStorage
     240              :         {
     241          147 :             virtual ~IStorage() = default;
     242              :             virtual void RemoveEntity(EntityId id) = 0;
     243              :         };
     244              :         template <typename T>
     245              :         struct StorageWrapper : IStorage
     246              :         {
     247              :             ComponentStorage<T> storage;
     248              : 
     249            2 :             void RemoveEntity(EntityId id) override
     250              :             {
     251            2 :                 storage.Remove(id);
     252            2 :             }
     253              :         };
     254              :         std::unordered_map<std::type_index, std::unique_ptr<IStorage>> m_componentStores;
     255              :     };
     256              : 
     257              :     class ISystem
     258              :     {
     259              :     public:
     260          112 :         virtual ~ISystem() = default;
     261              :         virtual void Update(World& world, float dt) = 0;
     262              :     };
     263              : }
        

Generated by: LCOV version 2.0-1