Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
sparse_component_store.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cstddef>
5#include <cstdint>
6#include <type_traits>
7
8#include "gaia/cnt/darray.h"
9#include "gaia/cnt/sparse_storage.h"
10#include "gaia/ecs/component_cache_item.h"
11#include "gaia/ecs/id.h"
12#include "gaia/mem/mem_alloc.h"
13#include "gaia/mem/smallblock_allocator.h"
14
16namespace gaia {
17 namespace ecs {
19 template <typename T>
20 struct SparseComponentRecord {
22 Entity entity;
24 T value{};
25 };
26
28 struct RuntimeSparseComponentRecord {
30 Entity entity;
32 void* pData = nullptr;
33 };
34 } // namespace ecs
35
36 namespace cnt {
37 template <typename T>
38 struct to_sparse_id<ecs::SparseComponentRecord<T>> {
39 static sparse_id get(const ecs::SparseComponentRecord<T>& item) noexcept {
40 return (sparse_id)item.entity.id();
41 }
42 };
43
44 template <>
45 struct to_sparse_id<ecs::RuntimeSparseComponentRecord> {
46 static sparse_id get(const ecs::RuntimeSparseComponentRecord& item) noexcept {
47 return (sparse_id)item.entity.id();
48 }
49 };
50 } // namespace cnt
51
52 namespace ecs {
53 namespace detail {
55 struct SparseComponentStoreErased {
57 void* pStore = nullptr;
59 void* (*func_add)(void*, Entity) = nullptr;
61 void* (*func_mut)(void*, Entity) = nullptr;
63 const void* (*func_get)(const void*, Entity) = nullptr;
64 void (*func_del)(void*, Entity) = nullptr;
65 bool (*func_has)(const void*, Entity) = nullptr;
66 bool (*func_copy_entity)(void*, Entity, Entity) = nullptr;
67 uint32_t (*func_count)(const void*) = nullptr;
68 void (*func_collect_entities)(const void*, cnt::darray<Entity>&) = nullptr;
69 bool (*func_for_each_entity)(const void*, void*, bool (*)(void*, Entity)) = nullptr;
70 void (*func_clear_store)(void*) = nullptr;
71 void (*func_del_store)(void*) = nullptr;
72 };
73
75 struct RuntimeSparseComponentStore final {
76 GAIA_USE_SMALLBLOCK(RuntimeSparseComponentStore)
78 static constexpr uint32_t PayloadPageBytes = 16U * 1024U;
80 static constexpr uint32_t MaxPayloadsPerPage = 256U;
81
83 cnt::sparse_storage<RuntimeSparseComponentRecord> data;
85 cnt::darray<void*> payloadPages;
87 const ComponentCacheItem* pItem = nullptr;
89 void* pFreePayload = nullptr;
91 uint32_t payloadStride = 0;
93 uint32_t payloadAlignment = 0;
95 uint32_t payloadsPerPage = 0;
97 uint32_t nextPayload = 0;
98
99 explicit RuntimeSparseComponentStore(const ComponentCacheItem& item): pItem(&item) {
100 const auto size = item.comp.size();
101 if (size == 0)
102 return;
103
104 const auto itemAlignment = item.comp.alig();
105 payloadAlignment = itemAlignment < alignof(void*) ? (uint32_t)alignof(void*) : itemAlignment;
106 const auto payloadSize = size < sizeof(void*) ? (uint32_t)sizeof(void*) : size;
107 payloadStride = mem::align(payloadSize, payloadAlignment);
108 payloadsPerPage = PayloadPageBytes / payloadStride;
109 if (payloadsPerPage == 0)
110 payloadsPerPage = 1;
111 else if (payloadsPerPage > MaxPayloadsPerPage)
112 payloadsPerPage = MaxPayloadsPerPage;
113 nextPayload = payloadsPerPage;
114 }
115
116 static cnt::sparse_id sid(Entity entity) {
117 return (cnt::sparse_id)entity.id();
118 }
119
121 GAIA_NODISCARD void* alloc_payload() {
122 if (pFreePayload != nullptr) {
123 auto* pData = pFreePayload;
124 memcpy(&pFreePayload, pData, sizeof(pFreePayload));
125 return pData;
126 }
127
128 if (nextPayload == payloadsPerPage) {
129 const auto pageBytes = payloadStride * payloadsPerPage;
130 auto* pPage = payloadAlignment <= alignof(std::max_align_t)
131 ? mem::mem_alloc("Runtime sparse payload page", pageBytes)
132 : mem::mem_alloc_alig("Runtime sparse payload page", pageBytes, payloadAlignment);
133 GAIA_ASSERT(pPage != nullptr);
134 payloadPages.push_back(pPage);
135 nextPayload = 0;
136 }
137
138 auto* pData = (uint8_t*)payloadPages.back() + ((uintptr_t)payloadStride * nextPayload);
139 ++nextPayload;
140 return pData;
141 }
142
144 void free_payload(void* pData) {
145 memcpy(pData, &pFreePayload, sizeof(pFreePayload));
146 pFreePayload = pData;
147 }
148
150 void free_payload_pages() {
151 for (auto* pPage: payloadPages) {
152 if (payloadAlignment <= alignof(std::max_align_t))
153 mem::mem_free("Runtime sparse payload page", pPage);
154 else
155 mem::mem_free_alig("Runtime sparse payload page", pPage);
156 }
157 payloadPages.clear();
158 pFreePayload = nullptr;
159 nextPayload = payloadsPerPage;
160 }
161
162 void* add(Entity entity) {
163 const auto sparseId = sid(entity);
164 if (data.has(sparseId))
165 return data[sparseId].pData;
166
167 void* pData = nullptr;
168 const auto size = pItem->comp.size();
169 if (size != 0) {
170 pData = alloc_payload();
171 GAIA_ASSERT(pData != nullptr);
172 if (pItem->func_ctor != nullptr)
173 pItem->func_ctor(pData, 1);
174 }
175
176 data.add(RuntimeSparseComponentRecord{entity, pData});
177 return pData;
178 }
179
180 void* mut(Entity entity) {
181 GAIA_ASSERT(data.has(sid(entity)));
182 return data[sid(entity)].pData;
183 }
184
185 const void* get(Entity entity) const {
186 GAIA_ASSERT(data.has(sid(entity)));
187 return data[sid(entity)].pData;
188 }
189
190 void del_entity(Entity entity) {
191 const auto sparseId = sid(entity);
192 if (!data.has(sparseId))
193 return;
194
195 auto* pData = data[sparseId].pData;
196 if (pData != nullptr) {
197 pItem->dtor(pData);
198 free_payload(pData);
199 }
200 data.del(sparseId);
201 }
202
203 bool has(Entity entity) const {
204 return data.has(sid(entity));
205 }
206
207 bool copy_entity(Entity dstEntity, Entity srcEntity) {
208 if (!has(srcEntity))
209 return false;
210
211 auto* pDst = add(dstEntity);
212 const auto* pSrc = get(srcEntity);
213 if (pDst != nullptr)
214 pItem->copy(pDst, pSrc, 0, 0, pItem->comp.size(), pItem->comp.size());
215 return true;
216 }
217
218 uint32_t count() const {
219 return (uint32_t)data.size();
220 }
221
222 void collect_entities(cnt::darray<Entity>& out) const {
223 out.reserve(out.size() + (uint32_t)data.size());
224 for (const auto& item: data)
225 out.push_back(item.entity);
226 }
227
228 void clear_store() {
229 while (!data.empty())
230 del_entity(data.begin()->entity);
231 free_payload_pages();
232 }
233 };
234
236 template <typename T>
237 struct SparseComponentStore final {
238 GAIA_USE_SMALLBLOCK(SparseComponentStore)
239
240 cnt::sparse_storage<SparseComponentRecord<T>> data;
241
242 static cnt::sparse_id sid(Entity entity) {
243 return (cnt::sparse_id)entity.id();
244 }
245
246 T& add(Entity entity) {
247 const auto sparseId = sid(entity);
248 if (data.has(sparseId))
249 return data[sparseId].value;
250
251 auto& item = data.add(SparseComponentRecord<T>{entity});
252 return item.value;
253 }
254
255 T& mut(Entity entity) {
256 GAIA_ASSERT(data.has(sid(entity)));
257 return data[sid(entity)].value;
258 }
259
260 const T& get(Entity entity) const {
261 GAIA_ASSERT(data.has(sid(entity)));
262 return data[sid(entity)].value;
263 }
264
265 void del_entity(Entity entity) {
266 const auto sparseId = sid(entity);
267 if (data.has(sparseId))
268 data.del(sparseId);
269 }
270
271 bool has(Entity entity) const {
272 return data.has(sid(entity));
273 }
274
275 bool copy_entity(Entity dstEntity, Entity srcEntity) {
276 if (!has(srcEntity))
277 return false;
278 add(dstEntity) = get(srcEntity);
279 return true;
280 }
281
282 uint32_t count() const {
283 return (uint32_t)data.size();
284 }
285
286 void collect_entities(cnt::darray<Entity>& out) const {
287 out.reserve(out.size() + (uint32_t)data.size());
288 for (const auto& item: data)
289 out.push_back(item.entity);
290 }
291
292 void clear_store() {
293 data.clear();
294 }
295 };
296
297 template <typename Store>
298 static SparseComponentStoreErased make_sparse_component_store_erased(Store* pStore) {
299 SparseComponentStoreErased store{};
300 store.pStore = pStore;
301 store.func_add = [](void* pStoreRaw, Entity entity) {
302 if constexpr (std::is_pointer_v<decltype(static_cast<Store*>(pStoreRaw)->add(entity))>)
303 return static_cast<Store*>(pStoreRaw)->add(entity);
304 else
305 return (void*)&static_cast<Store*>(pStoreRaw)->add(entity);
306 };
307 store.func_mut = [](void* pStoreRaw, Entity entity) {
308 if constexpr (std::is_pointer_v<decltype(static_cast<Store*>(pStoreRaw)->mut(entity))>)
309 return static_cast<Store*>(pStoreRaw)->mut(entity);
310 else
311 return (void*)&static_cast<Store*>(pStoreRaw)->mut(entity);
312 };
313 store.func_get = [](const void* pStoreRaw, Entity entity) {
314 if constexpr (std::is_pointer_v<decltype(static_cast<const Store*>(pStoreRaw)->get(entity))>)
315 return static_cast<const Store*>(pStoreRaw)->get(entity);
316 else
317 return (const void*)&static_cast<const Store*>(pStoreRaw)->get(entity);
318 };
319 store.func_del = [](void* pStoreRaw, Entity entity) {
320 static_cast<Store*>(pStoreRaw)->del_entity(entity);
321 };
322 store.func_has = [](const void* pStoreRaw, Entity entity) {
323 return static_cast<const Store*>(pStoreRaw)->has(entity);
324 };
325 store.func_copy_entity = [](void* pStoreRaw, Entity dstEntity, Entity srcEntity) {
326 return static_cast<Store*>(pStoreRaw)->copy_entity(dstEntity, srcEntity);
327 };
328 store.func_count = [](const void* pStoreRaw) {
329 return static_cast<const Store*>(pStoreRaw)->count();
330 };
331 store.func_collect_entities = [](const void* pStoreRaw, cnt::darray<Entity>& out) {
332 static_cast<const Store*>(pStoreRaw)->collect_entities(out);
333 };
334 store.func_for_each_entity = [](const void* pStoreRaw, void* pCtx, bool (*func)(void*, Entity)) {
335 const auto& data = static_cast<const Store*>(pStoreRaw)->data;
336 for (const auto& item: data) {
337 if (!func(pCtx, item.entity))
338 return false;
339 }
340 return true;
341 };
342 store.func_clear_store = [](void* pStoreRaw) {
343 static_cast<Store*>(pStoreRaw)->clear_store();
344 };
345 store.func_del_store = [](void* pStoreRaw) {
346 delete static_cast<Store*>(pStoreRaw);
347 };
348 return store;
349 }
350 } // namespace detail
351 } // namespace ecs
352} // namespace gaia
static sparse_id get(const T &item) noexcept
Returns the sparse identifier for an item.
Definition sparse_storage.h:53