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/map.h"
10#include "gaia/cnt/sparse_storage.h"
11#include "gaia/core/utility.h"
12#include "gaia/ecs/component_cache_item.h"
13#include "gaia/ecs/id.h"
14#include "gaia/mem/mem_alloc.h"
15#include "gaia/mem/smallblock_allocator.h"
16
18namespace gaia {
19 namespace ecs {
21 template <typename T>
22 struct SparseComponentRecord {
24 Entity entity;
26 T value{};
27 };
28
30 struct RuntimeSparseComponentRecord {
32 Entity entity;
34 void* pData = nullptr;
35 };
36 } // namespace ecs
37
38 namespace cnt {
39 template <typename T>
40 struct to_sparse_id<ecs::SparseComponentRecord<T>> {
41 static sparse_id get(const ecs::SparseComponentRecord<T>& item) noexcept {
42 return (sparse_id)item.entity.id();
43 }
44 };
45
46 template <>
47 struct to_sparse_id<ecs::RuntimeSparseComponentRecord> {
48 static sparse_id get(const ecs::RuntimeSparseComponentRecord& item) noexcept {
49 return (sparse_id)item.entity.id();
50 }
51 };
52 } // namespace cnt
53
54 namespace ecs {
55 namespace detail {
57 struct SparseComponentStoreErased {
59 void* pStore = nullptr;
61 void* (*func_add)(void*, Entity) = nullptr;
63 void* (*func_mut)(void*, Entity) = nullptr;
65 const void* (*func_get)(const void*, Entity) = nullptr;
67 const void* (*func_get_entity)(const void*, Entity) = nullptr;
68 void (*func_del)(void*, Entity) = nullptr;
69 bool (*func_has)(const void*, Entity) = nullptr;
70 bool (*func_copy_entity)(void*, Entity, Entity) = nullptr;
71 uint32_t (*func_count)(const void*) = nullptr;
72 void (*func_collect_entities)(const void*, cnt::darray<Entity>&) = nullptr;
73 bool (*func_each_entity)(const void*, void*, bool (*)(void*, Entity)) = nullptr;
74 void (*func_clear_store)(void*) = nullptr;
75 void (*func_del_store)(void*) = nullptr;
76 };
77
79 struct RuntimeSparseComponentStore final {
80 GAIA_USE_SMALLBLOCK(RuntimeSparseComponentStore)
82 static constexpr uint32_t PayloadPageBytes = 16U * 1024U;
84 static constexpr uint32_t MaxPayloadsPerPage = 256U;
85
87 cnt::sparse_storage<RuntimeSparseComponentRecord> data;
89 cnt::map<EntityLookupKey, RuntimeSparseComponentRecord> pairData;
91 cnt::darray<void*> payloadPages;
93 const ComponentCacheItem* pItem = nullptr;
95 void* pFreePayload = nullptr;
97 uint32_t payloadStride = 0;
99 uint32_t payloadAlignment = 0;
101 uint32_t payloadsPerPage = 0;
103 uint32_t nextPayload = 0;
104
105 explicit RuntimeSparseComponentStore(const ComponentCacheItem& item): pItem(&item) {
106 const auto size = item.comp.size();
107 if (size == 0)
108 return;
109
110 const auto itemAlignment = item.comp.alig();
111 payloadAlignment = itemAlignment < alignof(void*) ? (uint32_t)alignof(void*) : itemAlignment;
112 const auto payloadSize = size < sizeof(void*) ? (uint32_t)sizeof(void*) : size;
113 payloadStride = mem::align(payloadSize, payloadAlignment);
114 payloadsPerPage = PayloadPageBytes / payloadStride;
115 if (payloadsPerPage == 0)
116 payloadsPerPage = 1;
117 else if (payloadsPerPage > MaxPayloadsPerPage)
118 payloadsPerPage = MaxPayloadsPerPage;
119 nextPayload = payloadsPerPage;
120 }
121
122 static cnt::sparse_id sid(Entity entity) {
123 GAIA_ASSERT(!entity.pair());
124 return (cnt::sparse_id)entity.id();
125 }
126
129 GAIA_NODISCARD void* create_payload() {
130 const auto size = pItem->comp.size();
131 if (size == 0)
132 return nullptr;
133
134 auto* pData = alloc_payload();
135 GAIA_ASSERT(pData != nullptr);
136 if (pItem->func_ctor != nullptr)
137 pItem->func_ctor(pData, 1);
138 return pData;
139 }
140
143 void destroy_payload(void* pData) {
144 if (pData == nullptr)
145 return;
146
147 pItem->dtor(pData);
148 free_payload(pData);
149 }
150
152 GAIA_NODISCARD void* alloc_payload() {
153 if (pFreePayload != nullptr) {
154 auto* pData = pFreePayload;
155 memcpy(&pFreePayload, pData, sizeof(pFreePayload));
156 return pData;
157 }
158
159 if (nextPayload == payloadsPerPage) {
160 const auto pageBytes = payloadStride * payloadsPerPage;
161 auto* pPage = payloadAlignment <= alignof(std::max_align_t)
162 ? mem::mem_alloc("Runtime sparse payload page", pageBytes)
163 : mem::mem_alloc_alig("Runtime sparse payload page", pageBytes, payloadAlignment);
164 GAIA_ASSERT(pPage != nullptr);
165 payloadPages.push_back(pPage);
166 nextPayload = 0;
167 }
168
169 auto* pData = (uint8_t*)payloadPages.back() + ((uintptr_t)payloadStride * nextPayload);
170 ++nextPayload;
171 return pData;
172 }
173
175 void free_payload(void* pData) {
176 memcpy(pData, &pFreePayload, sizeof(pFreePayload));
177 pFreePayload = pData;
178 }
179
181 void free_payload_pages() {
182 for (auto* pPage: payloadPages) {
183 if (payloadAlignment <= alignof(std::max_align_t))
184 mem::mem_free("Runtime sparse payload page", pPage);
185 else
186 mem::mem_free_alig("Runtime sparse payload page", pPage);
187 }
188 payloadPages.clear();
189 pFreePayload = nullptr;
190 nextPayload = payloadsPerPage;
191 }
192
193 void* add(Entity entity) {
194 if GAIA_UNLIKELY (entity.pair()) {
195 const auto key = EntityLookupKey(entity);
196 const auto it = pairData.find(key);
197 if (it != pairData.end())
198 return it->second.pData;
199
200 auto* pData = create_payload();
201 return pairData.emplace(key, RuntimeSparseComponentRecord{entity, pData}).first->second.pData;
202 }
203
204 const auto sparseId = sid(entity);
205 if (data.has(sparseId))
206 return data[sparseId].pData;
207
208 auto* pData = create_payload();
209 data.add(RuntimeSparseComponentRecord{entity, pData});
210 return pData;
211 }
212
213 void* mut(Entity entity) {
214 if GAIA_UNLIKELY (entity.pair()) {
215 const auto it = pairData.find(EntityLookupKey(entity));
216 GAIA_ASSERT(it != pairData.end());
217 return it != pairData.end() ? it->second.pData : nullptr;
218 }
219
220 GAIA_ASSERT(data.has(sid(entity)));
221 return data[sid(entity)].pData;
222 }
223
224 const void* get(Entity entity) const {
225 if GAIA_UNLIKELY (entity.pair()) {
226 const auto it = pairData.find(EntityLookupKey(entity));
227 GAIA_ASSERT(it != pairData.end());
228 return it != pairData.end() ? it->second.pData : nullptr;
229 }
230
231 GAIA_ASSERT(data.has(sid(entity)));
232 return data[sid(entity)].pData;
233 }
234
238 GAIA_NODISCARD const void* get_entity(Entity entity) const {
239 GAIA_ASSERT(!entity.pair());
240 GAIA_ASSERT(data.has(sid(entity)));
241 return data[sid(entity)].pData;
242 }
243
244 void del_entity(Entity entity) {
245 if GAIA_UNLIKELY (entity.pair()) {
246 const auto it = pairData.find(EntityLookupKey(entity));
247 if (it == pairData.end())
248 return;
249
250 destroy_payload(it->second.pData);
251 pairData.erase(it);
252 return;
253 }
254
255 const auto sparseId = sid(entity);
256 if (!data.has(sparseId))
257 return;
258
259 destroy_payload(data[sparseId].pData);
260 data.del(sparseId);
261 }
262
263 bool has(Entity entity) const {
264 if GAIA_UNLIKELY (entity.pair())
265 return pairData.contains(EntityLookupKey(entity));
266
267 return data.has(sid(entity));
268 }
269
270 bool copy_entity(Entity dstEntity, Entity srcEntity) {
271 if (!has(srcEntity))
272 return false;
273
274 auto* pDst = add(dstEntity);
275 const auto* pSrc = get(srcEntity);
276 if (pDst != nullptr)
277 pItem->copy(pDst, pSrc, 0, 0, pItem->comp.size(), pItem->comp.size());
278 return true;
279 }
280
281 uint32_t count() const {
282 return (uint32_t)(data.size() + pairData.size());
283 }
284
285 void collect_entities(cnt::darray<Entity>& out) const {
286 out.reserve(out.size() + count());
287 for (const auto& item: data)
288 out.push_back(item.entity);
289
290 const auto firstPair = out.size();
291 for (const auto& item: pairData)
292 out.push_back(item.first.entity());
293 core::sort(out.begin() + firstPair, out.end(), core::is_smaller<Entity>{});
294 }
295
300 GAIA_NODISCARD bool each_entity(void* ctx, bool (*func)(void*, Entity)) const {
301 for (const auto& item: data) {
302 if (!func(ctx, item.entity))
303 return false;
304 }
305
306 if (pairData.empty())
307 return true;
308
309 cnt::darray<Entity> pairEntities;
310 pairEntities.reserve((uint32_t)pairData.size());
311 for (const auto& item: pairData)
312 pairEntities.push_back(item.first.entity());
313 core::sort(pairEntities.begin(), pairEntities.end(), core::is_smaller<Entity>{});
314 for (auto entity: pairEntities) {
315 if (!func(ctx, entity))
316 return false;
317 }
318 return true;
319 }
320
321 void clear_store() {
322 while (!data.empty())
323 del_entity(data.begin()->entity);
324 while (!pairData.empty())
325 del_entity(pairData.begin()->first.entity());
326 free_payload_pages();
327 }
328 };
329
331 template <typename T>
332 struct SparseComponentStore final {
333 GAIA_USE_SMALLBLOCK(SparseComponentStore)
334
335 cnt::sparse_storage<SparseComponentRecord<T>> data;
337 cnt::map<EntityLookupKey, SparseComponentRecord<T>> pairData;
338
339 static cnt::sparse_id sid(Entity entity) {
340 GAIA_ASSERT(!entity.pair());
341 return (cnt::sparse_id)entity.id();
342 }
343
344 T& add(Entity entity) {
345 if GAIA_UNLIKELY (entity.pair()) {
346 const auto key = EntityLookupKey(entity);
347 const auto it = pairData.find(key);
348 if (it != pairData.end())
349 return it->second.value;
350
351 return pairData.emplace(key, SparseComponentRecord<T>{entity}).first->second.value;
352 }
353
354 const auto sparseId = sid(entity);
355 if (data.has(sparseId))
356 return data[sparseId].value;
357
358 auto& item = data.add(SparseComponentRecord<T>{entity});
359 return item.value;
360 }
361
362 T& mut(Entity entity) {
363 if GAIA_UNLIKELY (entity.pair()) {
364 const auto it = pairData.find(EntityLookupKey(entity));
365 GAIA_ASSERT(it != pairData.end());
366 return it->second.value;
367 }
368
369 GAIA_ASSERT(data.has(sid(entity)));
370 return data[sid(entity)].value;
371 }
372
373 const T& get(Entity entity) const {
374 if GAIA_UNLIKELY (entity.pair()) {
375 return get_pair(entity);
376 }
377
378 return get_entity(entity);
379 }
380
384 GAIA_NODISCARD const T& get_entity(Entity entity) const {
385 GAIA_ASSERT(!entity.pair());
386 GAIA_ASSERT(data.has(sid(entity)));
387 return data[sid(entity)].value;
388 }
389
393 GAIA_NODISCARD const T& get_pair(Entity entity) const {
394 GAIA_ASSERT(entity.pair());
395 const auto it = pairData.find(EntityLookupKey(entity));
396 GAIA_ASSERT(it != pairData.end());
397 return it->second.value;
398 }
399
403 GAIA_NODISCARD T* try_mut(Entity entity) {
404 if GAIA_UNLIKELY (entity.pair()) {
405 const auto it = pairData.find(EntityLookupKey(entity));
406 return it != pairData.end() ? &it->second.value : nullptr;
407 }
408
409 const auto sparseId = sid(entity);
410 return data.has(sparseId) ? &data[sparseId].value : nullptr;
411 }
412
416 GAIA_NODISCARD const T* try_get(Entity entity) const {
417 if GAIA_UNLIKELY (entity.pair()) {
418 const auto it = pairData.find(EntityLookupKey(entity));
419 return it != pairData.end() ? &it->second.value : nullptr;
420 }
421
422 const auto sparseId = sid(entity);
423 return data.has(sparseId) ? &data[sparseId].value : nullptr;
424 }
425
426 void del_entity(Entity entity) {
427 if GAIA_UNLIKELY (entity.pair()) {
428 pairData.erase(EntityLookupKey(entity));
429 return;
430 }
431
432 const auto sparseId = sid(entity);
433 if (data.has(sparseId))
434 data.del(sparseId);
435 }
436
437 bool has(Entity entity) const {
438 if GAIA_UNLIKELY (entity.pair())
439 return pairData.contains(EntityLookupKey(entity));
440
441 return data.has(sid(entity));
442 }
443
444 bool copy_entity(Entity dstEntity, Entity srcEntity) {
445 if (!has(srcEntity))
446 return false;
447 add(dstEntity) = get(srcEntity);
448 return true;
449 }
450
451 uint32_t count() const {
452 return (uint32_t)(data.size() + pairData.size());
453 }
454
455 void collect_entities(cnt::darray<Entity>& out) const {
456 out.reserve(out.size() + count());
457 for (const auto& item: data)
458 out.push_back(item.entity);
459
460 const auto firstPair = out.size();
461 for (const auto& item: pairData)
462 out.push_back(item.first.entity());
463 core::sort(out.begin() + firstPair, out.end(), core::is_smaller<Entity>{});
464 }
465
470 GAIA_NODISCARD bool each_entity(void* ctx, bool (*func)(void*, Entity)) const {
471 for (const auto& item: data) {
472 if (!func(ctx, item.entity))
473 return false;
474 }
475
476 if (pairData.empty())
477 return true;
478
479 cnt::darray<Entity> pairEntities;
480 pairEntities.reserve((uint32_t)pairData.size());
481 for (const auto& item: pairData)
482 pairEntities.push_back(item.first.entity());
483 core::sort(pairEntities.begin(), pairEntities.end(), core::is_smaller<Entity>{});
484 for (auto entity: pairEntities) {
485 if (!func(ctx, entity))
486 return false;
487 }
488 return true;
489 }
490
491 void clear_store() {
492 data.clear();
493 pairData.clear();
494 }
495 };
496
497 template <typename Store>
498 static SparseComponentStoreErased make_sparse_component_store_erased(Store* pStore) {
499 SparseComponentStoreErased store{};
500 store.pStore = pStore;
501 store.func_add = [](void* pStoreRaw, Entity entity) {
502 if constexpr (std::is_pointer_v<decltype(static_cast<Store*>(pStoreRaw)->add(entity))>)
503 return static_cast<Store*>(pStoreRaw)->add(entity);
504 else
505 return (void*)&static_cast<Store*>(pStoreRaw)->add(entity);
506 };
507 store.func_mut = [](void* pStoreRaw, Entity entity) {
508 if constexpr (std::is_pointer_v<decltype(static_cast<Store*>(pStoreRaw)->mut(entity))>)
509 return static_cast<Store*>(pStoreRaw)->mut(entity);
510 else
511 return (void*)&static_cast<Store*>(pStoreRaw)->mut(entity);
512 };
513 store.func_get = [](const void* pStoreRaw, Entity entity) {
514 if constexpr (std::is_pointer_v<decltype(static_cast<const Store*>(pStoreRaw)->get(entity))>)
515 return static_cast<const Store*>(pStoreRaw)->get(entity);
516 else
517 return (const void*)&static_cast<const Store*>(pStoreRaw)->get(entity);
518 };
519 store.func_get_entity = [](const void* pStoreRaw, Entity entity) {
520 if constexpr (std::is_pointer_v<decltype(static_cast<const Store*>(pStoreRaw)->get_entity(entity))>)
521 return static_cast<const Store*>(pStoreRaw)->get_entity(entity);
522 else
523 return (const void*)&static_cast<const Store*>(pStoreRaw)->get_entity(entity);
524 };
525 store.func_del = [](void* pStoreRaw, Entity entity) {
526 static_cast<Store*>(pStoreRaw)->del_entity(entity);
527 };
528 store.func_has = [](const void* pStoreRaw, Entity entity) {
529 return static_cast<const Store*>(pStoreRaw)->has(entity);
530 };
531 store.func_copy_entity = [](void* pStoreRaw, Entity dstEntity, Entity srcEntity) {
532 return static_cast<Store*>(pStoreRaw)->copy_entity(dstEntity, srcEntity);
533 };
534 store.func_count = [](const void* pStoreRaw) {
535 return static_cast<const Store*>(pStoreRaw)->count();
536 };
537 store.func_collect_entities = [](const void* pStoreRaw, cnt::darray<Entity>& out) {
538 static_cast<const Store*>(pStoreRaw)->collect_entities(out);
539 };
540 store.func_each_entity = [](const void* pStoreRaw, void* pCtx, bool (*func)(void*, Entity)) {
541 return static_cast<const Store*>(pStoreRaw)->each_entity(pCtx, func);
542 };
543 store.func_clear_store = [](void* pStoreRaw) {
544 static_cast<Store*>(pStoreRaw)->clear_store();
545 };
546 store.func_del_store = [](void* pStoreRaw) {
547 delete static_cast<Store*>(pStoreRaw);
548 };
549 return store;
550 }
551 } // namespace detail
552 } // namespace ecs
553} // namespace gaia
static sparse_id get(const T &item) noexcept
Returns the sparse identifier for an item.
Definition sparse_storage.h:53