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 genEntities;
118 uint8_t cntEntities;
119 };
120
121 private:
122 GAIA_NODISCARD static Component empty_comp() noexcept {
123 return Component(IdentifierIdBad, 0, 0, 0, DataStorageType::Table);
124 }
125
126 GAIA_NODISCARD static Component comp_from_item(const ComponentCacheItem* pItem) noexcept {
127 return pItem == nullptr ? empty_comp() : pItem->comp;
128 }
129
130 struct ShapeData {
131 ArchetypeIdLookupKey::LookupHash archetypeIdHash;
133 LookupHash hashLookup = {0};
135 QueryMask queryMask{};
136 Properties properties{};
138 ChunkDataOffsets dataOffsets{};
140 Entity ids[ChunkHeader::MAX_COMPONENTS];
142 const ComponentCacheItem* compItems[ChunkHeader::MAX_COMPONENTS]{};
144 ChunkDataOffset compOffs[ChunkHeader::MAX_COMPONENTS];
145 };
146
147 struct StorageData {
149 cnt::darray<Chunk*> chunks;
151 uint32_t firstFreeChunkIdx = 0;
152 };
153
154 struct RuntimeData {
156 uint32_t listIdx = BadIndex;
158 uint8_t observedTermCnt = 0;
159
161 uint32_t deleteReq : 1;
163 uint32_t dead : 1;
165 uint32_t lifespanCountdownMax : ARCHETYPE_LIFESPAN_BITS;
167 uint32_t lifespanCountdown : ARCHETYPE_LIFESPAN_BITS;
168
169 RuntimeData(): deleteReq(0), dead(0), lifespanCountdownMax(1), lifespanCountdown(0) {}
170 };
171
172 struct EdgeData {
174 ArchetypeGraph graph;
175 };
176
177 struct PairIndexData {
181 EntityId id;
183 uint8_t start;
185 uint8_t count;
186 };
187
188 PairIndexData() noexcept {}
189
191 uint8_t pairIndexBuffer[ChunkHeader::MAX_COMPONENTS];
193 uint8_t pairsAsIndexBuffer[ChunkHeader::MAX_COMPONENTS];
194 uint8_t pairRelIndexBuffer[ChunkHeader::MAX_COMPONENTS];
195 uint8_t pairTgtIndexBuffer[ChunkHeader::MAX_COMPONENTS];
197 PairCountBucket pairRelCountBuffer[ChunkHeader::MAX_COMPONENTS];
198 PairCountBucket pairTgtCountBuffer[ChunkHeader::MAX_COMPONENTS];
200 uint8_t pairCnt = 0;
202 uint8_t pairCntIs = 0;
204 uint8_t pairRelCountCnt = 0;
206 uint8_t pairTgtCountCnt = 0;
207
208 static PairIndexData* create(EntitySpan ids) {
209 auto* pPairIndex = mem::AllocHelper::alloc<PairIndexData>("ArchetypePairIndex");
210 (void)new (pPairIndex) PairIndexData();
211
212 uint8_t pairRelIndexCnt = 0;
213 uint8_t pairTgtIndexCnt = 0;
214
215 GAIA_FOR(ids.size()) {
216 if (!ids[i].pair())
217 continue;
218
219 pPairIndex->pairIndexBuffer[pPairIndex->pairCnt] = (uint8_t)i;
220 ++pPairIndex->pairCnt;
221 add_pair_index_bucket(
222 pPairIndex->pairRelCountBuffer, pPairIndex->pairRelCountCnt, pPairIndex->pairRelIndexBuffer,
223 pairRelIndexCnt, (EntityId)ids[i].id(), (uint8_t)i);
224 add_pair_index_bucket(
225 pPairIndex->pairTgtCountBuffer, pPairIndex->pairTgtCountCnt, pPairIndex->pairTgtIndexBuffer,
226 pairTgtIndexCnt, (EntityId)ids[i].gen(), (uint8_t)i);
227
228 if (ids[i].id() == Is.id())
229 pPairIndex->pairsAsIndexBuffer[pPairIndex->pairCntIs++] = (uint8_t)i;
230 }
231
232 if (pPairIndex->pairCnt == 0) {
233 destroy(pPairIndex);
234 return nullptr;
235 }
236
237 return pPairIndex;
238 }
239
240 static void destroy(PairIndexData* pPairIndex) {
241 if (pPairIndex == nullptr)
242 return;
243
244 pPairIndex->~PairIndexData();
245 mem::AllocHelper::free("ArchetypePairIndex", pPairIndex);
246 }
247
248 static PairCountBucket&
249 ensure_pair_index_bucket(PairCountBucket* pBuckets, uint8_t& bucketCnt, EntityId id, uint8_t start) {
250 GAIA_FOR(bucketCnt) {
251 if (pBuckets[i].id == id)
252 return pBuckets[i];
253 }
254
255 GAIA_ASSERT(bucketCnt < ChunkHeader::MAX_COMPONENTS);
256 auto& bucket = pBuckets[bucketCnt++];
257 bucket.id = id;
258 bucket.start = start;
259 bucket.count = 0;
260 return bucket;
261 }
262
263 static void add_pair_index_bucket(
264 PairCountBucket* pBuckets, uint8_t& bucketCnt, uint8_t* pIndexBuffer, uint8_t& indexCnt, EntityId id,
265 uint8_t idsIdx) {
266 auto& bucket = ensure_pair_index_bucket(pBuckets, bucketCnt, id, indexCnt);
267 GAIA_ASSERT(indexCnt < ChunkHeader::MAX_COMPONENTS);
268 pIndexBuffer[indexCnt++] = idsIdx;
269 ++bucket.count;
270 }
271
272 static uint32_t pair_count_from_buckets(const PairCountBucket* pBuckets, uint8_t bucketCnt, EntityId id) {
273 GAIA_FOR(bucketCnt) {
274 if (pBuckets[i].id == id)
275 return pBuckets[i].count;
276 }
277
278 return 0;
279 }
280
281 GAIA_NODISCARD static std::span<const uint8_t> pair_indices_from_buckets(
282 const PairCountBucket* pBuckets, uint8_t bucketCnt, const uint8_t* pIndexBuffer, EntityId id) {
283 GAIA_FOR(bucketCnt) {
284 if (pBuckets[i].id != id)
285 continue;
286
287 return {pIndexBuffer + pBuckets[i].start, pBuckets[i].count};
288 }
289
290 return {};
291 }
292
293 GAIA_NODISCARD uint32_t pair_matches(EntitySpan ids, Entity pair) const {
294 GAIA_ASSERT(pair.pair());
295
296 if (pair == Pair(All, All))
297 return pairCnt;
298
299 if (pair.id() == All.id())
300 return pair_count_from_buckets(pairTgtCountBuffer, pairTgtCountCnt, (EntityId)pair.gen());
301
302 if (pair.gen() == All.id())
303 return pair_count_from_buckets(pairRelCountBuffer, pairRelCountCnt, (EntityId)pair.id());
304
305 return core::has_if(
306 ids,
307 [pair](Entity entity) {
308 return entity == pair;
309 })
310 ? 1u
311 : 0u;
312 }
313
314 GAIA_NODISCARD Entity entity_from_pairs_as_idx(EntitySpan ids, uint32_t idx) const {
315 const auto idsIdx = pairsAsIndexBuffer[idx];
316 return ids[idsIdx];
317 }
318
319 GAIA_NODISCARD std::span<const uint8_t> pair_indices() const {
320 return {&pairIndexBuffer[0], pairCnt};
321 }
322
323 GAIA_NODISCARD std::span<const uint8_t> pair_rel_indices(Entity relation) const {
324 return pair_indices_from_buckets(
325 pairRelCountBuffer, pairRelCountCnt, pairRelIndexBuffer, (EntityId)relation.id());
326 }
327
328 GAIA_NODISCARD std::span<const uint8_t> pair_tgt_indices(Entity target) const {
329 return pair_indices_from_buckets(
330 pairTgtCountBuffer, pairTgtCountCnt, pairTgtIndexBuffer, (EntityId)target.id());
331 }
332 };
333
335 const World& m_world;
337 const ComponentCache& m_cc;
339 uint32_t& m_worldVersion;
340
341 ShapeData m_shape{};
342 StorageData m_storage{};
344 // cnt::dbitset m_disabledMask;
345 EdgeData m_edges{};
346 RuntimeData m_runtime{};
347 PairIndexData* m_pPairIndex = nullptr;
348
350 Archetype(const World& world, const ComponentCache& cc, uint32_t& worldVersion):
351 m_world(world), m_cc(cc), m_worldVersion(worldVersion) //
352 {}
353
354 ~Archetype() {
355 // Delete all archetype chunks
356 for (auto* pChunk: m_storage.chunks)
357 Chunk::free(pChunk);
358 PairIndexData::destroy(m_pPairIndex);
359 }
360
364 void update_data_offsets(uintptr_t memoryAddress) {
365 uintptr_t offset = 0;
366
367 // Versions
368 // We expect versions to fit in the first 256 bytes.
369 // With 32 components per archetype this gives us some headroom.
370 {
371 offset += mem::padding<alignof(ComponentVersion)>(memoryAddress);
372
373 const auto cnt = m_shape.properties.cntEntities + 1; // + 1 for entities
374 GAIA_ASSERT(offset < 256);
375 m_shape.dataOffsets.firstByte_Versions = (ChunkDataVersionOffset)offset;
376 offset += sizeof(ComponentVersion) * cnt;
377 }
378
379 // Entity ids
380 {
381 offset += mem::padding<alignof(Entity)>(offset);
382
383 const auto cnt = m_shape.properties.cntEntities;
384 if (cnt != 0) {
385 m_shape.dataOffsets.firstByte_CompEntities = (ChunkDataOffset)offset;
386
387 // Storage-wise, treat the component array as it it were MAX_COMPONENTS long.
388 offset += sizeof(Entity) * ChunkHeader::MAX_COMPONENTS;
389 }
390 }
391
392 // Component records
393 {
394 offset += mem::padding<alignof(ComponentRecord)>(offset);
395
396 const auto cnt = m_shape.properties.cntEntities;
397 if (cnt != 0) {
398
399 m_shape.dataOffsets.firstByte_Records = (ChunkDataOffset)offset;
400
401 // Storage-wise, treat the component array as it it were MAX_COMPONENTS long.
402 offset += sizeof(ComponentRecord) * cnt;
403 }
404 }
405
406 // First entity offset
407 {
408 offset += mem::padding<alignof(Entity)>(offset);
409 m_shape.dataOffsets.firstByte_EntityData = (ChunkDataOffset)offset;
410 }
411 }
412
421 static bool est_max_entities_per_chunk(
422 uint32_t offs, const ComponentCacheItem* const* pItems, uint32_t cnt, uint32_t cap, uint32_t maxDataOffset) {
423 GAIA_FOR(cnt) {
424 const auto comp = comp_from_item(pItems[i]);
425 if (!component_uses_table_storage(comp))
426 continue;
427
428 const auto* pItem = pItems[i];
429 GAIA_ASSERT(pItem != nullptr);
430
431 // If we're beyond what the chunk could take, subtract one entity
432 offs = pItem->calc_new_mem_offset(offs, cap);
433 if (offs >= maxDataOffset)
434 return false;
435 }
436
437 return true;
438 }
439
440 static void reg_components(
441 Archetype& arch, EntitySpan ids, const ComponentCacheItem* const* pItems, uint8_t from, uint8_t to,
442 uint32_t& currOff, uint32_t count) {
443 auto& ofs = arch.m_shape.compOffs;
444
445 // Set component ids
446 GAIA_FOR2(from, to) arch.m_shape.ids[i] = ids[i];
447
448 // Calculate offsets and assign them indices according to our mappings
449 GAIA_FOR2(from, to) {
450 const auto comp = comp_from_item(pItems[i]);
451 const auto compIdx = i;
452
453 if (!component_uses_table_storage(comp)) {
454 ofs[compIdx] = {};
455 } else {
456 const auto alig = comp.alig();
457 currOff = mem::align(currOff, alig);
458 ofs[compIdx] = (ChunkDataOffset)currOff;
459
460 currOff = pItems[i]->calc_new_mem_offset(currOff, count);
461 }
462 }
463 }
464
465 public:
466 Archetype(Archetype&&) = delete;
467 Archetype(const Archetype&) = delete;
468 Archetype& operator=(Archetype&&) = delete;
469 Archetype& operator=(const Archetype&) = delete;
470
474 s.save(m_storage.firstFreeChunkIdx);
475 s.save(m_runtime.listIdx);
476
477 s.save((uint32_t)m_storage.chunks.size());
478 for (auto* pChunk: m_storage.chunks) {
479 s.save(pChunk->idx());
480 pChunk->save(s);
481 }
482 }
483
487 s.load(m_storage.firstFreeChunkIdx);
488 s.load(m_runtime.listIdx);
489
490 uint32_t chunkCnt = 0;
491 s.load(chunkCnt);
492 m_storage.chunks.resize(chunkCnt, nullptr);
493
494 GAIA_FOR(chunkCnt) {
495 uint32_t chunkIdx = 0;
496 s.load(chunkIdx);
497
498 auto* pChunk = m_storage.chunks[chunkIdx];
499 // If the chunk doesn't exist it means it's not a part of the initial setup.
500 if (pChunk == nullptr) {
501 pChunk = Chunk::create(
502 m_world, m_cc, chunkIdx, //
503 m_shape.properties.capacity, m_shape.properties.cntEntities, //
504 m_shape.properties.genEntities, m_shape.properties.chunkDataBytes, //
505 m_worldVersion, m_shape.dataOffsets, m_shape.ids, m_shape.compItems, m_shape.compOffs);
506 m_storage.chunks[chunkIdx] = pChunk;
507 }
508
509 pChunk->set_idx(chunkIdx);
510 pChunk->load(s);
511 }
512 }
513
516 void list_idx(uint32_t idx) {
517 m_runtime.listIdx = idx;
518 }
519
522 uint32_t list_idx() const {
523 return m_runtime.listIdx;
524 }
525
529 GAIA_NODISCARD bool cmp_comps(const ArchetypeLookupChecker& other) const {
530 return detail::cmp_comps(ids_view(), other.m_comps);
531 }
532
539 GAIA_NODISCARD static Archetype*
540 create(const World& world, ArchetypeId archetypeId, uint32_t& worldVersion, EntitySpan ids) {
541 const auto& cc = comp_cache(world);
542
543 auto* newArch = mem::AllocHelper::alloc<Archetype>("Archetype");
544 (void)new (newArch) Archetype(world, cc, worldVersion);
545
546 newArch->m_archetypeId = archetypeId;
547 newArch->m_shape.archetypeIdHash = ArchetypeIdLookupKey::calc(archetypeId);
548
549 // Calculate component mask. This will be used to early exit matching archetypes in simple queries.
550 // TODO: Performance could be improved if we're an archetype comming from one already known.
551 // We could simply take the predecessor's mask and update it with the new ids.
552 newArch->m_shape.queryMask = build_entity_mask({ids.data(), ids.size()});
553
554 const auto cnt = (uint32_t)ids.size();
555 newArch->m_shape.properties.cntEntities = (uint8_t)ids.size();
556
557 auto compItems = std::span(&newArch->m_shape.compItems[0], cnt);
558 GAIA_FOR(cnt) compItems[i] = ids[i].pair() ? cc.find_pair_payload(ids[i]) : cc.find(ids[i]);
559
560 // Calculate offsets
561 static auto ChunkDataAreaOffset = Chunk::chunk_data_area_offset();
562 newArch->update_data_offsets(
563 // This is not a real memory address.
564 // Chunk memory is organized as header+data. The offsets we calculate here belong to
565 // the data area.
566 // Every allocated chunk is going to have the same relative offset from the header part
567 // which is why providing a fictional relative offset is enough.
568 ChunkDataAreaOffset);
569 const auto& offs = newArch->m_shape.dataOffsets;
570 newArch->m_pPairIndex = PairIndexData::create(ids);
571
572 // Find the index of the last generic component in both arrays
573 const auto entsCnt = (uint32_t)ids.size();
574 uint32_t entsGeneric = entsCnt;
575 if (entsCnt > 0) {
576 for (auto i = entsCnt - 1; i != (uint32_t)-1; --i) {
577 if (ids[i].kind() != EntityKind::EK_Uni)
578 break;
579 --entsGeneric;
580 }
581 }
582
583 uint32_t genCompsSize = 0;
584 uint32_t uniCompsSize = 0;
585 GAIA_FOR(entsGeneric) genCompsSize += comp_from_item(newArch->m_shape.compItems[i]).size();
586 GAIA_FOR2(entsGeneric, cnt) uniCompsSize += comp_from_item(newArch->m_shape.compItems[i]).size();
587
588 auto compute_max_entities_for_chunk = [&](uint32_t maxEntities, uint32_t dataLimit) -> uint32_t {
589 uint32_t low = 1;
590 uint32_t high = maxEntities;
591 uint32_t best = 1;
592
593 // Helper to test if a given entity count fits in the chunk
594 auto try_fit = [&](uint32_t count) -> bool {
595 const uint32_t currOff = offs.firstByte_EntityData + (count * sizeof(Entity));
596
597 if (!est_max_entities_per_chunk(currOff, newArch->m_shape.compItems, entsGeneric, count, dataLimit))
598 return false;
599 if (!est_max_entities_per_chunk(
600 currOff, newArch->m_shape.compItems + entsGeneric, cnt - entsGeneric, 1, dataLimit))
601 return false;
602
603 return true;
604 };
605
606 // Binary search for the lookup
607 while (low <= high) {
608 uint32_t mid = (low + high) / 2;
609 if (try_fit(mid)) {
610 best = mid;
611 low = mid + 1;
612 } else {
613 high = mid - 1;
614 }
615 }
616
617 return best;
618 };
619
620 // Calculate the number of entities per chunk precisely so we can fit as many of them into a
621 // chunk as possible. Start at the smallest size class and only upsize when the archetype is
622 // wide enough that a smaller chunk can't hold the target row count.
623 constexpr uint32_t MinEntitiesPerChunk = 1536;
624 uint32_t maxGenItemsInArchetype = 0;
625
626 auto compute_max_entities_for_size_type = [&](uint32_t sizeType) -> uint32_t {
627 const uint32_t dataLimit = Chunk::chunk_data_bytes(mem_block_size(sizeType));
628 const uint32_t fixedSize = offs.firstByte_EntityData + uniCompsSize + 1;
629 GAIA_ASSERT(dataLimit > fixedSize);
630
631 // Theoretical maximum number of generic component rows we can fit into one chunk.
632 // This can be further reduced due to alignment and padding.
633 const uint32_t itemSize = genCompsSize + (uint32_t)sizeof(Entity);
634 const uint32_t maxEntities = (dataLimit - fixedSize) / itemSize;
635 return compute_max_entities_for_chunk(maxEntities > 0 ? maxEntities : 1, dataLimit);
636 };
637
638 if (archetypeId == 0) {
639 // Keep the root archetype compact enough to avoid growing the maximum row snapshot used by iterators.
640 maxGenItemsInArchetype = compute_max_entities_for_size_type(2);
641 } else {
642 for (uint32_t sizeType = 0; sizeType < MemoryBlockSizeClasses; ++sizeType) {
643 maxGenItemsInArchetype = compute_max_entities_for_size_type(sizeType);
644 if (maxGenItemsInArchetype >= MinEntitiesPerChunk)
645 break;
646 }
647 }
648
649 // MAX_CHUNK_ENTITIES is intentionally based on the 32 KiB class to keep iterator snapshots bounded.
650 // Wide archetypes can still use the 64 KiB-class blocks because their row count stays below this cap.
651 if (maxGenItemsInArchetype > ChunkHeader::MAX_CHUNK_ENTITIES)
652 maxGenItemsInArchetype = ChunkHeader::MAX_CHUNK_ENTITIES;
653
654 // Update the offsets according to the recalculated maxGenItemsInArchetype
655 auto currOff = offs.firstByte_EntityData + ((uint32_t)sizeof(Entity) * maxGenItemsInArchetype);
656 reg_components(
657 *newArch, ids, newArch->m_shape.compItems, (uint8_t)0, (uint8_t)entsGeneric, currOff,
658 maxGenItemsInArchetype);
659 reg_components(
660 *newArch, ids, newArch->m_shape.compItems, (uint8_t)entsGeneric, (uint8_t)ids.size(), currOff, 1);
661
662 newArch->m_shape.properties.capacity = (uint16_t)maxGenItemsInArchetype;
663 newArch->m_shape.properties.chunkDataBytes = (ChunkDataOffset)currOff;
664 newArch->m_shape.properties.genEntities = (uint8_t)entsGeneric;
665
666 return newArch;
667 }
668
671 void static destroy(Archetype* pArchetype) {
672 GAIA_ASSERT(pArchetype != nullptr);
673 pArchetype->~Archetype();
674 mem::AllocHelper::free("Archetype", pArchetype);
675 }
676
679 QueryMask queryMask() const {
680 return m_shape.queryMask;
681 }
682
685 ArchetypeIdLookupKey::LookupHash id_hash() const {
686 return m_shape.archetypeIdHash;
687 }
688
691 void set_hashes(LookupHash hashLookup) {
692 m_shape.hashLookup = hashLookup;
693 }
694
700 void enable_entity(Chunk* pChunk, uint16_t row, bool enableEntity, EntityContainers& recs) {
701 pChunk->enable_entity(row, enableEntity, recs);
702 // m_disabledMask.set(pChunk->idx(), enableEntity ? true : pChunk->has_disabled_entities());
703 }
704
707 void del(Chunk* pChunk) {
708 // Make sure there are any chunks to delete
709 GAIA_ASSERT(!m_storage.chunks.empty());
710
711 const auto chunkIndex = pChunk->idx();
712
713 // Make sure the chunk is a part of the chunk array
714 GAIA_ASSERT(chunkIndex == core::get_index(m_storage.chunks, pChunk));
715
716 // Remove the chunk from the chunk array. We are swapping this chunk's entry
717 // with the last one in the array. Therefore, we first update the last item's
718 // index with the current chunk's index and then do the swapping.
719 m_storage.chunks.back()->set_idx(chunkIndex);
720 core::swap_erase(m_storage.chunks, chunkIndex);
721
722 // Delete the chunk now. Otherwise, if the chunk happened to be the last
723 // one we would end up overriding released memory.
724 Chunk::free(pChunk);
725 }
726
731 GAIA_NODISCARD Chunk* foc_free_chunk() {
732 const auto chunkCnt = m_storage.chunks.size();
733
734 if (chunkCnt > 0) {
735 for (uint32_t i = m_storage.firstFreeChunkIdx; i < m_storage.chunks.size(); ++i) {
736 auto* pChunk = m_storage.chunks[i];
737 GAIA_ASSERT(pChunk != nullptr);
738 const auto entityCnt = pChunk->size();
739 if (entityCnt < pChunk->capacity()) {
740 m_storage.firstFreeChunkIdx = i;
741 return pChunk;
742 }
743 }
744 }
745
746 // Make sure not too many chunks are allocated
747 GAIA_ASSERT(chunkCnt < UINT32_MAX);
748
749 // No free space found anywhere. Let's create a new chunk.
750 auto* pChunk = Chunk::create(
751 m_world, m_cc, chunkCnt, //
752 m_shape.properties.capacity, m_shape.properties.cntEntities, //
753 m_shape.properties.genEntities, m_shape.properties.chunkDataBytes, //
754 m_worldVersion, m_shape.dataOffsets, m_shape.ids, m_shape.compItems, m_shape.compOffs);
755
756 m_storage.firstFreeChunkIdx = m_storage.chunks.size();
757 m_storage.chunks.push_back(pChunk);
758 return pChunk;
759 }
760
765 // This is expected to be called only if there are any chunks
766 GAIA_ASSERT(!m_storage.chunks.empty());
767
768 auto* pChunk = m_storage.chunks[m_storage.firstFreeChunkIdx];
769 if (pChunk->size() >= pChunk->capacity())
770 ++m_storage.firstFreeChunkIdx;
771 }
772
776 void try_update_free_chunk_idx(Chunk& chunkThatRemovedEntity) {
777 // This is expected to be called only if there are any chunks
778 GAIA_ASSERT(!m_storage.chunks.empty());
779
780 if (chunkThatRemovedEntity.idx() == m_storage.firstFreeChunkIdx)
781 return;
782
783 if (chunkThatRemovedEntity.idx() < m_storage.firstFreeChunkIdx) {
784 m_storage.firstFreeChunkIdx = chunkThatRemovedEntity.idx();
785 return;
786 }
787
788 auto* pChunk = m_storage.chunks[m_storage.firstFreeChunkIdx];
789 if (pChunk->size() >= pChunk->capacity())
790 ++m_storage.firstFreeChunkIdx;
791 }
792
797 void remove_entity_raw(Chunk& chunk, uint16_t row, EntityContainers& recs) {
798 chunk.remove_entity(row, recs);
799 try_update_free_chunk_idx(chunk);
800 }
801
806 void remove_entity(Chunk& chunk, uint16_t row, EntityContainers& recs) {
807 remove_entity_raw(chunk, row, recs);
808 chunk.update_versions();
809 }
810
813 GAIA_NODISCARD const Properties& props() const {
814 return m_shape.properties;
815 }
816
819 GAIA_NODISCARD const cnt::darray<Chunk*>& chunks() const {
820 return m_storage.chunks;
821 }
822
825 GAIA_NODISCARD LookupHash lookup_hash() const {
826 return m_shape.hashLookup;
827 }
828
831 GAIA_NODISCARD EntitySpan ids_view() const {
832 return {&m_shape.ids[0], m_shape.properties.cntEntities};
833 }
834
837 GAIA_NODISCARD ChunkDataOffsetSpan comp_offs_view() const {
838 return {&m_shape.compOffs[0], m_shape.properties.cntEntities};
839 }
840
843 GAIA_NODISCARD uint32_t pairs() const {
844 return m_pPairIndex != nullptr ? m_pPairIndex->pairCnt : 0;
845 }
846
849 GAIA_NODISCARD uint32_t pairs_is() const {
850 return m_pPairIndex != nullptr ? m_pPairIndex->pairCntIs : 0;
851 }
852
857 GAIA_NODISCARD uint32_t pair_matches(Entity pair) const {
858 return m_pPairIndex != nullptr ? m_pPairIndex->pair_matches(ids_view(), pair) : 0;
859 }
860
864 GAIA_NODISCARD Entity entity_from_pairs_as_idx(uint32_t idx) const {
865 GAIA_ASSERT(m_pPairIndex != nullptr);
866 return m_pPairIndex->entity_from_pairs_as_idx(ids_view(), idx);
867 }
868
871 GAIA_NODISCARD std::span<const uint8_t> pair_indices() const {
872 return m_pPairIndex != nullptr ? m_pPairIndex->pair_indices() : std::span<const uint8_t>{};
873 }
874
878 GAIA_NODISCARD std::span<const uint8_t> pair_rel_indices(Entity relation) const {
879 return m_pPairIndex != nullptr ? m_pPairIndex->pair_rel_indices(relation) : std::span<const uint8_t>{};
880 }
881
885 GAIA_NODISCARD std::span<const uint8_t> pair_tgt_indices(Entity target) const {
886 return m_pPairIndex != nullptr ? m_pPairIndex->pair_tgt_indices(target) : std::span<const uint8_t>{};
887 }
888
892 GAIA_NODISCARD bool has(Entity entity) const {
893 return core::has_if(ids_view(), [&](Entity e) {
894 return e == entity;
895 });
896 }
897
900 GAIA_ASSERT(m_runtime.observedTermCnt < m_shape.properties.cntEntities);
901 ++m_runtime.observedTermCnt;
902 }
903
906 GAIA_ASSERT(m_runtime.observedTermCnt > 0);
907 --m_runtime.observedTermCnt;
908 }
909
912 GAIA_NODISCARD bool has_observed_terms() const {
913 return m_runtime.observedTermCnt != 0;
914 }
915
919 template <typename T>
920 GAIA_NODISCARD bool has() const {
921 if constexpr (is_pair<T>::value) {
922 const auto rel = m_cc.get<typename T::rel>().entity;
923 const auto tgt = m_cc.get<typename T::tgt>().entity;
924 return has((Entity)Pair(rel, tgt));
925 } else {
926 const auto* pComp = m_cc.find<T>();
927 return pComp != nullptr && has(pComp->entity);
928 }
929 }
930
931 //----------------------------------------------------------------------
932
938 template <bool Enabled>
939 Entity get_flat_entity(size_t flatIdx) const {
940 size_t offset = 0;
941 for (const auto* pChunk: chunks()) {
942 if (pChunk->empty())
943 continue;
944
945 uint32_t cnt = 0;
946 if constexpr (Enabled) {
947 cnt = pChunk->size_enabled();
948 } else {
949 cnt = pChunk->size_disabled();
950 }
951
952 if (flatIdx < offset + cnt) {
953 if constexpr (Enabled) {
954 const auto idx = (uint32_t)(flatIdx - offset) + pChunk->size_disabled();
955 return pChunk->entity_view()[idx];
956 } else {
957 const auto idx = (uint32_t)(flatIdx - offset);
958 return pChunk->entity_view()[idx];
959 }
960 }
961
962 offset += cnt;
963 }
964
965 GAIA_ASSERT(false);
966 return EntityBad;
967 }
968
976 template <bool Enabled>
977 const void* get_flat_comp_ptr(uint32_t compIdx, size_t flatIdx, Entity& outEntity) const {
978 size_t offset = 0;
979 for (const auto* pChunk: chunks()) {
980 if (pChunk->empty())
981 continue;
982
983 uint32_t cnt = 0;
984 if constexpr (Enabled) {
985 cnt = pChunk->size_enabled();
986 } else {
987 cnt = pChunk->size_disabled();
988 }
989
990 if (flatIdx < offset + cnt) {
991 if constexpr (Enabled) {
992 const auto idx = (uint32_t)(flatIdx - offset) + pChunk->size_disabled();
993 const auto* pData = pChunk->comp_ptr(compIdx, idx);
994 outEntity = pChunk->entity_view()[idx];
995 return pData;
996 } else {
997 const auto idx = (uint32_t)(flatIdx - offset);
998 const auto* pData = pChunk->comp_ptr(compIdx, idx);
999 outEntity = pChunk->entity_view()[idx];
1000 return pData;
1001 }
1002 }
1003
1004 offset += cnt;
1005 }
1006
1007 GAIA_ASSERT(false);
1008 return nullptr;
1009 }
1010
1012 template <bool Enabled>
1013 void sort_entities_inter(size_t low, size_t high, TSortByFunc func) {
1014 if (low >= high)
1015 return;
1016
1017 Entity pivotEntity = get_flat_entity<Enabled>(high);
1018
1019 size_t i = low;
1020 for (size_t j = low; j < high; ++j) {
1021 Entity jEntity = get_flat_entity<Enabled>(j);
1022 if (func(m_world, &jEntity, &pivotEntity) < 0) {
1023 if (i != j) {
1024 Entity iEntity = get_flat_entity<Enabled>(i);
1025 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, jEntity);
1026 }
1027 ++i;
1028 }
1029 }
1030
1031 {
1032 Entity iEntity = get_flat_entity<Enabled>(i);
1033 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, pivotEntity);
1034 }
1035
1036 if (i > 0)
1037 sort_entities_inter<Enabled>(low, i - 1, func);
1038 sort_entities_inter<Enabled>(i + 1, high, func);
1039 }
1040
1042 template <bool Enabled>
1044 const ComponentCacheItem* pItem, uint32_t compIdx, size_t low, size_t high, TSortByFunc func) {
1045 if (low >= high)
1046 return;
1047
1048 Entity pivotEntity;
1049 const void* pPivotData = get_flat_comp_ptr<Enabled>(compIdx, high, pivotEntity);
1050
1051 size_t i = low;
1052 for (size_t j = low; j < high; ++j) {
1053 Entity jEntity;
1054 const void* jData = get_flat_comp_ptr<Enabled>(compIdx, j, jEntity);
1055 if (func(m_world, jData, pPivotData) < 0) {
1056 if (i != j) {
1057 Entity iEntity;
1058 (void)get_flat_comp_ptr<Enabled>(compIdx, i, iEntity);
1059 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, jEntity);
1060 }
1061 ++i;
1062 }
1063 }
1064
1065 {
1066 Entity iEntity;
1067 (void)get_flat_comp_ptr<Enabled>(compIdx, i, iEntity);
1068 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, pivotEntity);
1069 }
1070
1071 if (i > 0)
1072 sort_entities_inter<Enabled>(pItem, compIdx, low, i - 1, func);
1073 sort_entities_inter<Enabled>(pItem, compIdx, i + 1, high, func);
1074 }
1075
1079 void sort_entities(Entity entity, TSortByFunc func) {
1080 // TODO: We currently have to calculate the number of entities in the archetype from chunks.
1081 // Additionally, to get the right index we need to loop through chunks again because
1082 // the entities are not spread evenly among chunks (we can't just divide the index by
1083 // the number of chunks and module with the same number to get the index inside a chunk).
1084 // This is not optimal, and makes sorting more expensive.
1085
1086 if (entity == EntityBad) {
1087 {
1088 uint32_t entities = 0;
1089 for (const auto* pChunk: m_storage.chunks)
1090 entities += pChunk->size_enabled();
1091 if (entities != 0)
1092 sort_entities_inter<true>(0, entities - 1, func);
1093 }
1094 {
1095 uint32_t entities = 0;
1096 for (const auto* pChunk: m_storage.chunks)
1097 entities += pChunk->size_disabled();
1098 if (entities != 0)
1099 sort_entities_inter<false>(0, entities - 1, func);
1100 }
1101 } else {
1102 const auto* pItem = m_cc.find(entity);
1103 GAIA_ASSERT(pItem != nullptr && "Trying to sort by a component that has not been registered");
1104 if (pItem == nullptr)
1105 return;
1106
1107 const auto compIdx = chunks()[0]->comp_idx(entity);
1108 {
1109 uint32_t entities = 0;
1110 for (const auto* pChunk: m_storage.chunks)
1111 entities += pChunk->size_enabled();
1112 if (entities != 0)
1113 sort_entities_inter<true>(pItem, compIdx, 0, entities - 1, func);
1114 }
1115 {
1116 uint32_t entities = 0;
1117 for (const auto* pChunk: m_storage.chunks)
1118 entities += pChunk->size_disabled();
1119 if (entities != 0)
1120 sort_entities_inter<false>(pItem, compIdx, 0, entities - 1, func);
1121 }
1122 }
1123 }
1124
1125 //----------------------------------------------------------------------
1126
1130 void build_graph_edges(Archetype* pArchetypeRight, Entity entity) {
1131 // Loops can't happen
1132 GAIA_ASSERT(pArchetypeRight != this);
1133
1134 m_edges.graph.add_edge_right(entity, pArchetypeRight->id(), pArchetypeRight->id_hash());
1135 pArchetypeRight->build_graph_edges_left(this, entity);
1136 }
1137
1141 void build_graph_edges_left(Archetype* pArchetypeLeft, Entity entity) {
1142 // Loops can't happen
1143 GAIA_ASSERT(pArchetypeLeft != this);
1144
1145 m_edges.graph.add_edge_left(entity, pArchetypeLeft->id(), pArchetypeLeft->id_hash());
1146 }
1147
1151 void del_graph_edges(Archetype* pArchetypeRight, Entity entity) {
1152 // Loops can't happen
1153 GAIA_ASSERT(pArchetypeRight != this);
1154
1155 m_edges.graph.del_edge_right(entity);
1156 pArchetypeRight->del_graph_edges_left(this, entity);
1157 }
1158
1162 void del_graph_edges_left([[maybe_unused]] Archetype* pArchetypeLeft, Entity entity) {
1163 // Loops can't happen
1164 GAIA_ASSERT(pArchetypeLeft != this);
1165
1166 m_edges.graph.del_edge_left(entity);
1167 }
1168
1173 m_edges.graph.del_edge_right(entity);
1174 }
1175
1180 m_edges.graph.del_edge_left(entity);
1181 }
1182
1186 GAIA_NODISCARD ArchetypeGraphEdge find_edge_right(Entity entity) const {
1187 return m_edges.graph.find_edge_right(entity);
1188 }
1189
1193 GAIA_NODISCARD ArchetypeGraphEdge find_edge_left(Entity entity) const {
1194 return m_edges.graph.find_edge_left(entity);
1195 }
1196
1199 GAIA_NODISCARD auto& right_edges() {
1200 return m_edges.graph.right_edges();
1201 }
1202
1205 GAIA_NODISCARD const auto& right_edges() const {
1206 return m_edges.graph.right_edges();
1207 }
1208
1211 GAIA_NODISCARD auto& left_edges() {
1212 return m_edges.graph.left_edges();
1213 }
1214
1217 GAIA_NODISCARD const auto& left_edges() const {
1218 return m_edges.graph.left_edges();
1219 }
1220
1223 GAIA_NODISCARD bool empty() const {
1224 return m_storage.chunks.empty();
1225 }
1226
1228 void req_del() {
1229 m_runtime.deleteReq = 1;
1230 }
1231
1234 GAIA_NODISCARD bool is_req_del() const {
1235 return m_runtime.deleteReq;
1236 }
1237
1241 void set_max_lifespan(uint32_t lifespan) {
1242 GAIA_ASSERT(lifespan <= MAX_ARCHETYPE_LIFESPAN);
1243
1244 m_runtime.lifespanCountdownMax = 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 [%s]", entity.id(), entity.gen(), (int)name.size(), name.empty() ? "" : name.data(),
1307 EntityKindString[entity.kind()]);
1308 } else if (entity.pair()) {
1309 const auto rel = entity_name(world, entity.id());
1310 const auto tgt = entity_name(world, entity.gen());
1311 GAIA_LOG_N(
1312 " pair [%u:%u] %.*s -> %.*s", entity.id(), entity.gen(), (int)rel.size(),
1313 rel.empty() ? "" : rel.data(), (int)tgt.size(), tgt.empty() ? "" : tgt.data());
1314 } else {
1315 const auto& cc = comp_cache(world);
1316 const auto& desc = cc.get(entity);
1317 const auto symbol = desc.symbol_name();
1318 GAIA_LOG_N(
1319 " hash:%016" PRIx64 ", size:%3u B, align:%3u B, [%u:%u] %.*s [%s]", desc.hashLookup.hash,
1320 desc.comp.size(), desc.comp.alig(), desc.entity.id(), desc.entity.gen(), (int)symbol.size(),
1321 symbol.data(), EntityKindString[entity.kind()]);
1322 }
1323 }
1324
1328 static void diag_basic_info(const World& world, const Archetype& archetype) {
1329 auto ids = archetype.ids_view();
1330
1331 // Calculate the number of entities in archetype
1332 uint32_t entCnt = 0;
1333 uint32_t entCntDisabled = 0;
1334 for (const auto* chunk: archetype.m_storage.chunks) {
1335 entCnt += chunk->size();
1336 entCntDisabled += chunk->size_disabled();
1337 }
1338
1339 // Calculate the number of components
1340 uint32_t genCompsSize = 0;
1341 uint32_t uniCompsSize = 0;
1342 {
1343 const auto& p = archetype.props();
1344 GAIA_FOR(p.genEntities) genCompsSize += comp_from_item(archetype.m_shape.compItems[i]).size();
1345 GAIA_FOR2(p.genEntities, p.cntEntities)
1346 uniCompsSize += comp_from_item(archetype.m_shape.compItems[i]).size();
1347 }
1348
1349 const auto chunkBytes = Chunk::chunk_total_bytes(archetype.props().chunkDataBytes);
1350 const auto sizeType = mem_block_size_type(chunkBytes);
1351 const auto allocSize = mem_block_size(sizeType) / 1024;
1352
1353 GAIA_LOG_N(
1354 "aid:%u, "
1355 "hash:%016" PRIx64 ", "
1356 "chunks:%u (%uK), data:%u/%u/%u B, "
1357 "entities:%u/%u/%u",
1358 archetype.id(), archetype.lookup_hash().hash, (uint32_t)archetype.chunks().size(), allocSize, genCompsSize,
1359 uniCompsSize, archetype.props().chunkDataBytes, entCnt, entCntDisabled, archetype.props().capacity);
1360
1361 if (!ids.empty()) {
1362 GAIA_LOG_N(" Components - count:%u", (uint32_t)ids.size());
1363 for (const auto ent: ids)
1364 diag_entity(world, ent);
1365 }
1366 }
1367
1371 static void diag_graph_info(const World& world, const Archetype& archetype) {
1372 archetype.m_edges.graph.diag(world);
1373 }
1374
1377 static void diag_chunk_info(const Archetype& archetype) {
1378 const auto& chunks = archetype.m_storage.chunks;
1379 if (chunks.empty())
1380 return;
1381
1382 GAIA_LOG_N(" Chunks");
1383 for (const auto* pChunk: chunks)
1384 pChunk->diag();
1385 }
1386
1390 static void diag_entity_info(const World& world, const Archetype& archetype) {
1391 const auto& chunks = archetype.m_storage.chunks;
1392 if (chunks.empty())
1393 return;
1394
1395 GAIA_LOG_N(" Entities");
1396 bool noEntities = true;
1397 for (const auto* pChunk: chunks) {
1398 if (pChunk->empty())
1399 continue;
1400 noEntities = false;
1401
1402 auto ev = pChunk->entity_view();
1403 for (auto entity: ev)
1404 diag_entity(world, entity);
1405 }
1406 if (noEntities)
1407 GAIA_LOG_N(" N/A");
1408 }
1409
1413 static void diag(const World& world, const Archetype& archetype) {
1414 diag_basic_info(world, archetype);
1415 diag_graph_info(world, archetype);
1416 diag_chunk_info(archetype);
1417 diag_entity_info(world, archetype);
1418 }
1419 };
1420
1423 class GAIA_API ArchetypeLookupKey final {
1424 Archetype::LookupHash m_hash;
1425 const ArchetypeBase* m_pArchetypeBase;
1426
1427 public:
1429 static constexpr bool IsDirectHashKey = true;
1430
1431 ArchetypeLookupKey(): m_hash({0}), m_pArchetypeBase(nullptr) {}
1432 explicit ArchetypeLookupKey(Archetype::LookupHash hash, const ArchetypeBase* pArchetypeBase):
1433 m_hash(hash), m_pArchetypeBase(pArchetypeBase) {}
1434
1437 GAIA_NODISCARD size_t hash() const {
1438 return (size_t)m_hash.hash;
1439 }
1440
1443 GAIA_NODISCARD Archetype* archetype() const {
1444 return (Archetype*)m_pArchetypeBase;
1445 }
1446
1447 GAIA_NODISCARD bool operator==(const ArchetypeLookupKey& other) const {
1448 // Hash doesn't match we don't have a match.
1449 // Hash collisions are expected to be very unlikely so optimize for this case.
1450 if GAIA_LIKELY (m_hash != other.m_hash)
1451 return false;
1452
1453 const auto id = m_pArchetypeBase->id();
1454 if (id == ArchetypeIdBad) {
1455 const auto* pArchetype = (const Archetype*)other.m_pArchetypeBase;
1456 const auto* pArchetypeLookupChecker = (const ArchetypeLookupChecker*)m_pArchetypeBase;
1457 return pArchetype->cmp_comps(*pArchetypeLookupChecker);
1458 }
1459
1460 // Real ArchetypeID is given. Compare the pointers.
1461 // Normally we'd compare archetype IDs but because we do not allow archetype copies and all archetypes are
1462 // unique it's guaranteed that if pointers are the same we have a match.
1463 // This also saves a pointer indirection because we do not access the memory the pointer points to.
1464 return m_pArchetypeBase == other.m_pArchetypeBase;
1465 }
1466 };
1467
1468 using ArchetypeMapByHash = cnt::map<ArchetypeLookupKey, Archetype*>;
1469 } // namespace ecs
1470} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:27
GAIA_NODISCARD size_type size() const noexcept
Returns the number of elements.
Definition darray_impl.h:504
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:1423
GAIA_NODISCARD size_t hash() const
Hash of the archetype lookup value.
Definition archetype.h:1437
GAIA_NODISCARD Archetype * archetype() const
Resolves the lookup key to its concrete archetype.
Definition archetype.h:1443
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:939
void observed_terms_dec()
Decrements the number of terms observing this archetype.
Definition archetype.h:905
QueryMask queryMask() const
Query mask describing which simple queries may match this archetype.
Definition archetype.h:679
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:540
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:731
void set_max_lifespan(uint32_t lifespan)
Sets maximal lifespan of an archetype.
Definition archetype.h:1241
void build_graph_edges(Archetype *pArchetypeRight, Entity entity)
Builds a graph edge from this archetype to the right archetype.
Definition archetype.h:1130
GAIA_NODISCARD bool cmp_comps(const ArchetypeLookupChecker &other) const
Compares the lookup checker component spans in archetype order.
Definition archetype.h:529
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:1043
GAIA_NODISCARD EntitySpan ids_view() const
Span over the component and entity identifiers defining the archetype shape.
Definition archetype.h:831
void set_hashes(LookupHash hashLookup)
Sets hashes for each component type and lookup.
Definition archetype.h:691
void observed_terms_inc()
Increments the number of terms observing this archetype.
Definition archetype.h:899
GAIA_NODISCARD const auto & left_edges() const
Const view over the archetype "del" graph edges.
Definition archetype.h:1217
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:1328
static void destroy(Archetype *pArchetype)
Destroys the archetype and frees its memory.
Definition archetype.h:671
GAIA_NODISCARD auto & left_edges()
Mutable view over the archetype "del" graph edges.
Definition archetype.h:1211
GAIA_NODISCARD uint32_t pairs() const
Returns the number of pairs registered in the archetype.
Definition archetype.h:843
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:1179
void remove_entity(Chunk &chunk, uint16_t row, EntityContainers &recs)
Removes an entity from the chunk and updates the chunk versions.
Definition archetype.h:806
void del(Chunk *pChunk)
Removes a chunk from the list of chunks managed by their archetype and deletes its memory.
Definition archetype.h:707
GAIA_NODISCARD uint32_t pairs_is() const
Returns the number of Is pairs registered in the archetype.
Definition archetype.h:849
void sort_entities_inter(size_t low, size_t high, TSortByFunc func)
Generic in-place quicksort across chunks.
Definition archetype.h:1013
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:912
ArchetypeIdLookupKey::LookupHash id_hash() const
Archetype id hash used for graph edge storage.
Definition archetype.h:685
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:1151
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:977
GAIA_NODISCARD const Properties & props() const
Shape properties of this archetype.
Definition archetype.h:813
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:700
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:1193
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:1413
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:825
GAIA_NODISCARD bool empty() const
Checks is there are no chunk in the archetype.
Definition archetype.h:1223
void save(ser::serializer &s)
Serializes the archetype: free-chunk index, list index, and each chunk.
Definition archetype.h:473
GAIA_NODISCARD bool has(Entity entity) const
Checks if an entity is a part of the archetype.
Definition archetype.h:892
GAIA_NODISCARD const auto & right_edges() const
Const view over the archetype "add" graph edges.
Definition archetype.h:1205
GAIA_NODISCARD bool is_req_del() const
Returns true if this archetype is requested to be deleted.
Definition archetype.h:1234
void load(ser::serializer &s)
Deserializes the archetype, recreating missing chunks from the shape description.
Definition archetype.h:486
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:1162
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:516
GAIA_NODISCARD auto & right_edges()
Mutable view over the archetype "add" graph edges.
Definition archetype.h:1199
GAIA_NODISCARD bool has() const
Checks if component T is present in the chunk.
Definition archetype.h:920
GAIA_NODISCARD ChunkDataOffsetSpan comp_offs_view() const
Span over the per-term component data offsets.
Definition archetype.h:837
void build_graph_edges_left(Archetype *pArchetypeLeft, Entity entity)
Records a cached "del" graph edge from entity entity to this archetype.
Definition archetype.h:1141
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:764
GAIA_NODISCARD ArchetypeGraphEdge find_edge_right(Entity entity) const
Checks if an archetype graph "add" edge with entity entity exists.
Definition archetype.h:1186
void req_del()
Request deleting the archetype.
Definition archetype.h:1228
uint32_t list_idx() const
Archetype index in the world list of active archetypes.
Definition archetype.h:522
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:776
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:885
GAIA_NODISCARD Entity entity_from_pairs_as_idx(uint32_t idx) const
Resolves a relation-pair index to its pair entity.
Definition archetype.h:864
static void diag_entity_info(const World &world, const Archetype &archetype)
Logs entity diagnostics for each entity of the archetype.
Definition archetype.h:1390
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:857
void remove_entity_raw(Chunk &chunk, uint16_t row, EntityContainers &recs)
Removes an entity from the chunk.
Definition archetype.h:797
static void diag_graph_info(const World &world, const Archetype &archetype)
Logs the archetype graph edge diagnostics.
Definition archetype.h:1371
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:878
GAIA_NODISCARD std::span< const uint8_t > pair_indices() const
Indices of all relation pairs within the ids array.
Definition archetype.h:871
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:1172
GAIA_NODISCARD const cnt::darray< Chunk * > & chunks() const
Chunks managed by this archetype.
Definition archetype.h:819
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:1377
void sort_entities(Entity entity, TSortByFunc func)
Sorts all entities in the archetypes according to the given function.
Definition archetype.h:1079
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:1270
GAIA_NODISCARD uint32_t idx() const
Returns the index of this chunk in its archetype's storage.
Definition chunk.h:1815
void update_versions()
Updates the version numbers for this chunk.
Definition chunk.h:619
GAIA_NODISCARD uint16_t size() const
Returns the total number of entities in the chunk (both enabled and disabled)
Definition chunk.h:1908
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:1399
Owns entities, components, archetypes, queries, observers, and systems.
Definition world.h:80
Wrapper for two Entities forming a relationship pair.
Definition id.h:614
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:262
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:179
EntityId id
Relation or target entity id the bucket counts.
Definition archetype.h:181
uint8_t count
Number of pair indices belonging to this relation or target.
Definition archetype.h:185
uint8_t start
First pair index within the archetype ids array.
Definition archetype.h:183
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:118
ChunkDataOffset chunkDataBytes
How many bytes of data is needed for a fully utilized chunk.
Definition archetype.h:114
uint8_t genEntities
The number of generic entities/components.
Definition archetype.h:116
Identifier of a registered component type. Packs the component id, size, alignment,...
Definition id.h:42
Identifier of an entity or component instance in the world. Packs the entity index,...
Definition id.h:296
GAIA_NODISCARD constexpr auto gen() const noexcept
Generation index of the entity.
Definition id.h:365
GAIA_NODISCARD constexpr bool pair() const noexcept
Whether this id refers to a relationship pair.
Definition id.h:377
GAIA_NODISCARD constexpr auto kind() const noexcept
Entity kind of this id.
Definition id.h:389
GAIA_NODISCARD constexpr auto id() const noexcept
Entity index in the entity array.
Definition id.h:359
GAIA_NODISCARD constexpr bool entity() const noexcept
Whether this id refers to an entity.
Definition id.h:371
Detects whether a type is a relationship pair.
Definition id.h:285
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