Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
entity_container.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cstdint>
5#include <type_traits>
6
7#include "gaia/cnt/ilist.h"
8#include "gaia/cnt/map.h"
9#include "gaia/cnt/paged_storage.h"
10#include "gaia/ecs/api.h"
11#include "gaia/ecs/id.h"
12#include "gaia/mem/smallblock_allocator.h"
13
15namespace gaia {
16 namespace ecs {
17 struct EntityContainer;
18 }
19
20 namespace cnt {
21 template <>
22 struct to_page_storage_id<ecs::EntityContainer> {
23 static page_storage_id get(const ecs::EntityContainer& item) noexcept;
24 };
25 } // namespace cnt
26
27 namespace ecs {
28 class Chunk;
29 class Archetype;
30 class World;
31#if GAIA_USE_WEAK_ENTITY
32 struct WeakEntityTracker;
33#endif
34
35 struct EntityContainerCtx {
36 bool isEntity;
37 bool isPair;
38 };
39
40 using EntityContainerFlagsType = uint16_t;
41 enum EntityContainerFlags : EntityContainerFlagsType {
42 OnDelete_Remove = 1 << 0,
43 OnDelete_Delete = 1 << 1,
44 OnDelete_Error = 1 << 2,
45 OnDeleteTarget_Remove = 1 << 3,
46 OnDeleteTarget_Delete = 1 << 4,
47 OnDeleteTarget_Error = 1 << 5,
48 HasAcyclic = 1 << 6,
49 HasCantCombine = 1 << 7,
50 IsExclusive = 1 << 8,
51 HasAliasOf = 1 << 9,
52 IsSingleton = 1 << 10,
53 DeleteRequested = 1 << 11,
54 RefDecreased = 1 << 12, // GAIA_USE_SAFE_ENTITY
55 Load = 1 << 13, // EntityContainer is being loaded from a file
56 IsObserved = 1 << 14, // At least one observer is registered for this term.
57 IsDontFragment = 1 << 15,
58 };
59
60 struct EntityContainer {
63 uint32_t idx;
64
66 // Bits in this section need to be 1:1 with Entity internal data
68
69 struct EntityData {
71 uint32_t gen : 28;
73 uint32_t ent : 1;
75 uint32_t pair : 1;
77 uint32_t reserved : 1;
83 uint32_t dis : 1;
84 };
85
86 union {
87 EntityData data;
88 uint32_t dataRaw;
89 };
90
92
94 uint16_t row;
96 uint16_t flags = 0;
97
98#if GAIA_USE_SAFE_ENTITY
101 uint32_t refCnt = 1;
102#else
104 uint32_t unused = 0;
105#endif
106
107#if GAIA_USE_WEAK_ENTITY
108 WeakEntityTracker* pWeakTracker = nullptr;
109#endif
110
112 Archetype* pArchetype = nullptr;
114 Chunk* pChunk = nullptr;
116 const Entity* pEntity = nullptr;
117 // uint8_t depthDependsOn = 0;
118
119 EntityContainer() = default;
120
121 GAIA_NODISCARD static EntityContainer create(uint32_t index, uint32_t generation, void* pCtx) {
122 auto* ctx = (EntityContainerCtx*)pCtx;
123
124 EntityContainer ec{};
125 ec.idx = index;
126 ec.data.gen = generation;
127 ec.data.ent = (uint32_t)ctx->isEntity;
128 ec.data.pair = (uint32_t)ctx->isPair;
129 return ec;
130 }
131
132 GAIA_NODISCARD static Entity handle(const EntityContainer& ec) {
133 return Entity(ec.idx, ec.data.gen, (bool)ec.data.ent, (bool)ec.data.pair);
134 }
135
136 void req_del() {
137 flags |= DeleteRequested;
138 }
139 };
140
141 struct PairRecords {
142 private:
144 cnt::map<EntityLookupKey, EntityContainer> m_records;
145
146 public:
148 void clear() {
149 m_records = {};
150 }
151
155 GAIA_NODISCARD bool contains(Entity entity) const {
156 GAIA_ASSERT(entity.pair());
157 return m_records.contains(EntityLookupKey(entity));
158 }
159
163 GAIA_NODISCARD EntityContainer* find(Entity entity) {
164 GAIA_ASSERT(entity.pair());
165
166 const auto it = m_records.find(EntityLookupKey(entity));
167 if (it == m_records.end())
168 return nullptr;
169
170 return &it->second;
171 }
172
176 GAIA_NODISCARD const EntityContainer* find(Entity entity) const {
177 GAIA_ASSERT(entity.pair());
178
179 const auto it = m_records.find(EntityLookupKey(entity));
180 if (it == m_records.end())
181 return nullptr;
182
183 return &it->second;
184 }
185
189 void add(Entity entity, EntityContainer&& ec) {
190 GAIA_ASSERT(entity.pair());
191 m_records.emplace(EntityLookupKey(entity), GAIA_MOV(ec));
192 }
193
198 GAIA_NODISCARD bool try_add(Entity entity, EntityContainer&& ec) {
199 GAIA_ASSERT(entity.pair());
200
201 if (contains(entity))
202 return false;
203
204 add(entity, GAIA_MOV(ec));
205 return true;
206 }
207
210 void remove(Entity entity) {
211 GAIA_ASSERT(entity.pair());
212 m_records.erase(EntityLookupKey(entity));
213 }
214
219 GAIA_NODISCARD bool remove(Entity entity, EntityContainer& ec) {
220 GAIA_ASSERT(entity.pair());
221
222 const auto it = m_records.find(EntityLookupKey(entity));
223 if (it == m_records.end())
224 return false;
225
226 ec = GAIA_MOV(it->second);
227 m_records.erase(it);
228 return true;
229 }
230
233 GAIA_NODISCARD auto begin() {
234 return m_records.begin();
235 }
236
239 GAIA_NODISCARD auto begin() const {
240 return m_records.begin();
241 }
242
245 GAIA_NODISCARD auto end() {
246 return m_records.end();
247 }
248
251 GAIA_NODISCARD auto end() const {
252 return m_records.end();
253 }
254 };
255
256 struct EntityContainers {
257 private:
259 PairRecords m_pairRecords;
260
261 public:
264 cnt::paged_ilist<EntityContainer, Entity> entities;
265
267 void pair_records_clear() {
268 m_pairRecords.clear();
269 }
270
274 GAIA_NODISCARD bool pair_record_contains(Entity entity) const {
275 return m_pairRecords.contains(entity);
276 }
277
281 GAIA_NODISCARD EntityContainer* pair_record_find(Entity entity) {
282 return m_pairRecords.find(entity);
283 }
284
288 GAIA_NODISCARD const EntityContainer* pair_record_find(Entity entity) const {
289 return m_pairRecords.find(entity);
290 }
291
295 void pair_record_add(Entity entity, EntityContainer&& ec) {
296 m_pairRecords.add(entity, GAIA_MOV(ec));
297 }
298
303 GAIA_NODISCARD bool pair_record_try_add(Entity entity, EntityContainer&& ec) {
304 return m_pairRecords.try_add(entity, GAIA_MOV(ec));
305 }
306
309 void pair_record_remove(Entity entity) {
310 m_pairRecords.remove(entity);
311 }
312
317 GAIA_NODISCARD bool pair_record_remove(Entity entity, EntityContainer& ec) {
318 return m_pairRecords.remove(entity, ec);
319 }
320
323 GAIA_NODISCARD auto pair_record_begin() {
324 return m_pairRecords.begin();
325 }
326
329 GAIA_NODISCARD auto pair_record_begin() const {
330 return m_pairRecords.begin();
331 }
332
335 GAIA_NODISCARD auto pair_record_end() {
336 return m_pairRecords.end();
337 }
338
341 GAIA_NODISCARD auto pair_record_end() const {
342 return m_pairRecords.end();
343 }
344
345 EntityContainer& operator[](Entity entity) {
346 if (!entity.pair())
347 return entities[entity.id()];
348
349 auto* pPair = m_pairRecords.find(entity);
350 GAIA_ASSERT(pPair != nullptr);
351 return *pPair;
352 }
353
354 const EntityContainer& operator[](Entity entity) const {
355 if (!entity.pair())
356 return entities[entity.id()];
357
358 const auto* pPair = m_pairRecords.find(entity);
359 GAIA_ASSERT(pPair != nullptr);
360 return *pPair;
361 }
362 };
363
364 } // namespace ecs
365
366 namespace cnt {
367 template <>
368 struct ilist_handle_traits<ecs::Entity> {
369 static ecs::Entity make(uint32_t id, uint32_t gen, const ecs::Entity& prev) {
370 return ecs::Entity((ecs::EntityId)id, gen, prev.entity(), prev.pair());
371 }
372 };
373 } // namespace cnt
374
375 namespace ecs {
376
377#if GAIA_USE_SAFE_ENTITY
381 class SafeEntity {
382 World* m_w = nullptr;
383 Entity m_entity;
384
385 public:
386 SafeEntity() = default;
387 SafeEntity(World& w, Entity entity): m_w(&w), m_entity(entity) {
388 acquire();
389 }
390
391 ~SafeEntity() {
392 release();
393 }
394
395 SafeEntity(const SafeEntity& other): m_w(other.m_w), m_entity(other.m_entity) {
396 acquire();
397 }
398 SafeEntity& operator=(const SafeEntity& other) {
399 GAIA_ASSERT(core::addressof(other) != this);
400
401 release();
402 m_w = other.m_w;
403 m_entity = other.m_entity;
404 acquire();
405 return *this;
406 }
407
408 SafeEntity(SafeEntity&& other) noexcept: m_w(other.m_w), m_entity(other.m_entity) {
409 other.m_w = nullptr;
410 other.m_entity = EntityBad;
411 }
412 SafeEntity& operator=(SafeEntity&& other) noexcept {
413 GAIA_ASSERT(core::addressof(other) != this);
414
415 release();
416 m_w = other.m_w;
417 m_entity = other.m_entity;
418
419 other.m_w = nullptr;
420 other.m_entity = EntityBad;
421 return *this;
422 }
423
424 private:
426 void acquire() {
427 if GAIA_UNLIKELY (m_w == nullptr)
428 return;
429
430 safe_entity_acquire(*m_w, m_entity);
431 }
432
435 void release() {
436 if GAIA_UNLIKELY (m_w == nullptr)
437 return;
438
439 // World cleanup and destruction clear every record before they destroy components,
440 // so a SafeEntity stored in a component finds no record there and has nothing to release.
441 safe_entity_release(*m_w, m_entity);
442 }
443
444 public:
445 template <typename Serializer>
446 void save(Serializer& s) const {
447 s.save(m_entity);
448 }
449 template <typename Serializer>
450 void load(Serializer& s) {
451 s.load(m_entity);
452 }
453
454 GAIA_NODISCARD Entity entity() const noexcept {
455 return m_entity;
456 }
457 GAIA_NODISCARD operator Entity() const noexcept {
458 return m_entity;
459 }
460
461 bool operator==(const SafeEntity& other) const noexcept {
462 return m_entity == other.entity();
463 }
464 };
465
466 inline bool operator==(const SafeEntity& e1, Entity e2) noexcept {
467 return e1.entity() == e2;
468 }
469 inline bool operator==(Entity e1, const SafeEntity& e2) noexcept {
470 return e1 == e2.entity();
471 }
472
473 inline bool operator!=(const SafeEntity& e1, Entity e2) noexcept {
474 return e1.entity() != e2;
475 }
476 inline bool operator!=(Entity e1, const SafeEntity& e2) noexcept {
477 return e1 != e2.entity();
478 }
479#endif
480
481#if GAIA_USE_WEAK_ENTITY
482 class WeakEntity;
483
484 struct WeakEntityTracker {
485 GAIA_USE_SMALLBLOCK(WeakEntityTracker)
486
487 WeakEntityTracker* next = nullptr;
488 WeakEntityTracker* prev = nullptr;
489 WeakEntity* pWeakEntity = nullptr;
490 };
491
502 class WeakEntity {
503 friend class World;
504 friend struct WeakEntityTracker;
505
506 World* m_w = nullptr;
507 WeakEntityTracker* m_pTracker = nullptr;
508 Entity m_entity;
509
510 public:
511 WeakEntity() = default;
512 WeakEntity(World& w, Entity entity): m_w(&w), m_pTracker(new WeakEntityTracker()), m_entity(entity) {
513 set_tracker();
514 }
515 ~WeakEntity() {
516 del_tracker();
517 }
518
519 WeakEntity(const WeakEntity& other): m_w(other.m_w), m_entity(other.m_entity) {
520 if (other.m_pTracker == nullptr)
521 return;
522
523 m_pTracker = new WeakEntityTracker();
524 set_tracker();
525 }
526 WeakEntity& operator=(const WeakEntity& other) {
527 GAIA_ASSERT(core::addressof(other) != this);
528
529 del_tracker();
530
531 m_w = other.m_w;
532 m_entity = other.m_entity;
533
534 if (other.m_pTracker != nullptr) {
535 m_pTracker = new WeakEntityTracker();
536 set_tracker();
537 }
538
539 return *this;
540 }
541
542 WeakEntity(WeakEntity&& other) noexcept: m_w(other.m_w), m_pTracker(other.m_pTracker), m_entity(other.m_entity) {
543 other.m_w = nullptr;
544 if (other.m_pTracker != nullptr)
545 other.m_pTracker->pWeakEntity = this;
546 other.m_pTracker = nullptr;
547 other.m_entity = EntityBad;
548 }
549 WeakEntity& operator=(WeakEntity&& other) noexcept {
550 GAIA_ASSERT(core::addressof(other) != this);
551
552 del_tracker();
553
554 m_w = other.m_w;
555 m_pTracker = other.m_pTracker;
556 m_entity = other.m_entity;
557
558 other.m_w = nullptr;
559 if (other.m_pTracker != nullptr)
560 other.m_pTracker->pWeakEntity = this;
561 other.m_pTracker = nullptr;
562 other.m_entity = EntityBad;
563 return *this;
564 }
565
566 void set_tracker() {
567 GAIA_ASSERT(m_pTracker != nullptr);
568 GAIA_ASSERT(m_w != nullptr);
569 GAIA_ASSERT(m_entity != EntityBad);
570
571 m_pTracker->pWeakEntity = this;
572 m_pTracker->prev = nullptr;
573
574 auto& ec = fetch_mut(*m_w, m_entity);
575 m_pTracker->next = ec.pWeakTracker;
576 if (ec.pWeakTracker != nullptr)
577 ec.pWeakTracker->prev = m_pTracker;
578 ec.pWeakTracker = m_pTracker;
579 }
580
581 void del_tracker() {
582 if (m_pTracker == nullptr)
583 return;
584
585 if (m_pTracker->next != nullptr)
586 m_pTracker->next->prev = m_pTracker->prev;
587 if (m_pTracker->prev != nullptr)
588 m_pTracker->prev->next = m_pTracker->next;
589
590 // A linked tracker means the target's record still owns this list. That holds while the target
591 // is being deleted, when valid() already fails: deletion destroys the target's components
592 // (a self reference among them) before it invalidates the list. Only the head needs the
593 // record. World cleanup releases every tracker before it destroys components, so none is
594 // linked there.
595 if (m_pTracker->prev == nullptr && m_w != nullptr) {
596 auto* pEc = try_fetch_mut(*m_w, m_entity);
597 if (pEc != nullptr && pEc->pWeakTracker == m_pTracker)
598 pEc->pWeakTracker = m_pTracker->next;
599 }
600
601 delete m_pTracker;
602 m_pTracker = nullptr;
603 }
604
605 template <typename Serializer>
606 void save(Serializer& s) const {
607 s.save(m_entity.val);
608 }
609 template <typename Serializer>
610 void load(Serializer& s) {
611 del_tracker();
612 Identifier id{};
613 s.load(id);
614 m_entity = Entity(id);
615 if (m_w != nullptr && m_entity != EntityBad) {
616 m_pTracker = new WeakEntityTracker();
617 set_tracker();
618 }
619 }
620
621 GAIA_NODISCARD Entity entity() const noexcept {
622 return m_entity;
623 }
624 GAIA_NODISCARD operator Entity() const noexcept {
625 return entity();
626 }
627
628 bool operator==(const WeakEntity& other) const noexcept {
629 return m_entity == other.entity();
630 }
631 };
632
633 inline bool operator==(const WeakEntity& e1, Entity e2) noexcept {
634 return e1.entity() == e2;
635 }
636 inline bool operator==(Entity e1, const WeakEntity& e2) noexcept {
637 return e1 == e2.entity();
638 }
639
640 inline bool operator!=(const WeakEntity& e1, Entity e2) noexcept {
641 return e1.entity() != e2;
642 }
643 inline bool operator!=(Entity e1, const WeakEntity& e2) noexcept {
644 return e1 != e2.entity();
645 }
646#endif
647 } // namespace ecs
648
649 namespace cnt {
650 inline page_storage_id to_page_storage_id<ecs::EntityContainer>::get(const ecs::EntityContainer& item) noexcept {
651 return item.idx;
652 }
653 } // namespace cnt
654} // namespace gaia
void load(Reader &reader, T &data)
Read data using Reader at compile-time.
Definition ser_ct.h:112
void save(Writer &writer, const T &data)
Write data using Writer at compile-time.
Definition ser_ct.h:101
static TItemHandle make(uint32_t id, uint32_t gen, const TItemHandle &prev)
Rebuilds a handle after its generation changes.
Definition ilist.h:497
static page_storage_id get(const T &item) noexcept
Applies the default conversion for an item.
Definition paged_storage.h:47