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 : }
|