Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
archetype.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cinttypes>
5#include <cstdint>
6
7#include "gaia/cnt/darray.h"
8// #include "gaia/cnt/dbitset.h"
9#include "gaia/core/hashing_policy.h"
10#include "gaia/core/utility.h"
11#include "gaia/ecs/api.h"
12#include "gaia/ecs/archetype_common.h"
13#include "gaia/ecs/archetype_graph.h"
14#include "gaia/ecs/chunk.h"
15#include "gaia/ecs/chunk_allocator.h"
16#include "gaia/ecs/chunk_header.h"
17#include "gaia/ecs/component.h"
18#include "gaia/ecs/component_cache.h"
19#include "gaia/ecs/id.h"
20#include "gaia/ecs/query_mask.h"
21#include "gaia/mem/mem_alloc.h"
22#include "gaia/ser/ser_binary.h"
23
24namespace gaia {
25 namespace ecs {
26 class World;
27 class Archetype;
28 struct EntityContainer;
29
31 namespace detail {
32 GAIA_NODISCARD inline bool cmp_comps(EntitySpan comps, EntitySpan compsOther) {
33 const auto s0 = comps.size();
34 const auto s1 = compsOther.size();
35
36 // Size has to match
37 if (s0 != s1)
38 return false;
39
40 // Elements have to match
41 GAIA_FOR(s0) {
42 if (comps[i] != compsOther[i])
43 return false;
44 }
45
46 return true;
47 }
48 } // namespace detail
50
57
58 GAIA_NODISCARD bool operator==(const ArchetypeChunkPair& other) const {
59 return pArchetype == other.pArchetype && pChunk == other.pChunk;
60 }
61 };
62
65 protected:
67 ArchetypeId m_archetypeId = ArchetypeIdBad;
68
69 public:
72 GAIA_NODISCARD ArchetypeId id() const {
73 return m_archetypeId;
74 }
75 };
76
79 friend class Archetype;
80
82 EntitySpan m_comps;
83
84 public:
85 ArchetypeLookupChecker(EntitySpan comps): m_comps(comps) {}
86
90 GAIA_NODISCARD bool cmp_comps(const ArchetypeLookupChecker& other) const {
91 return detail::cmp_comps(m_comps, other.m_comps);
92 }
93 };
94
97 class GAIA_API Archetype final: public ArchetypeBase {
98 public:
101
103 static constexpr uint16_t ARCHETYPE_LIFESPAN_BITS = 7;
105 static_assert(ARCHETYPE_LIFESPAN_BITS >= ChunkHeader::CHUNK_LIFESPAN_BITS);
107 static constexpr uint16_t MAX_ARCHETYPE_LIFESPAN = (1 << ARCHETYPE_LIFESPAN_BITS) - 1;
108
110 struct Properties {
112 uint16_t capacity;
114 ChunkDataOffset chunkDataBytes;
116 uint8_t cntEntities;
117 };
118
119 private:
120 GAIA_NODISCARD static Component empty_comp() noexcept {
121 return Component(IdentifierIdBad, 0, 0, 0, DataStorageType::Table);
122 }
123
124 GAIA_NODISCARD static Component comp_from_item(Entity term, const ComponentCacheItem* pItem) noexcept {
125 return pItem == nullptr ? empty_comp() : archetype_component(term, pItem->comp);
126 }
127
128 struct ShapeData {
129 ArchetypeIdLookupKey::LookupHash archetypeIdHash;
131 LookupHash hashLookup = {0};
133 QueryMask queryMask{};
134 Properties properties{};
136 ChunkDataOffsets dataOffsets{};
138 Entity ids[ChunkHeader::MAX_COMPONENTS];
140 const ComponentCacheItem* compItems[ChunkHeader::MAX_COMPONENTS]{};
142 ChunkDataOffset compOffs[ChunkHeader::MAX_COMPONENTS];
143 };
144
145 struct StorageData {
147 cnt::darray<Chunk*> chunks;
149 uint32_t firstFreeChunkIdx = 0;
150 };
151
152 struct RuntimeData {
154 uint32_t listIdx = BadIndex;
156 uint8_t observedTermCnt = 0;
157
159 uint32_t deleteReq : 1;
161 uint32_t dead : 1;
163 uint32_t lifespanCountdownMax : ARCHETYPE_LIFESPAN_BITS;
165 uint32_t lifespanCountdown : ARCHETYPE_LIFESPAN_BITS;
166
167 RuntimeData(): deleteReq(0), dead(0), lifespanCountdownMax(1), lifespanCountdown(0) {}
168 };
169
170 struct EdgeData {
172 ArchetypeGraph graph;
173 };
174
175 struct PairIndexData {
179 EntityId id;
181 uint8_t start;
183 uint8_t count;
184 };
185
186 PairIndexData() noexcept {}
187
189 uint8_t pairIndexBuffer[ChunkHeader::MAX_COMPONENTS];
191 uint8_t pairsAsIndexBuffer[ChunkHeader::MAX_COMPONENTS];
192 uint8_t pairRelIndexBuffer[ChunkHeader::MAX_COMPONENTS];
193 uint8_t pairTgtIndexBuffer[ChunkHeader::MAX_COMPONENTS];
195 PairCountBucket pairRelCountBuffer[ChunkHeader::MAX_COMPONENTS];
196 PairCountBucket pairTgtCountBuffer[ChunkHeader::MAX_COMPONENTS];
198 uint8_t pairCnt = 0;
200 uint8_t pairCntIs = 0;
202 uint8_t pairRelCountCnt = 0;
204 uint8_t pairTgtCountCnt = 0;
205
206 static PairIndexData* create(EntitySpan ids) {
207 auto* pPairIndex = mem::AllocHelper::alloc<PairIndexData>("ArchetypePairIndex");
208 (void)new (pPairIndex) PairIndexData();
209
210 uint8_t pairRelIndexCnt = 0;
211 uint8_t pairTgtIndexCnt = 0;
212
213 GAIA_FOR(ids.size()) {
214 if (!ids[i].pair())
215 continue;
216
217 pPairIndex->pairIndexBuffer[pPairIndex->pairCnt] = (uint8_t)i;
218 ++pPairIndex->pairCnt;
219 add_pair_index_bucket(
220 pPairIndex->pairRelCountBuffer, pPairIndex->pairRelCountCnt, pPairIndex->pairRelIndexBuffer,
221 pairRelIndexCnt, (EntityId)ids[i].id(), (uint8_t)i);
222 add_pair_index_bucket(
223 pPairIndex->pairTgtCountBuffer, pPairIndex->pairTgtCountCnt, pPairIndex->pairTgtIndexBuffer,
224 pairTgtIndexCnt, (EntityId)ids[i].gen(), (uint8_t)i);
225
226 if (ids[i].id() == Is.id())
227 pPairIndex->pairsAsIndexBuffer[pPairIndex->pairCntIs++] = (uint8_t)i;
228 }
229
230 if (pPairIndex->pairCnt == 0) {
231 destroy(pPairIndex);
232 return nullptr;
233 }
234
235 return pPairIndex;
236 }
237
238 static void destroy(PairIndexData* pPairIndex) {
239 if (pPairIndex == nullptr)
240 return;
241
242 pPairIndex->~PairIndexData();
243 mem::AllocHelper::free("ArchetypePairIndex", pPairIndex);
244 }
245
246 static PairCountBucket&
247 ensure_pair_index_bucket(PairCountBucket* pBuckets, uint8_t& bucketCnt, EntityId id, uint8_t start) {
248 GAIA_FOR(bucketCnt) {
249 if (pBuckets[i].id == id)
250 return pBuckets[i];
251 }
252
253 GAIA_ASSERT(bucketCnt < ChunkHeader::MAX_COMPONENTS);
254 auto& bucket = pBuckets[bucketCnt++];
255 bucket.id = id;
256 bucket.start = start;
257 bucket.count = 0;
258 return bucket;
259 }
260
261 static void add_pair_index_bucket(
262 PairCountBucket* pBuckets, uint8_t& bucketCnt, uint8_t* pIndexBuffer, uint8_t& indexCnt, EntityId id,
263 uint8_t idsIdx) {
264 auto& bucket = ensure_pair_index_bucket(pBuckets, bucketCnt, id, indexCnt);
265 GAIA_ASSERT(indexCnt < ChunkHeader::MAX_COMPONENTS);
266 pIndexBuffer[indexCnt++] = idsIdx;
267 ++bucket.count;
268 }
269
270 static uint32_t pair_count_from_buckets(const PairCountBucket* pBuckets, uint8_t bucketCnt, EntityId id) {
271 GAIA_FOR(bucketCnt) {
272 if (pBuckets[i].id == id)
273 return pBuckets[i].count;
274 }
275
276 return 0;
277 }
278
279 GAIA_NODISCARD static std::span<const uint8_t> pair_indices_from_buckets(
280 const PairCountBucket* pBuckets, uint8_t bucketCnt, const uint8_t* pIndexBuffer, EntityId id) {
281 GAIA_FOR(bucketCnt) {
282 if (pBuckets[i].id != id)
283 continue;
284
285 return {pIndexBuffer + pBuckets[i].start, pBuckets[i].count};
286 }
287
288 return {};
289 }
290
291 GAIA_NODISCARD uint32_t pair_matches(EntitySpan ids, Entity pair) const {
292 GAIA_ASSERT(pair.pair());
293
294 if (pair == Pair(All, All))
295 return pairCnt;
296
297 if (pair.id() == All.id())
298 return pair_count_from_buckets(pairTgtCountBuffer, pairTgtCountCnt, (EntityId)pair.gen());
299
300 if (pair.gen() == All.id())
301 return pair_count_from_buckets(pairRelCountBuffer, pairRelCountCnt, (EntityId)pair.id());
302
303 return core::has_if(
304 ids,
305 [pair](Entity entity) {
306 return entity == pair;
307 })
308 ? 1u
309 : 0u;
310 }
311
312 GAIA_NODISCARD Entity entity_from_pairs_as_idx(EntitySpan ids, uint32_t idx) const {
313 const auto idsIdx = pairsAsIndexBuffer[idx];
314 return ids[idsIdx];
315 }
316
317 GAIA_NODISCARD std::span<const uint8_t> pair_indices() const {
318 return {&pairIndexBuffer[0], pairCnt};
319 }
320
321 GAIA_NODISCARD std::span<const uint8_t> pair_rel_indices(Entity relation) const {
322 return pair_indices_from_buckets(
323 pairRelCountBuffer, pairRelCountCnt, pairRelIndexBuffer, (EntityId)relation.id());
324 }
325
326 GAIA_NODISCARD std::span<const uint8_t> pair_tgt_indices(Entity target) const {
327 return pair_indices_from_buckets(
328 pairTgtCountBuffer, pairTgtCountCnt, pairTgtIndexBuffer, (EntityId)target.id());
329 }
330 };
331
333 const World& m_world;
335 const ComponentCache& m_cc;
337 uint32_t& m_worldVersion;
338
339 ShapeData m_shape{};
340 StorageData m_storage{};
342 // cnt::dbitset m_disabledMask;
343 EdgeData m_edges{};
344 RuntimeData m_runtime{};
345 PairIndexData* m_pPairIndex = nullptr;
346
348 Archetype(const World& world, const ComponentCache& cc, uint32_t& worldVersion):
349 m_world(world), m_cc(cc), m_worldVersion(worldVersion) //
350 {}
351
352 ~Archetype() {
353 // Delete all archetype chunks
354 for (auto* pChunk: m_storage.chunks)
355 Chunk::free(pChunk);
356 PairIndexData::destroy(m_pPairIndex);
357 }
358
362 void update_data_offsets() {
363 uintptr_t offset = 0;
364
365 // Versions
366 // We expect versions to fit in the first 256 bytes.
367 // With 32 components per archetype this gives us some headroom.
368 {
369 const auto cnt = (uint32_t)m_shape.properties.cntEntities + 1U; // + 1 for entities
370 GAIA_ASSERT(offset < 256);
371 m_shape.dataOffsets.firstByte_Versions = (ChunkDataVersionOffset)offset;
372 offset += sizeof(ComponentVersion) * cnt;
373 }
374
375 // Entity ids
376 {
377 offset += mem::padding<alignof(Entity)>(offset);
378
379 const auto cnt = m_shape.properties.cntEntities;
380 if (cnt != 0) {
381 m_shape.dataOffsets.firstByte_CompEntities = (ChunkDataOffset)offset;
382
383 // Storage-wise, treat the component array as it it were MAX_COMPONENTS long.
384 offset += sizeof(Entity) * ChunkHeader::MAX_COMPONENTS;
385 }
386 }
387
388 // Component records
389 {
390 offset += mem::padding<alignof(ComponentRecord)>(offset);
391
392 const auto cnt = m_shape.properties.cntEntities;
393 if (cnt != 0) {
394
395 m_shape.dataOffsets.firstByte_Records = (ChunkDataOffset)offset;
396
397 // Storage-wise, treat the component array as it it were MAX_COMPONENTS long.
398 offset += sizeof(ComponentRecord) * cnt;
399 }
400 }
401
402 // First entity offset
403 {
404 offset += mem::padding<alignof(Entity)>(offset);
405 m_shape.dataOffsets.firstByte_EntityData = (ChunkDataOffset)offset;
406 }
407 }
408
417 static bool est_max_entities_per_chunk(
418 uint32_t offs, const Entity* ids, const ComponentCacheItem* const* pItems, uint32_t cnt, uint32_t cap,
419 uint32_t maxDataOffset) {
420 GAIA_FOR(cnt) {
421 const auto comp = comp_from_item(ids[i], pItems[i]);
422 if (!component_uses_table_storage(comp))
423 continue;
424
425 const auto* pItem = pItems[i];
426 GAIA_ASSERT(pItem != nullptr);
427
428 // If we're beyond what the chunk could take, subtract one entity
429 offs = pItem->calc_new_mem_offset(offs, cap);
430 if (offs >= maxDataOffset)
431 return false;
432 }
433
434 return true;
435 }
436
437 static void reg_components(
438 Archetype& arch, EntitySpan ids, const ComponentCacheItem* const* pItems, uint8_t from, uint8_t to,
439 uint32_t& currOff, uint32_t count) {
440 auto& ofs = arch.m_shape.compOffs;
441
442 // Set component ids
443 GAIA_FOR2(from, to) arch.m_shape.ids[i] = ids[i];
444
445 // Calculate offsets and assign them indices according to our mappings
446 GAIA_FOR2(from, to) {
447 const auto comp = comp_from_item(ids[i], pItems[i]);
448 const auto compIdx = i;
449
450 if (!component_uses_table_storage(comp)) {
451 ofs[compIdx] = {};
452 } else {
453 const auto alig = comp.alig();
454 // Offsets are relative to the chunk data area which is only MemoryBlockAlignment-aligned
455 GAIA_ASSERT(alig <= MemoryBlockAlignment);
456 currOff = mem::align(currOff, alig);
457 ofs[compIdx] = (ChunkDataOffset)currOff;
458
459 currOff = pItems[i]->calc_new_mem_offset(currOff, count);
460 }
461 }
462 }
463
464 public:
465 Archetype(Archetype&&) = delete;
466 Archetype(const Archetype&) = delete;
467 Archetype& operator=(Archetype&&) = delete;
468 Archetype& operator=(const Archetype&) = delete;
469
473 s.save(m_storage.firstFreeChunkIdx);
474 s.save(m_runtime.listIdx);
475
476 s.save((uint32_t)m_storage.chunks.size());
477 for (auto* pChunk: m_storage.chunks) {
478 s.save(pChunk->idx());
479 pChunk->save(s);
480 }
481 }
482
486 s.load(m_storage.firstFreeChunkIdx);
487 s.load(m_runtime.listIdx);
488
489 uint32_t chunkCnt = 0;
490 s.load(chunkCnt);
491 m_storage.chunks.resize(chunkCnt, nullptr);
492
493 GAIA_FOR(chunkCnt) {
494 uint32_t chunkIdx = 0;
495 s.load(chunkIdx);
496
497 auto* pChunk = m_storage.chunks[chunkIdx];
498 // If the chunk doesn't exist it means it's not a part of the initial setup.
499 if (pChunk == nullptr) {
500 pChunk = Chunk::create(
501 m_world, m_cc, chunkIdx, //
502 m_shape.properties.capacity, m_shape.properties.cntEntities, //
503 m_shape.properties.chunkDataBytes, //
504 m_worldVersion, m_shape.dataOffsets, m_shape.ids, m_shape.compItems, m_shape.compOffs);
505 m_storage.chunks[chunkIdx] = pChunk;
506 }
507
508 pChunk->set_idx(chunkIdx);
509 pChunk->load(s);
510 }
511 }
512
515 void list_idx(uint32_t idx) {
516 m_runtime.listIdx = idx;
517 }
518
521 uint32_t list_idx() const {
522 return m_runtime.listIdx;
523 }
524
528 GAIA_NODISCARD bool cmp_comps(const ArchetypeLookupChecker& other) const {
529 return detail::cmp_comps(ids_view(), other.m_comps);
530 }
531
538 GAIA_NODISCARD static Archetype*
539 create(const World& world, ArchetypeId archetypeId, uint32_t& worldVersion, EntitySpan ids) {
540 const auto& cc = comp_cache(world);
541
542 auto* newArch = mem::AllocHelper::alloc<Archetype>("Archetype");
543 (void)new (newArch) Archetype(world, cc, worldVersion);
544
545 newArch->m_archetypeId = archetypeId;
546 newArch->m_shape.archetypeIdHash = ArchetypeIdLookupKey::calc(archetypeId);
547
548 // Calculate component mask. This will be used to early exit matching archetypes in simple queries.
549 // TODO: Performance could be improved if we're an archetype comming from one already known.
550 // We could simply take the predecessor's mask and update it with the new ids.
551 newArch->m_shape.queryMask = build_entity_mask({ids.data(), ids.size()});
552
553 const auto cnt = (uint32_t)ids.size();
554 newArch->m_shape.properties.cntEntities = (uint8_t)ids.size();
555
556 auto compItems = std::span(&newArch->m_shape.compItems[0], cnt);
557 GAIA_FOR(cnt) compItems[i] = ids[i].pair() ? cc.find_pair_payload(ids[i]) : cc.find(ids[i]);
558
559 // Calculate offsets
560 newArch->update_data_offsets();
561 const auto& offs = newArch->m_shape.dataOffsets;
562 newArch->m_pPairIndex = PairIndexData::create(ids);
563
564 uint32_t compsSize = 0;
565 GAIA_FOR(cnt)
566 compsSize += comp_from_item(ids[i], newArch->m_shape.compItems[i]).size();
567
568 auto compute_max_entities_for_chunk = [&](uint32_t maxEntities, uint32_t dataLimit) -> uint32_t {
569 uint32_t low = 1;
570 uint32_t high = maxEntities;
571 uint32_t best = 1;
572
573 // Helper to test if a given entity count fits in the chunk
574 auto try_fit = [&](uint32_t count) -> bool {
575 const uint32_t currOff = offs.firstByte_EntityData + (count * sizeof(Entity));
576 return est_max_entities_per_chunk(currOff, ids.data(), newArch->m_shape.compItems, cnt, count, dataLimit);
577 };
578
579 // Binary search for the lookup
580 while (low <= high) {
581 uint32_t mid = (low + high) / 2;
582 if (try_fit(mid)) {
583 best = mid;
584 low = mid + 1;
585 } else {
586 high = mid - 1;
587 }
588 }
589
590 return best;
591 };
592
593 // Calculate the number of entities per chunk precisely so we can fit as many of them into a
594 // chunk as possible. Start at the smallest size class and only upsize when the archetype is
595 // wide enough that a smaller chunk can't hold the target row count.
596 constexpr uint32_t MinEntitiesPerChunk = 1536;
597 uint32_t maxItemsInArchetype = 0;
598
599 auto compute_max_entities_for_size_type = [&](uint32_t sizeType) -> uint32_t {
600 const uint32_t dataLimit = Chunk::chunk_data_bytes(mem_block_size(sizeType));
601 const uint32_t fixedSize = offs.firstByte_EntityData + 1;
602 GAIA_ASSERT(dataLimit > fixedSize);
603
604 // Theoretical maximum number of component rows we can fit into one chunk.
605 // This can be further reduced due to alignment and padding.
606 const uint32_t itemSize = compsSize + (uint32_t)sizeof(Entity);
607 const uint32_t maxEntities = (dataLimit - fixedSize) / itemSize;
608 return compute_max_entities_for_chunk(maxEntities > 0 ? maxEntities : 1, dataLimit);
609 };
610
611 if (archetypeId == 0) {
612 // Keep the root archetype compact enough to avoid growing the maximum row snapshot used by iterators.
613 maxItemsInArchetype = compute_max_entities_for_size_type(2);
614 } else {
615 for (uint32_t sizeType = 0; sizeType < MemoryBlockSizeClasses; ++sizeType) {
616 maxItemsInArchetype = compute_max_entities_for_size_type(sizeType);
617 if (maxItemsInArchetype >= MinEntitiesPerChunk)
618 break;
619 }
620 }
621
622 // MAX_CHUNK_ENTITIES is intentionally based on the 32 KiB class to keep iterator snapshots bounded.
623 // Wide archetypes can still use the 64 KiB-class blocks because their row count stays below this cap.
624 if (maxItemsInArchetype > ChunkHeader::MAX_CHUNK_ENTITIES)
625 maxItemsInArchetype = ChunkHeader::MAX_CHUNK_ENTITIES;
626
627 // Update the offsets according to the recalculated maxItemsInArchetype
628 auto currOff = offs.firstByte_EntityData + ((uint32_t)sizeof(Entity) * maxItemsInArchetype);
629 reg_components(
630 *newArch, ids, newArch->m_shape.compItems, (uint8_t)0, (uint8_t)ids.size(), currOff, maxItemsInArchetype);
631
632 newArch->m_shape.properties.capacity = (uint16_t)maxItemsInArchetype;
633 newArch->m_shape.properties.chunkDataBytes = (ChunkDataOffset)currOff;
634
635 return newArch;
636 }
637
640 void static destroy(Archetype* pArchetype) {
641 GAIA_ASSERT(pArchetype != nullptr);
642 pArchetype->~Archetype();
643 mem::AllocHelper::free("Archetype", pArchetype);
644 }
645
648 QueryMask queryMask() const {
649 return m_shape.queryMask;
650 }
651
654 ArchetypeIdLookupKey::LookupHash id_hash() const {
655 return m_shape.archetypeIdHash;
656 }
657
660 void set_hashes(LookupHash hashLookup) {
661 m_shape.hashLookup = hashLookup;
662 }
663
669 void enable_entity(Chunk* pChunk, uint16_t row, bool enableEntity, EntityContainers& recs) {
670 pChunk->enable_entity(row, enableEntity, recs);
671 // m_disabledMask.set(pChunk->idx(), enableEntity ? true : pChunk->has_disabled_entities());
672 }
673
676 void del(Chunk* pChunk) {
677 // Make sure there are any chunks to delete
678 GAIA_ASSERT(!m_storage.chunks.empty());
679
680 const auto chunkIndex = pChunk->idx();
681
682 // Make sure the chunk is a part of the chunk array
683 GAIA_ASSERT(chunkIndex == core::get_index(m_storage.chunks, pChunk));
684
685 // Remove the chunk from the chunk array. We are swapping this chunk's entry
686 // with the last one in the array. Therefore, we first update the last item's
687 // index with the current chunk's index and then do the swapping.
688 m_storage.chunks.back()->set_idx(chunkIndex);
689 core::swap_erase(m_storage.chunks, chunkIndex);
690
691 // Delete the chunk now. Otherwise, if the chunk happened to be the last
692 // one we would end up overriding released memory.
693 Chunk::free(pChunk);
694 }
695
700 GAIA_NODISCARD Chunk* foc_free_chunk() {
701 const auto chunkCnt = m_storage.chunks.size();
702
703 if (chunkCnt > 0) {
704 for (uint32_t i = m_storage.firstFreeChunkIdx; i < m_storage.chunks.size(); ++i) {
705 auto* pChunk = m_storage.chunks[i];
706 GAIA_ASSERT(pChunk != nullptr);
707 const auto entityCnt = pChunk->size();
708 if (entityCnt < pChunk->capacity()) {
709 m_storage.firstFreeChunkIdx = i;
710 return pChunk;
711 }
712 }
713 }
714
715 // Make sure not too many chunks are allocated
716 GAIA_ASSERT(chunkCnt < UINT32_MAX);
717
718 // No free space found anywhere. Let's create a new chunk.
719 auto* pChunk = Chunk::create(
720 m_world, m_cc, chunkCnt, //
721 m_shape.properties.capacity, m_shape.properties.cntEntities, //
722 m_shape.properties.chunkDataBytes, //
723 m_worldVersion, m_shape.dataOffsets, m_shape.ids, m_shape.compItems, m_shape.compOffs);
724
725 m_storage.firstFreeChunkIdx = m_storage.chunks.size();
726 m_storage.chunks.push_back(pChunk);
727 return pChunk;
728 }
729
734 // This is expected to be called only if there are any chunks
735 GAIA_ASSERT(!m_storage.chunks.empty());
736
737 auto* pChunk = m_storage.chunks[m_storage.firstFreeChunkIdx];
738 if (pChunk->size() >= pChunk->capacity())
739 ++m_storage.firstFreeChunkIdx;
740 }
741
745 void try_update_free_chunk_idx(Chunk& chunkThatRemovedEntity) {
746 // This is expected to be called only if there are any chunks
747 GAIA_ASSERT(!m_storage.chunks.empty());
748
749 if (chunkThatRemovedEntity.idx() == m_storage.firstFreeChunkIdx)
750 return;
751
752 if (chunkThatRemovedEntity.idx() < m_storage.firstFreeChunkIdx) {
753 m_storage.firstFreeChunkIdx = chunkThatRemovedEntity.idx();
754 return;
755 }
756
757 auto* pChunk = m_storage.chunks[m_storage.firstFreeChunkIdx];
758 if (pChunk->size() >= pChunk->capacity())
759 ++m_storage.firstFreeChunkIdx;
760 }
761
766 void remove_entity_raw(Chunk& chunk, uint16_t row, EntityContainers& recs) {
767 chunk.remove_entity(row, recs);
768 try_update_free_chunk_idx(chunk);
769 }
770
775 void remove_entity(Chunk& chunk, uint16_t row, EntityContainers& recs) {
776 remove_entity_raw(chunk, row, recs);
777 chunk.update_versions();
778 }
779
782 GAIA_NODISCARD const Properties& props() const {
783 return m_shape.properties;
784 }
785
788 GAIA_NODISCARD const cnt::darray<Chunk*>& chunks() const {
789 return m_storage.chunks;
790 }
791
794 GAIA_NODISCARD LookupHash lookup_hash() const {
795 return m_shape.hashLookup;
796 }
797
800 GAIA_NODISCARD EntitySpan ids_view() const {
801 return {&m_shape.ids[0], m_shape.properties.cntEntities};
802 }
803
806 GAIA_NODISCARD ChunkDataOffsetSpan comp_offs_view() const {
807 return {&m_shape.compOffs[0], m_shape.properties.cntEntities};
808 }
809
812 GAIA_NODISCARD uint32_t pairs() const {
813 return m_pPairIndex != nullptr ? m_pPairIndex->pairCnt : 0;
814 }
815
818 GAIA_NODISCARD uint32_t pairs_is() const {
819 return m_pPairIndex != nullptr ? m_pPairIndex->pairCntIs : 0;
820 }
821
826 GAIA_NODISCARD uint32_t pair_matches(Entity pair) const {
827 return m_pPairIndex != nullptr ? m_pPairIndex->pair_matches(ids_view(), pair) : 0;
828 }
829
833 GAIA_NODISCARD Entity entity_from_pairs_as_idx(uint32_t idx) const {
834 GAIA_ASSERT(m_pPairIndex != nullptr);
835 return m_pPairIndex->entity_from_pairs_as_idx(ids_view(), idx);
836 }
837
840 GAIA_NODISCARD std::span<const uint8_t> pair_indices() const {
841 return m_pPairIndex != nullptr ? m_pPairIndex->pair_indices() : std::span<const uint8_t>{};
842 }
843
847 GAIA_NODISCARD std::span<const uint8_t> pair_rel_indices(Entity relation) const {
848 return m_pPairIndex != nullptr ? m_pPairIndex->pair_rel_indices(relation) : std::span<const uint8_t>{};
849 }
850
854 GAIA_NODISCARD std::span<const uint8_t> pair_tgt_indices(Entity target) const {
855 return m_pPairIndex != nullptr ? m_pPairIndex->pair_tgt_indices(target) : std::span<const uint8_t>{};
856 }
857
861 GAIA_NODISCARD bool has(Entity entity) const {
862 return core::has_if(ids_view(), [&](Entity e) {
863 return e == entity;
864 });
865 }
866
869 GAIA_ASSERT(m_runtime.observedTermCnt < m_shape.properties.cntEntities);
870 ++m_runtime.observedTermCnt;
871 }
872
875 GAIA_ASSERT(m_runtime.observedTermCnt > 0);
876 --m_runtime.observedTermCnt;
877 }
878
881 GAIA_NODISCARD bool has_observed_terms() const {
882 return m_runtime.observedTermCnt != 0;
883 }
884
888 template <typename T>
889 GAIA_NODISCARD bool has() const {
890 if constexpr (is_pair<T>::value) {
891 const auto rel = m_cc.get<typename T::rel>().entity;
892 const auto tgt = m_cc.get<typename T::tgt>().entity;
893 return has((Entity)Pair(rel, tgt));
894 } else {
895 const auto* pComp = m_cc.find<T>();
896 return pComp != nullptr && has(pComp->entity);
897 }
898 }
899
900 //----------------------------------------------------------------------
901
907 template <bool Enabled>
908 Entity get_flat_entity(size_t flatIdx) const {
909 size_t offset = 0;
910 for (const auto* pChunk: chunks()) {
911 if (pChunk->empty())
912 continue;
913
914 uint32_t cnt = 0;
915 if constexpr (Enabled) {
916 cnt = pChunk->size_enabled();
917 } else {
918 cnt = pChunk->size_disabled();
919 }
920
921 if (flatIdx < offset + cnt) {
922 if constexpr (Enabled) {
923 const auto idx = (uint32_t)(flatIdx - offset) + pChunk->size_disabled();
924 return pChunk->entity_view()[idx];
925 } else {
926 const auto idx = (uint32_t)(flatIdx - offset);
927 return pChunk->entity_view()[idx];
928 }
929 }
930
931 offset += cnt;
932 }
933
934 GAIA_ASSERT(false);
935 return EntityBad;
936 }
937
945 template <bool Enabled>
946 const void* get_flat_comp_ptr(uint32_t compIdx, size_t flatIdx, Entity& outEntity) const {
947 size_t offset = 0;
948 for (const auto* pChunk: chunks()) {
949 if (pChunk->empty())
950 continue;
951
952 uint32_t cnt = 0;
953 if constexpr (Enabled) {
954 cnt = pChunk->size_enabled();
955 } else {
956 cnt = pChunk->size_disabled();
957 }
958
959 if (flatIdx < offset + cnt) {
960 if constexpr (Enabled) {
961 const auto idx = (uint32_t)(flatIdx - offset) + pChunk->size_disabled();
962 const auto* pData = pChunk->comp_ptr(compIdx, idx);
963 outEntity = pChunk->entity_view()[idx];
964 return pData;
965 } else {
966 const auto idx = (uint32_t)(flatIdx - offset);
967 const auto* pData = pChunk->comp_ptr(compIdx, idx);
968 outEntity = pChunk->entity_view()[idx];
969 return pData;
970 }
971 }
972
973 offset += cnt;
974 }
975
976 GAIA_ASSERT(false);
977 return nullptr;
978 }
979
981 template <bool Enabled>
982 void sort_entities_inter(size_t low, size_t high, TSortByFunc func) {
983 while (low < high) {
984 const auto pivotIdx = low + (high - low) / 2;
985 Entity pivotEntity = get_flat_entity<Enabled>(pivotIdx);
986 if (pivotIdx != high) {
987 const Entity highEntity = get_flat_entity<Enabled>(high);
988 Chunk::swap_chunk_entities(const_cast<World&>(m_world), pivotEntity, highEntity);
989 }
990
991 size_t i = low;
992 for (size_t j = low; j < high; ++j) {
993 Entity jEntity = get_flat_entity<Enabled>(j);
994 if (func(m_world, &jEntity, &pivotEntity) < 0) {
995 if (i != j) {
996 Entity iEntity = get_flat_entity<Enabled>(i);
997 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, jEntity);
998 }
999 ++i;
1000 }
1001 }
1002
1003 {
1004 Entity iEntity = get_flat_entity<Enabled>(i);
1005 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, pivotEntity);
1006 }
1007
1010 if (i - low < high - i) {
1011 if (low < i)
1012 sort_entities_inter<Enabled>(low, i - 1, func);
1013 low = i + 1;
1014 } else {
1015 if (i < high)
1016 sort_entities_inter<Enabled>(i + 1, high, func);
1017 if (i == 0)
1018 return;
1019 high = i - 1;
1020 }
1021 }
1022 }
1023
1025 template <bool Enabled>
1027 const ComponentCacheItem* pItem, uint32_t compIdx, size_t low, size_t high, TSortByFunc func) {
1028 while (low < high) {
1029 const auto pivotIdx = low + (high - low) / 2;
1030 Entity pivotEntity;
1031 (void)get_flat_comp_ptr<Enabled>(compIdx, pivotIdx, pivotEntity);
1032 if (pivotIdx != high) {
1033 Entity highEntity;
1034 (void)get_flat_comp_ptr<Enabled>(compIdx, high, highEntity);
1035 Chunk::swap_chunk_entities(const_cast<World&>(m_world), pivotEntity, highEntity);
1036 }
1037 const void* pPivotData = get_flat_comp_ptr<Enabled>(compIdx, high, pivotEntity);
1038
1039 size_t i = low;
1040 for (size_t j = low; j < high; ++j) {
1041 Entity jEntity;
1042 const void* jData = get_flat_comp_ptr<Enabled>(compIdx, j, jEntity);
1043 if (func(m_world, jData, pPivotData) < 0) {
1044 if (i != j) {
1045 Entity iEntity;
1046 (void)get_flat_comp_ptr<Enabled>(compIdx, i, iEntity);
1047 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, jEntity);
1048 }
1049 ++i;
1050 }
1051 }
1052
1053 {
1054 Entity iEntity;
1055 (void)get_flat_comp_ptr<Enabled>(compIdx, i, iEntity);
1056 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, pivotEntity);
1057 }
1058
1059 if (i - low < high - i) {
1060 if (low < i)
1061 sort_entities_inter<Enabled>(pItem, compIdx, low, i - 1, func);
1062 low = i + 1;
1063 } else {
1064 if (i < high)
1065 sort_entities_inter<Enabled>(pItem, compIdx, i + 1, high, func);
1066 if (i == 0)
1067 return;
1068 high = i - 1;
1069 }
1070 }
1071 }
1072
1076 void sort_entities(Entity entity, TSortByFunc func) {
1077 // TODO: We currently have to calculate the number of entities in the archetype from chunks.
1078 // Additionally, to get the right index we need to loop through chunks again because
1079 // the entities are not spread evenly among chunks (we can't just divide the index by
1080 // the number of chunks and module with the same number to get the index inside a chunk).
1081 // This is not optimal, and makes sorting more expensive.
1082
1083 if (entity == EntityBad) {
1084 {
1085 uint32_t entities = 0;
1086 for (const auto* pChunk: m_storage.chunks)
1087 entities += pChunk->size_enabled();
1088 if (entities != 0)
1089 sort_entities_inter<true>(0, entities - 1, func);
1090 }
1091 {
1092 uint32_t entities = 0;
1093 for (const auto* pChunk: m_storage.chunks)
1094 entities += pChunk->size_disabled();
1095 if (entities != 0)
1096 sort_entities_inter<false>(0, entities - 1, func);
1097 }
1098 } else {
1099 const auto* pItem = m_cc.find(entity);
1100 GAIA_ASSERT(pItem != nullptr && "Trying to sort by a component that has not been registered");
1101 if (pItem == nullptr)
1102 return;
1103
1104 const auto compIdx = chunks()[0]->comp_idx(entity);
1105 {
1106 uint32_t entities = 0;
1107 for (const auto* pChunk: m_storage.chunks)
1108 entities += pChunk->size_enabled();
1109 if (entities != 0)
1110 sort_entities_inter<true>(pItem, compIdx, 0, entities - 1, func);
1111 }
1112 {
1113 uint32_t entities = 0;
1114 for (const auto* pChunk: m_storage.chunks)
1115 entities += pChunk->size_disabled();
1116 if (entities != 0)
1117 sort_entities_inter<false>(pItem, compIdx, 0, entities - 1, func);
1118 }
1119 }
1120 }
1121
1122 //----------------------------------------------------------------------
1123
1127 void build_graph_edges(Archetype* pArchetypeRight, Entity entity) {
1128 // Loops can't happen
1129 GAIA_ASSERT(pArchetypeRight != this);
1130
1131 m_edges.graph.add_edge_right(entity, pArchetypeRight->id(), pArchetypeRight->id_hash());
1132 pArchetypeRight->build_graph_edges_left(this, entity);
1133 }
1134
1138 void build_graph_edges_left(Archetype* pArchetypeLeft, Entity entity) {
1139 // Loops can't happen
1140 GAIA_ASSERT(pArchetypeLeft != this);
1141
1142 m_edges.graph.add_edge_left(entity, pArchetypeLeft->id(), pArchetypeLeft->id_hash());
1143 }
1144
1148 void del_graph_edges(Archetype* pArchetypeRight, Entity entity) {
1149 // Loops can't happen
1150 GAIA_ASSERT(pArchetypeRight != this);
1151
1152 m_edges.graph.del_edge_right(entity);
1153 pArchetypeRight->del_graph_edges_left(this, entity);
1154 }
1155
1159 void del_graph_edges_left([[maybe_unused]] Archetype* pArchetypeLeft, Entity entity) {
1160 // Loops can't happen
1161 GAIA_ASSERT(pArchetypeLeft != this);
1162
1163 m_edges.graph.del_edge_left(entity);
1164 }
1165
1170 m_edges.graph.del_edge_right(entity);
1171 }
1172
1177 m_edges.graph.del_edge_left(entity);
1178 }
1179
1183 GAIA_NODISCARD ArchetypeGraphEdge find_edge_right(Entity entity) const {
1184 return m_edges.graph.find_edge_right(entity);
1185 }
1186
1190 GAIA_NODISCARD ArchetypeGraphEdge find_edge_left(Entity entity) const {
1191 return m_edges.graph.find_edge_left(entity);
1192 }
1193
1196 GAIA_NODISCARD auto& right_edges() {
1197 return m_edges.graph.right_edges();
1198 }
1199
1202 GAIA_NODISCARD const auto& right_edges() const {
1203 return m_edges.graph.right_edges();
1204 }
1205
1208 GAIA_NODISCARD auto& left_edges() {
1209 return m_edges.graph.left_edges();
1210 }
1211
1214 GAIA_NODISCARD const auto& left_edges() const {
1215 return m_edges.graph.left_edges();
1216 }
1217
1220 GAIA_NODISCARD bool empty() const {
1221 return m_storage.chunks.empty();
1222 }
1223
1225 void req_del() {
1226 m_runtime.deleteReq = 1;
1227 }
1228
1231 GAIA_NODISCARD bool is_req_del() const {
1232 return m_runtime.deleteReq;
1233 }
1234
1239 void set_max_lifespan(uint32_t lifespan) {
1240 GAIA_ASSERT(lifespan <= MAX_ARCHETYPE_LIFESPAN);
1241
1242 m_runtime.lifespanCountdownMax = lifespan;
1243 if (lifespan != 0 && m_runtime.lifespanCountdown > lifespan)
1244 m_runtime.lifespanCountdown = lifespan;
1245 }
1246
1250 GAIA_NODISCARD uint32_t max_lifespan() const {
1251 return m_runtime.lifespanCountdownMax;
1252 }
1253
1256 GAIA_NODISCARD bool dying() const {
1257 return m_runtime.lifespanCountdown > 0;
1258 }
1259
1261 void die() {
1262 m_runtime.dead = 1;
1263 }
1264
1267 GAIA_NODISCARD bool dead() const {
1268 return m_runtime.dead == 1;
1269 }
1270
1273 GAIA_ASSERT(!dead());
1274 m_runtime.lifespanCountdown = m_runtime.lifespanCountdownMax;
1275 }
1276
1278 void revive() {
1279 GAIA_ASSERT(!dead());
1280 m_runtime.lifespanCountdown = 0;
1281 m_runtime.deleteReq = 0;
1282 }
1283
1286 GAIA_NODISCARD bool progress_death() {
1287 GAIA_ASSERT(dying());
1288 GAIA_ASSERT(m_runtime.lifespanCountdownMax > 0);
1289 --m_runtime.lifespanCountdown;
1290 return dying();
1291 }
1292
1295 GAIA_NODISCARD bool ready_to_die() const {
1296 return m_runtime.lifespanCountdownMax > 0 && !dying() && empty();
1297 }
1298
1302 static void diag_entity(const World& world, Entity entity) {
1303 if (entity.entity()) {
1304 const auto name = entity_name(world, entity);
1305 GAIA_LOG_N(
1306 " ent [%u:%u] %.*s", entity.id(), entity.gen(), (int)name.size(), name.empty() ? "" : name.data());
1307 } else if (entity.pair()) {
1308 const auto rel = entity_name(world, entity.id());
1309 const auto tgt = entity_name(world, entity.gen());
1310 GAIA_LOG_N(
1311 " pair [%u:%u] %.*s -> %.*s", entity.id(), entity.gen(), (int)rel.size(),
1312 rel.empty() ? "" : rel.data(), (int)tgt.size(), tgt.empty() ? "" : tgt.data());
1313 } else {
1314 const auto& cc = comp_cache(world);
1315 const auto& desc = cc.get(entity);
1316 const auto symbol = desc.symbol_name();
1317 GAIA_LOG_N(
1318 " hash:%016" PRIx64 ", size:%3u B, align:%3u B, [%u:%u] %.*s", desc.hashLookup.hash, desc.comp.size(),
1319 desc.comp.alig(), desc.entity.id(), desc.entity.gen(), (int)symbol.size(), symbol.data());
1320 }
1321 }
1322
1326 static void diag_basic_info(const World& world, const Archetype& archetype) {
1327 auto ids = archetype.ids_view();
1328
1329 // Calculate the number of entities in archetype
1330 uint32_t entCnt = 0;
1331 uint32_t entCntDisabled = 0;
1332 for (const auto* chunk: archetype.m_storage.chunks) {
1333 entCnt += chunk->size();
1334 entCntDisabled += chunk->size_disabled();
1335 }
1336
1337 // Calculate the number of components
1338 uint32_t compsSize = 0;
1339 {
1340 const auto& p = archetype.props();
1341 GAIA_FOR(p.cntEntities)
1342 compsSize += comp_from_item(ids[i], archetype.m_shape.compItems[i]).size();
1343 }
1344
1345 const auto chunkBytes = Chunk::chunk_total_bytes(archetype.props().chunkDataBytes);
1346 const auto sizeType = mem_block_size_type(chunkBytes);
1347 const auto allocSize = mem_block_size(sizeType) / 1024;
1348
1349 GAIA_LOG_N(
1350 "aid:%u, "
1351 "hash:%016" PRIx64 ", "
1352 "chunks:%u (%uK), data:%u/%u B, "
1353 "entities:%u/%u/%u",
1354 archetype.id(), archetype.lookup_hash().hash, (uint32_t)archetype.chunks().size(), allocSize, compsSize,
1355 archetype.props().chunkDataBytes, entCnt, entCntDisabled, archetype.props().capacity);
1356
1357 if (!ids.empty()) {
1358 GAIA_LOG_N(" Components - count:%u", (uint32_t)ids.size());
1359 for (const auto ent: ids)
1360 diag_entity(world, ent);
1361 }
1362 }
1363
1367 static void diag_graph_info(const World& world, const Archetype& archetype) {
1368 archetype.m_edges.graph.diag(world);
1369 }
1370
1373 static void diag_chunk_info(const Archetype& archetype) {
1374 const auto& chunks = archetype.m_storage.chunks;
1375 if (chunks.empty())
1376 return;
1377
1378 GAIA_LOG_N(" Chunks");
1379 for (const auto* pChunk: chunks)
1380 pChunk->diag();
1381 }
1382
1386 static void diag_entity_info(const World& world, const Archetype& archetype) {
1387 const auto& chunks = archetype.m_storage.chunks;
1388 if (chunks.empty())
1389 return;
1390
1391 GAIA_LOG_N(" Entities");
1392 bool noEntities = true;
1393 for (const auto* pChunk: chunks) {
1394 if (pChunk->empty())
1395 continue;
1396 noEntities = false;
1397
1398 auto ev = pChunk->entity_view();
1399 for (auto entity: ev)
1400 diag_entity(world, entity);
1401 }
1402 if (noEntities)
1403 GAIA_LOG_N(" N/A");
1404 }
1405
1409 static void diag(const World& world, const Archetype& archetype) {
1410 diag_basic_info(world, archetype);
1411 diag_graph_info(world, archetype);
1412 diag_chunk_info(archetype);
1413 diag_entity_info(world, archetype);
1414 }
1415 };
1416
1419 class GAIA_API ArchetypeLookupKey final {
1420 Archetype::LookupHash m_hash;
1421 const ArchetypeBase* m_pArchetypeBase;
1422
1423 public:
1425 static constexpr bool IsDirectHashKey = true;
1426
1427 ArchetypeLookupKey(): m_hash({0}), m_pArchetypeBase(nullptr) {}
1428 explicit ArchetypeLookupKey(Archetype::LookupHash hash, const ArchetypeBase* pArchetypeBase):
1429 m_hash(hash), m_pArchetypeBase(pArchetypeBase) {}
1430
1433 GAIA_NODISCARD size_t hash() const {
1434 return (size_t)m_hash.hash;
1435 }
1436
1439 GAIA_NODISCARD Archetype* archetype() const {
1440 return (Archetype*)m_pArchetypeBase;
1441 }
1442
1443 GAIA_NODISCARD bool operator==(const ArchetypeLookupKey& other) const {
1444 // Hash doesn't match we don't have a match.
1445 // Hash collisions are expected to be very unlikely so optimize for this case.
1446 if GAIA_LIKELY (m_hash != other.m_hash)
1447 return false;
1448
1449 const auto id = m_pArchetypeBase->id();
1450 if (id == ArchetypeIdBad) {
1451 const auto* pArchetype = (const Archetype*)other.m_pArchetypeBase;
1452 const auto* pArchetypeLookupChecker = (const ArchetypeLookupChecker*)m_pArchetypeBase;
1453 return pArchetype->cmp_comps(*pArchetypeLookupChecker);
1454 }
1455
1456 // Real ArchetypeID is given. Compare the pointers.
1457 // Normally we'd compare archetype IDs but because we do not allow archetype copies and all archetypes are
1458 // unique it's guaranteed that if pointers are the same we have a match.
1459 // This also saves a pointer indirection because we do not access the memory the pointer points to.
1460 return m_pArchetypeBase == other.m_pArchetypeBase;
1461 }
1462 };
1463
1464 using ArchetypeMapByHash = cnt::map<ArchetypeLookupKey, Archetype*>;
1465 } // namespace ecs
1466} // namespace gaia
Array of elements of type.
Definition darray_ext_impl.h:31
GAIA_NODISCARD size_type size() const noexcept
Returns the number of elements.
Definition darray_ext_impl.h:561
Base shared by archetype lookups and concrete archetypes, carrying the archetype id.
Definition archetype.h:64
ArchetypeId m_archetypeId
Archetype ID - used to address the archetype directly in the world's list or archetypes.
Definition archetype.h:67
GAIA_NODISCARD ArchetypeId id() const
Archetype id used to address the archetype in the world list.
Definition archetype.h:72
Archetype lookup key that compares against a component term span instead of a full archetype id.
Definition archetype.h:78
GAIA_NODISCARD bool cmp_comps(const ArchetypeLookupChecker &other) const
Compares the lookup checker component spans in archetype order.
Definition archetype.h:90
Hashmap key for archetype lookup, combining a hash with the archetype base pointer....
Definition archetype.h:1419
GAIA_NODISCARD size_t hash() const
Hash of the archetype lookup value.
Definition archetype.h:1433
GAIA_NODISCARD Archetype * archetype() const
Resolves the lookup key to its concrete archetype.
Definition archetype.h:1439
Fixed-shape group of chunks storing entities that share the same component layout....
Definition archetype.h:97
Entity get_flat_entity(size_t flatIdx) const
Given a flat index, returns an entity at that index. E.g., if there are 2 chunks, the first one with ...
Definition archetype.h:908
void observed_terms_dec()
Decrements the number of terms observing this archetype.
Definition archetype.h:874
QueryMask queryMask() const
Query mask describing which simple queries may match this archetype.
Definition archetype.h:648
static GAIA_NODISCARD Archetype * create(const World &world, ArchetypeId archetypeId, uint32_t &worldVersion, EntitySpan ids)
Creates a new archetype from a component term span.
Definition archetype.h:539
GAIA_NODISCARD Chunk * foc_free_chunk()
Tries to locate a chunk that has some space left for a new entity. If not found a new chunk is create...
Definition archetype.h:700
void set_max_lifespan(uint32_t lifespan)
Sets maximal lifespan of an archetype. A dying archetype keeps no more of its countdown than the new ...
Definition archetype.h:1239
void build_graph_edges(Archetype *pArchetypeRight, Entity entity)
Builds a graph edge from this archetype to the right archetype.
Definition archetype.h:1127
GAIA_NODISCARD bool cmp_comps(const ArchetypeLookupChecker &other) const
Compares the lookup checker component spans in archetype order.
Definition archetype.h:528
void sort_entities_inter(const ComponentCacheItem *pItem, uint32_t compIdx, size_t low, size_t high, TSortByFunc func)
Generic in-place quicksort across chunks.
Definition archetype.h:1026
GAIA_NODISCARD EntitySpan ids_view() const
Span over the component and entity identifiers defining the archetype shape.
Definition archetype.h:800
void set_hashes(LookupHash hashLookup)
Sets hashes for each component type and lookup.
Definition archetype.h:660
void observed_terms_inc()
Increments the number of terms observing this archetype.
Definition archetype.h:868
GAIA_NODISCARD const auto & left_edges() const
Const view over the archetype "del" graph edges.
Definition archetype.h:1214
static void diag_basic_info(const World &world, const Archetype &archetype)
Logs basic archetype diagnostics: sizes, chunk count, entity counts, and component ids.
Definition archetype.h:1326
static void destroy(Archetype *pArchetype)
Destroys the archetype and frees its memory.
Definition archetype.h:640
GAIA_NODISCARD auto & left_edges()
Mutable view over the archetype "del" graph edges.
Definition archetype.h:1208
GAIA_NODISCARD uint32_t pairs() const
Returns the number of pairs registered in the archetype.
Definition archetype.h:812
void del_graph_edge_left_local(Entity entity)
Deletes a cached local "del" edge formed by entity. Intended for stale edge cache recovery when the o...
Definition archetype.h:1176
void remove_entity(Chunk &chunk, uint16_t row, EntityContainers &recs)
Removes an entity from the chunk and updates the chunk versions.
Definition archetype.h:775
void del(Chunk *pChunk)
Removes a chunk from the list of chunks managed by their archetype and deletes its memory.
Definition archetype.h:676
GAIA_NODISCARD uint32_t pairs_is() const
Returns the number of Is pairs registered in the archetype.
Definition archetype.h:818
void sort_entities_inter(size_t low, size_t high, TSortByFunc func)
Generic in-place quicksort across chunks.
Definition archetype.h:982
GAIA_NODISCARD bool ready_to_die() const
Tells whether archetype is ready to be deleted.
Definition archetype.h:1295
GAIA_NODISCARD bool dead() const
Checks is this chunk is dying.
Definition archetype.h:1267
GAIA_NODISCARD bool has_observed_terms() const
Whether any observer term currently observes this archetype.
Definition archetype.h:881
ArchetypeIdLookupKey::LookupHash id_hash() const
Archetype id hash used for graph edge storage.
Definition archetype.h:654
void del_graph_edges(Archetype *pArchetypeRight, Entity entity)
Removes the cached "add" edge for entity and propagates the delete to the right archetype.
Definition archetype.h:1148
const void * get_flat_comp_ptr(uint32_t compIdx, size_t flatIdx, Entity &outEntity) const
Given a flat index, returns pointer to component data at that index. E.g., if there are 2 chunks,...
Definition archetype.h:946
GAIA_NODISCARD const Properties & props() const
Shape properties of this archetype.
Definition archetype.h:782
void enable_entity(Chunk *pChunk, uint16_t row, bool enableEntity, EntityContainers &recs)
Enables or disables the entity on a given row in the chunk.
Definition archetype.h:669
void die()
Marks the chunk as dead.
Definition archetype.h:1261
GAIA_NODISCARD ArchetypeGraphEdge find_edge_left(Entity entity) const
Checks if an archetype graph "del" edge with entity entity exists.
Definition archetype.h:1190
static void diag(const World &world, const Archetype &archetype)
Performs diagnostics on a specific archetype. Prints basic info about it and the chunks it contains.
Definition archetype.h:1409
GAIA_NODISCARD bool progress_death()
Updates internal lifespan.
Definition archetype.h:1286
GAIA_NODISCARD LookupHash lookup_hash() const
Hash of the component terms used for archetype lookup.
Definition archetype.h:794
GAIA_NODISCARD bool empty() const
Checks is there are no chunk in the archetype.
Definition archetype.h:1220
void save(ser::serializer &s)
Serializes the archetype: free-chunk index, list index, and each chunk.
Definition archetype.h:472
GAIA_NODISCARD bool has(Entity entity) const
Checks if an entity is a part of the archetype.
Definition archetype.h:861
GAIA_NODISCARD const auto & right_edges() const
Const view over the archetype "add" graph edges.
Definition archetype.h:1202
GAIA_NODISCARD bool is_req_del() const
Returns true if this archetype is requested to be deleted.
Definition archetype.h:1231
void load(ser::serializer &s)
Deserializes the archetype, recreating missing chunks from the shape description.
Definition archetype.h:485
static void diag_entity(const World &world, Entity entity)
Logs a diagnostic line for one entity, pair, or component of the archetype.
Definition archetype.h:1302
void del_graph_edges_left(Archetype *pArchetypeLeft, Entity entity)
Removes the cached "del" edge for entity after the matching "add" edge is removed.
Definition archetype.h:1159
GAIA_NODISCARD bool dying() const
Checks is this chunk is dying.
Definition archetype.h:1256
void list_idx(uint32_t idx)
Sets the archetype index in the world list of active archetypes.
Definition archetype.h:515
GAIA_NODISCARD auto & right_edges()
Mutable view over the archetype "add" graph edges.
Definition archetype.h:1196
GAIA_NODISCARD bool has() const
Checks if component T is present in the chunk.
Definition archetype.h:889
GAIA_NODISCARD ChunkDataOffsetSpan comp_offs_view() const
Span over the per-term component data offsets.
Definition archetype.h:806
void build_graph_edges_left(Archetype *pArchetypeLeft, Entity entity)
Records a cached "del" graph edge from entity entity to this archetype.
Definition archetype.h:1138
void revive()
Makes the archetype alive again.
Definition archetype.h:1278
void try_update_free_chunk_idx()
Tries to update the index of the first chunk that has space left for at least one entity.
Definition archetype.h:733
GAIA_NODISCARD ArchetypeGraphEdge find_edge_right(Entity entity) const
Checks if an archetype graph "add" edge with entity entity exists.
Definition archetype.h:1183
void req_del()
Request deleting the archetype.
Definition archetype.h:1225
uint32_t list_idx() const
Archetype index in the world list of active archetypes.
Definition archetype.h:521
void try_update_free_chunk_idx(Chunk &chunkThatRemovedEntity)
Tries to update the index of the first chunk that has space left for at least one entity.
Definition archetype.h:745
GAIA_NODISCARD std::span< const uint8_t > pair_tgt_indices(Entity target) const
Indices of pairs whose target matches the given target.
Definition archetype.h:854
GAIA_NODISCARD Entity entity_from_pairs_as_idx(uint32_t idx) const
Resolves a relation-pair index to its pair entity.
Definition archetype.h:833
static void diag_entity_info(const World &world, const Archetype &archetype)
Logs entity diagnostics for each entity of the archetype.
Definition archetype.h:1386
GAIA_NODISCARD uint32_t pair_matches(Entity pair) const
Returns how many pair ids in this archetype match the provided wildcard-capable pair query....
Definition archetype.h:826
void remove_entity_raw(Chunk &chunk, uint16_t row, EntityContainers &recs)
Removes an entity from the chunk.
Definition archetype.h:766
static void diag_graph_info(const World &world, const Archetype &archetype)
Logs the archetype graph edge diagnostics.
Definition archetype.h:1367
GAIA_NODISCARD std::span< const uint8_t > pair_rel_indices(Entity relation) const
Indices of pairs whose relation matches the given relation.
Definition archetype.h:847
GAIA_NODISCARD std::span< const uint8_t > pair_indices() const
Indices of all relation pairs within the ids array.
Definition archetype.h:840
void start_dying()
Starts the process of dying.
Definition archetype.h:1272
void del_graph_edge_right_local(Entity entity)
Deletes a cached local "add" edge formed by entity. Intended for stale edge cache recovery when the o...
Definition archetype.h:1169
GAIA_NODISCARD const cnt::darray< Chunk * > & chunks() const
Chunks managed by this archetype.
Definition archetype.h:788
GAIA_NODISCARD uint32_t max_lifespan() const
Returns the maximal lifespan of the archetype. If zero, the archetype it kept indefinitely.
Definition archetype.h:1250
static void diag_chunk_info(const Archetype &archetype)
Logs per-chunk diagnostics for all chunks of the archetype.
Definition archetype.h:1373
void sort_entities(Entity entity, TSortByFunc func)
Sorts all entities in the archetypes according to the given function.
Definition archetype.h:1076
Fixed-capacity archetype storage unit holding entities and their component columns.
Definition chunk.h:36
void remove_entity(uint16_t row, EntityContainers &recs)
Tries to remove the entity at row row. Removal is done via swapping with last entity in chunk....
Definition chunk.h:1212
GAIA_NODISCARD uint32_t idx() const
Returns the index of this chunk in its archetype's storage.
Definition chunk.h:1645
void update_versions()
Updates the version numbers for this chunk.
Definition chunk.h:573
GAIA_NODISCARD uint16_t size() const
Returns the total number of entities in the chunk (both enabled and disabled)
Definition chunk.h:1738
void enable_entity(uint16_t row, bool enableEntity, EntityContainers &recs)
Enables or disables the entity on a given row in the chunk.
Definition chunk.h:1338
Owns entities, components, archetypes, queries, observers, and systems.
Definition world.h:80
Wrapper for two Entities forming a relationship pair. Conversion to Entity stores only id() of each e...
Definition id.h:559
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:197
T hash
Precomputed hash value.
Definition hashing_policy.h:51
Pairs an archetype with one of its chunks.
Definition archetype.h:52
Archetype * pArchetype
Archetype the chunk belongs to.
Definition archetype.h:54
Chunk * pChunk
Chunk owned by the paired archetype.
Definition archetype.h:56
Counts how many pair indices of one relation or target occupy a contiguous range.
Definition archetype.h:177
EntityId id
Relation or target entity id the bucket counts.
Definition archetype.h:179
uint8_t count
Number of pair indices belonging to this relation or target.
Definition archetype.h:183
uint8_t start
First pair index within the archetype ids array.
Definition archetype.h:181
Shape properties of this archetype.
Definition archetype.h:110
uint16_t capacity
The number of data entities this archetype can take (e.g 5 = 5 entities with all their components)
Definition archetype.h:112
uint8_t cntEntities
Total number of entities/components.
Definition archetype.h:116
ChunkDataOffset chunkDataBytes
How many bytes of data is needed for a fully utilized chunk.
Definition archetype.h:114
Identifier of a registered component type. Packs the component id, size, alignment,...
Definition id.h:45
Identifier of an entity or component instance in the world. Packs the entity index,...
Definition id.h:231
GAIA_NODISCARD constexpr auto gen() const noexcept
Generation index of the entity.
Definition id.h:301
GAIA_NODISCARD constexpr bool pair() const noexcept
Whether this id refers to a relationship pair.
Definition id.h:313
GAIA_NODISCARD constexpr auto id() const noexcept
Entity index in the entity array.
Definition id.h:295
GAIA_NODISCARD constexpr bool entity() const noexcept
Whether this id refers to an entity.
Definition id.h:307
Detects whether a type is a relationship pair.
Definition id.h:220
Runtime serializer type-erased handle. Traversal logic is shared with compile-time serialization,...
Definition ser_rt.h:94
void load(T &arg)
Deserializes a value through generic traversal.
Definition ser_rt.h:126
void save(const T &arg)
Serializes a value through generic traversal.
Definition ser_rt.h:115