Gaia-ECS v0.9.3
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
30 namespace detail {
31 GAIA_NODISCARD inline bool cmp_comps(EntitySpan comps, EntitySpan compsOther) {
32 const auto s0 = comps.size();
33 const auto s1 = compsOther.size();
34
35 // Size has to match
36 if (s0 != s1)
37 return false;
38
39 // Elements have to match
40 GAIA_FOR(s0) {
41 if (comps[i] != compsOther[i])
42 return false;
43 }
44
45 return true;
46 }
47 } // namespace detail
48
50 Archetype* pArchetype;
51 Chunk* pChunk;
52
53 GAIA_NODISCARD bool operator==(const ArchetypeChunkPair& other) const {
54 return pArchetype == other.pArchetype && pChunk == other.pChunk;
55 }
56 };
57
59 protected:
61 ArchetypeId m_archetypeId = ArchetypeIdBad;
62
63 public:
64 GAIA_NODISCARD ArchetypeId id() const {
65 return m_archetypeId;
66 }
67 };
68
70 friend class Archetype;
71
73 EntitySpan m_comps;
74
75 public:
76 ArchetypeLookupChecker(EntitySpan comps): m_comps(comps) {}
77
78 GAIA_NODISCARD bool cmp_comps(const ArchetypeLookupChecker& other) const {
79 return detail::cmp_comps(m_comps, other.m_comps);
80 }
81 };
82
83 class GAIA_API Archetype final: public ArchetypeBase {
84 public:
86
88 static constexpr uint16_t ARCHETYPE_LIFESPAN_BITS = 7;
90 static_assert(ARCHETYPE_LIFESPAN_BITS >= ChunkHeader::CHUNK_LIFESPAN_BITS);
92 static constexpr uint16_t MAX_ARCHETYPE_LIFESPAN = (1 << ARCHETYPE_LIFESPAN_BITS) - 1;
93
94 struct Properties {
96 uint16_t capacity;
98 ChunkDataOffset chunkDataBytes;
100 uint8_t genEntities;
102 uint8_t cntEntities;
103 };
104
105 private:
106 GAIA_NODISCARD static Component empty_comp() noexcept {
107 return Component(IdentifierIdBad, 0, 0, 0, DataStorageType::Table);
108 }
109
110 GAIA_NODISCARD static Component comp_from_item(const ComponentCacheItem* pItem) noexcept {
111 return pItem == nullptr ? empty_comp() : pItem->comp;
112 }
113
114 struct ShapeData {
115 ArchetypeIdLookupKey::LookupHash archetypeIdHash;
117 LookupHash hashLookup = {0};
119 QueryMask queryMask{};
120 Properties properties{};
122 ChunkDataOffsets dataOffsets{};
124 Entity ids[ChunkHeader::MAX_COMPONENTS];
126 const ComponentCacheItem* compItems[ChunkHeader::MAX_COMPONENTS]{};
128 ChunkDataOffset compOffs[ChunkHeader::MAX_COMPONENTS];
129 };
130
131 struct StorageData {
133 cnt::darray<Chunk*> chunks;
135 uint32_t firstFreeChunkIdx = 0;
136 };
137
138 struct RuntimeData {
140 uint32_t listIdx = BadIndex;
142 uint8_t observedTermCnt = 0;
143
145 uint32_t deleteReq : 1;
147 uint32_t dead : 1;
149 uint32_t lifespanCountdownMax : ARCHETYPE_LIFESPAN_BITS;
151 uint32_t lifespanCountdown : ARCHETYPE_LIFESPAN_BITS;
152
153 RuntimeData(): deleteReq(0), dead(0), lifespanCountdownMax(1), lifespanCountdown(0) {}
154 };
155
156 struct EdgeData {
158 ArchetypeGraph graph;
159 };
160
161 struct PairIndexData {
163 EntityId id = IdentifierIdBad;
164 uint8_t start = 0;
165 uint8_t count = 0;
166 };
167
169 uint8_t pairIndexBuffer[ChunkHeader::MAX_COMPONENTS];
171 uint8_t pairsAsIndexBuffer[ChunkHeader::MAX_COMPONENTS];
172 uint8_t pairRelIndexBuffer[ChunkHeader::MAX_COMPONENTS];
173 uint8_t pairTgtIndexBuffer[ChunkHeader::MAX_COMPONENTS];
175 PairCountBucket pairRelCountBuffer[ChunkHeader::MAX_COMPONENTS];
176 PairCountBucket pairTgtCountBuffer[ChunkHeader::MAX_COMPONENTS];
178 uint8_t pairCnt = 0;
180 uint8_t pairCntIs = 0;
182 uint8_t pairRelCountCnt = 0;
184 uint8_t pairTgtCountCnt = 0;
185
186 static PairIndexData* create(EntitySpan ids) {
187 auto* pPairIndex = mem::AllocHelper::alloc<PairIndexData>("ArchetypePairIndex");
188 (void)new (pPairIndex) PairIndexData();
189
190 uint8_t pairRelIndexCnt = 0;
191 uint8_t pairTgtIndexCnt = 0;
192
193 GAIA_FOR(ids.size()) {
194 if (!ids[i].pair())
195 continue;
196
197 pPairIndex->pairIndexBuffer[pPairIndex->pairCnt] = (uint8_t)i;
198 ++pPairIndex->pairCnt;
199 add_pair_index_bucket(
200 pPairIndex->pairRelCountBuffer, pPairIndex->pairRelCountCnt, pPairIndex->pairRelIndexBuffer,
201 pairRelIndexCnt, (EntityId)ids[i].id(), (uint8_t)i);
202 add_pair_index_bucket(
203 pPairIndex->pairTgtCountBuffer, pPairIndex->pairTgtCountCnt, pPairIndex->pairTgtIndexBuffer,
204 pairTgtIndexCnt, (EntityId)ids[i].gen(), (uint8_t)i);
205
206 if (ids[i].id() == Is.id())
207 pPairIndex->pairsAsIndexBuffer[pPairIndex->pairCntIs++] = (uint8_t)i;
208 }
209
210 if (pPairIndex->pairCnt == 0) {
211 destroy(pPairIndex);
212 return nullptr;
213 }
214
215 return pPairIndex;
216 }
217
218 static void destroy(PairIndexData* pPairIndex) {
219 if (pPairIndex == nullptr)
220 return;
221
222 pPairIndex->~PairIndexData();
223 mem::AllocHelper::free("ArchetypePairIndex", pPairIndex);
224 }
225
226 static PairCountBucket&
227 ensure_pair_index_bucket(PairCountBucket* pBuckets, uint8_t& bucketCnt, EntityId id, uint8_t start) {
228 GAIA_FOR(bucketCnt) {
229 if (pBuckets[i].id == id)
230 return pBuckets[i];
231 }
232
233 GAIA_ASSERT(bucketCnt < ChunkHeader::MAX_COMPONENTS);
234 auto& bucket = pBuckets[bucketCnt++];
235 bucket.id = id;
236 bucket.start = start;
237 bucket.count = 0;
238 return bucket;
239 }
240
241 static void add_pair_index_bucket(
242 PairCountBucket* pBuckets, uint8_t& bucketCnt, uint8_t* pIndexBuffer, uint8_t& indexCnt, EntityId id,
243 uint8_t idsIdx) {
244 auto& bucket = ensure_pair_index_bucket(pBuckets, bucketCnt, id, indexCnt);
245 GAIA_ASSERT(indexCnt < ChunkHeader::MAX_COMPONENTS);
246 pIndexBuffer[indexCnt++] = idsIdx;
247 ++bucket.count;
248 }
249
250 static uint32_t pair_count_from_buckets(const PairCountBucket* pBuckets, uint8_t bucketCnt, EntityId id) {
251 GAIA_FOR(bucketCnt) {
252 if (pBuckets[i].id == id)
253 return pBuckets[i].count;
254 }
255
256 return 0;
257 }
258
259 GAIA_NODISCARD static std::span<const uint8_t> pair_indices_from_buckets(
260 const PairCountBucket* pBuckets, uint8_t bucketCnt, const uint8_t* pIndexBuffer, EntityId id) {
261 GAIA_FOR(bucketCnt) {
262 if (pBuckets[i].id != id)
263 continue;
264
265 return {pIndexBuffer + pBuckets[i].start, pBuckets[i].count};
266 }
267
268 return {};
269 }
270
271 GAIA_NODISCARD uint32_t pair_matches(EntitySpan ids, Entity pair) const {
272 GAIA_ASSERT(pair.pair());
273
274 if (pair == Pair(All, All))
275 return pairCnt;
276
277 if (pair.id() == All.id())
278 return pair_count_from_buckets(pairTgtCountBuffer, pairTgtCountCnt, (EntityId)pair.gen());
279
280 if (pair.gen() == All.id())
281 return pair_count_from_buckets(pairRelCountBuffer, pairRelCountCnt, (EntityId)pair.id());
282
283 return core::has_if(
284 ids,
285 [pair](Entity entity) {
286 return entity == pair;
287 })
288 ? 1u
289 : 0u;
290 }
291
292 GAIA_NODISCARD Entity entity_from_pairs_as_idx(EntitySpan ids, uint32_t idx) const {
293 const auto idsIdx = pairsAsIndexBuffer[idx];
294 return ids[idsIdx];
295 }
296
297 GAIA_NODISCARD std::span<const uint8_t> pair_indices() const {
298 return {&pairIndexBuffer[0], pairCnt};
299 }
300
301 GAIA_NODISCARD std::span<const uint8_t> pair_rel_indices(Entity relation) const {
302 return pair_indices_from_buckets(
303 pairRelCountBuffer, pairRelCountCnt, pairRelIndexBuffer, (EntityId)relation.id());
304 }
305
306 GAIA_NODISCARD std::span<const uint8_t> pair_tgt_indices(Entity target) const {
307 return pair_indices_from_buckets(
308 pairTgtCountBuffer, pairTgtCountCnt, pairTgtIndexBuffer, (EntityId)target.id());
309 }
310 };
311
313 const World& m_world;
315 const ComponentCache& m_cc;
317 uint32_t& m_worldVersion;
318
319 ShapeData m_shape{};
320 StorageData m_storage{};
322 // cnt::dbitset m_disabledMask;
323 EdgeData m_edges{};
324 RuntimeData m_runtime{};
325 PairIndexData* m_pPairIndex = nullptr;
326
328 Archetype(const World& world, const ComponentCache& cc, uint32_t& worldVersion):
329 m_world(world), m_cc(cc), m_worldVersion(worldVersion) //
330 {}
331
332 ~Archetype() {
333 // Delete all archetype chunks
334 for (auto* pChunk: m_storage.chunks)
335 Chunk::free(pChunk);
336 PairIndexData::destroy(m_pPairIndex);
337 }
338
342 void update_data_offsets(uintptr_t memoryAddress) {
343 uintptr_t offset = 0;
344
345 // Versions
346 // We expect versions to fit in the first 256 bytes.
347 // With 32 components per archetype this gives us some headroom.
348 {
349 offset += mem::padding<alignof(ComponentVersion)>(memoryAddress);
350
351 const auto cnt = m_shape.properties.cntEntities + 1; // + 1 for entities
352 GAIA_ASSERT(offset < 256);
353 m_shape.dataOffsets.firstByte_Versions = (ChunkDataVersionOffset)offset;
354 offset += sizeof(ComponentVersion) * cnt;
355 }
356
357 // Entity ids
358 {
359 offset += mem::padding<alignof(Entity)>(offset);
360
361 const auto cnt = m_shape.properties.cntEntities;
362 if (cnt != 0) {
363 m_shape.dataOffsets.firstByte_CompEntities = (ChunkDataOffset)offset;
364
365 // Storage-wise, treat the component array as it it were MAX_COMPONENTS long.
366 offset += sizeof(Entity) * ChunkHeader::MAX_COMPONENTS;
367 }
368 }
369
370 // Component records
371 {
372 offset += mem::padding<alignof(ComponentRecord)>(offset);
373
374 const auto cnt = m_shape.properties.cntEntities;
375 if (cnt != 0) {
376
377 m_shape.dataOffsets.firstByte_Records = (ChunkDataOffset)offset;
378
379 // Storage-wise, treat the component array as it it were MAX_COMPONENTS long.
380 offset += sizeof(ComponentRecord) * cnt;
381 }
382 }
383
384 // First entity offset
385 {
386 offset += mem::padding<alignof(Entity)>(offset);
387 m_shape.dataOffsets.firstByte_EntityData = (ChunkDataOffset)offset;
388 }
389 }
390
399 static bool est_max_entities_per_chunk(
400 uint32_t offs, const ComponentCacheItem* const* pItems, uint32_t cnt, uint32_t cap, uint32_t maxDataOffset) {
401 GAIA_FOR(cnt) {
402 const auto comp = comp_from_item(pItems[i]);
403 if (!component_has_inline_data(comp))
404 continue;
405
406 const auto* pItem = pItems[i];
407 GAIA_ASSERT(pItem != nullptr);
408
409 // If we're beyond what the chunk could take, subtract one entity
410 offs = pItem->calc_new_mem_offset(offs, cap);
411 if (offs >= maxDataOffset)
412 return false;
413 }
414
415 return true;
416 }
417
418 static void reg_components(
419 Archetype& arch, EntitySpan ids, const ComponentCacheItem* const* pItems, uint8_t from, uint8_t to,
420 uint32_t& currOff, uint32_t count) {
421 auto& ofs = arch.m_shape.compOffs;
422
423 // Set component ids
424 GAIA_FOR2(from, to) arch.m_shape.ids[i] = ids[i];
425
426 // Calculate offsets and assign them indices according to our mappings
427 GAIA_FOR2(from, to) {
428 const auto comp = comp_from_item(pItems[i]);
429 const auto compIdx = i;
430
431 if (!component_has_inline_data(comp)) {
432 ofs[compIdx] = {};
433 } else {
434 const auto alig = comp.alig();
435 currOff = mem::align(currOff, alig);
436 ofs[compIdx] = (ChunkDataOffset)currOff;
437
438 // Make sure the following component list is properly aligned
439 currOff += comp.size() * count;
440 }
441 }
442 }
443
444 public:
445 Archetype(Archetype&&) = delete;
446 Archetype(const Archetype&) = delete;
447 Archetype& operator=(Archetype&&) = delete;
448 Archetype& operator=(const Archetype&) = delete;
449
450 void save(ser::serializer& s) {
451 s.save(m_storage.firstFreeChunkIdx);
452 s.save(m_runtime.listIdx);
453
454 s.save((uint32_t)m_storage.chunks.size());
455 for (auto* pChunk: m_storage.chunks) {
456 s.save(pChunk->idx());
457 pChunk->save(s);
458 }
459 }
460
461 void load(ser::serializer& s) {
462 s.load(m_storage.firstFreeChunkIdx);
463 s.load(m_runtime.listIdx);
464
465 uint32_t chunkCnt = 0;
466 s.load(chunkCnt);
467 m_storage.chunks.resize(chunkCnt, nullptr);
468
469 GAIA_FOR(chunkCnt) {
470 uint32_t chunkIdx = 0;
471 s.load(chunkIdx);
472
473 auto* pChunk = m_storage.chunks[chunkIdx];
474 // If the chunk doesn't exist it means it's not a part of the initial setup.
475 if (pChunk == nullptr) {
476 pChunk = Chunk::create(
477 m_world, m_cc, chunkIdx, //
478 m_shape.properties.capacity, m_shape.properties.cntEntities, //
479 m_shape.properties.genEntities, m_shape.properties.chunkDataBytes, //
480 m_worldVersion, m_shape.dataOffsets, m_shape.ids, m_shape.compItems, m_shape.compOffs);
481 m_storage.chunks[chunkIdx] = pChunk;
482 }
483
484 pChunk->set_idx(chunkIdx);
485 pChunk->load(s);
486 }
487 }
488
489 void list_idx(uint32_t idx) {
490 m_runtime.listIdx = idx;
491 }
492
493 uint32_t list_idx() const {
494 return m_runtime.listIdx;
495 }
496
497 GAIA_NODISCARD bool cmp_comps(const ArchetypeLookupChecker& other) const {
498 return detail::cmp_comps(ids_view(), other.m_comps);
499 }
500
501 GAIA_NODISCARD static Archetype*
502 create(const World& world, ArchetypeId archetypeId, uint32_t& worldVersion, EntitySpan ids) {
503 const auto& cc = comp_cache(world);
504
505 auto* newArch = mem::AllocHelper::alloc<Archetype>("Archetype");
506 (void)new (newArch) Archetype(world, cc, worldVersion);
507
508 newArch->m_archetypeId = archetypeId;
509 newArch->m_shape.archetypeIdHash = ArchetypeIdLookupKey::calc(archetypeId);
510
511 // Calculate component mask. This will be used to early exit matching archetypes in simple queries.
512 // TODO: Performance could be improved if we're an archetype comming from one already known.
513 // We could simply take the predecessor's mask and update it with the new ids.
514 newArch->m_shape.queryMask = build_entity_mask({ids.data(), ids.size()});
515
516 const auto cnt = (uint32_t)ids.size();
517 newArch->m_shape.properties.cntEntities = (uint8_t)ids.size();
518
519 auto compItems = std::span(&newArch->m_shape.compItems[0], cnt);
520 GAIA_FOR(cnt) {
521 const ComponentCacheItem* pItem = nullptr;
522 if (ids[i].pair()) {
523 // When using pairs we need to decode the storage type from them.
524 // This is what pair<Rel, Tgt>::type actually does to determine what type to use at compile-time.
525 Entity pairEntities[] = {pair_rel(world, ids[i]), pair_tgt(world, ids[i])};
526 const auto* pRelItem = cc.find(pairEntities[0]);
527 const auto* pTgtItem = cc.find(pairEntities[1]);
528 Component pairComponents[] = {comp_from_item(pRelItem), comp_from_item(pTgtItem)};
529 const uint32_t idx = (pairComponents[0].size() != 0U || pairComponents[1].size() == 0U) ? 0 : 1;
530 pItem = idx == 0 ? pRelItem : pTgtItem;
531 } else {
532 pItem = cc.find(ids[i]);
533 }
534 compItems[i] = pItem;
535 }
536
537 // Calculate offsets
538 static auto ChunkDataAreaOffset = Chunk::chunk_data_area_offset();
539 newArch->update_data_offsets(
540 // This is not a real memory address.
541 // Chunk memory is organized as header+data. The offsets we calculate here belong to
542 // the data area.
543 // Every allocated chunk is going to have the same relative offset from the header part
544 // which is why providing a fictional relative offset is enough.
545 ChunkDataAreaOffset);
546 const auto& offs = newArch->m_shape.dataOffsets;
547 newArch->m_pPairIndex = PairIndexData::create(ids);
548
549 // Find the index of the last generic component in both arrays
550 const auto entsCnt = (uint32_t)ids.size();
551 uint32_t entsGeneric = entsCnt;
552 if (entsCnt > 0) {
553 for (auto i = entsCnt - 1; i != (uint32_t)-1; --i) {
554 if (ids[i].kind() != EntityKind::EK_Uni)
555 break;
556 --entsGeneric;
557 }
558 }
559
560 uint32_t genCompsSize = 0;
561 uint32_t uniCompsSize = 0;
562 GAIA_FOR(entsGeneric) genCompsSize += comp_from_item(newArch->m_shape.compItems[i]).size();
563 GAIA_FOR2(entsGeneric, cnt) uniCompsSize += comp_from_item(newArch->m_shape.compItems[i]).size();
564
565 auto compute_max_entities_for_chunk = [&](uint32_t maxEntities, uint32_t dataLimit) -> uint32_t {
566 uint32_t low = 1;
567 uint32_t high = maxEntities;
568 uint32_t best = 1;
569
570 // Helper to test if a given entity count fits in the chunk
571 auto try_fit = [&](uint32_t count) -> bool {
572 const uint32_t currOff = offs.firstByte_EntityData + (count * sizeof(Entity));
573
574 if (!est_max_entities_per_chunk(currOff, newArch->m_shape.compItems, entsGeneric, count, dataLimit))
575 return false;
576 if (!est_max_entities_per_chunk(
577 currOff, newArch->m_shape.compItems + entsGeneric, cnt - entsGeneric, 1, dataLimit))
578 return false;
579
580 return true;
581 };
582
583 // Binary search for the lookup
584 while (low <= high) {
585 uint32_t mid = (low + high) / 2;
586 if (try_fit(mid)) {
587 best = mid;
588 low = mid + 1;
589 } else {
590 high = mid - 1;
591 }
592 }
593
594 return best;
595 };
596
597 // Calculate the number of entities per chunk precisely so we can fit as many of them into a
598 // chunk as possible. Start at the smallest size class and only upsize when the archetype is
599 // wide enough that a smaller chunk can't hold the target row count.
600 constexpr uint32_t MinEntitiesPerChunk = 1536;
601 uint32_t maxGenItemsInArchetype = 0;
602
603 auto compute_max_entities_for_size_type = [&](uint32_t sizeType) -> uint32_t {
604 const uint32_t dataLimit = Chunk::chunk_data_bytes(mem_block_size(sizeType));
605 const uint32_t fixedSize = offs.firstByte_EntityData + uniCompsSize + 1;
606 GAIA_ASSERT(dataLimit > fixedSize);
607
608 // Theoretical maximum number of generic component rows we can fit into one chunk.
609 // This can be further reduced due to alignment and padding.
610 const uint32_t itemSize = genCompsSize + (uint32_t)sizeof(Entity);
611 const uint32_t maxEntities = (dataLimit - fixedSize) / itemSize;
612 return compute_max_entities_for_chunk(maxEntities > 0 ? maxEntities : 1, dataLimit);
613 };
614
615 if (archetypeId == 0) {
616 // Keep the root archetype compact enough to avoid growing the maximum row snapshot used by iterators.
617 maxGenItemsInArchetype = compute_max_entities_for_size_type(2);
618 } else {
619 for (uint32_t sizeType = 0; sizeType < MemoryBlockSizeClasses; ++sizeType) {
620 maxGenItemsInArchetype = compute_max_entities_for_size_type(sizeType);
621 if (maxGenItemsInArchetype >= MinEntitiesPerChunk)
622 break;
623 }
624 }
625
626 // MAX_CHUNK_ENTITIES is intentionally based on the 32 KiB class to keep iterator snapshots bounded.
627 // Wide archetypes can still use the 64 KiB-class blocks because their row count stays below this cap.
628 if (maxGenItemsInArchetype > ChunkHeader::MAX_CHUNK_ENTITIES)
629 maxGenItemsInArchetype = ChunkHeader::MAX_CHUNK_ENTITIES;
630
631 // Update the offsets according to the recalculated maxGenItemsInArchetype
632 auto currOff = offs.firstByte_EntityData + ((uint32_t)sizeof(Entity) * maxGenItemsInArchetype);
633 reg_components(
634 *newArch, ids, newArch->m_shape.compItems, (uint8_t)0, (uint8_t)entsGeneric, currOff,
635 maxGenItemsInArchetype);
636 reg_components(
637 *newArch, ids, newArch->m_shape.compItems, (uint8_t)entsGeneric, (uint8_t)ids.size(), currOff, 1);
638
639 newArch->m_shape.properties.capacity = (uint16_t)maxGenItemsInArchetype;
640 newArch->m_shape.properties.chunkDataBytes = (ChunkDataOffset)currOff;
641 newArch->m_shape.properties.genEntities = (uint8_t)entsGeneric;
642
643 return newArch;
644 }
645
646 void static destroy(Archetype* pArchetype) {
647 GAIA_ASSERT(pArchetype != nullptr);
648 pArchetype->~Archetype();
649 mem::AllocHelper::free("Archetype", pArchetype);
650 }
651
652 QueryMask queryMask() const {
653 return m_shape.queryMask;
654 }
655
656 ArchetypeIdLookupKey::LookupHash id_hash() const {
657 return m_shape.archetypeIdHash;
658 }
659
662 void set_hashes(LookupHash hashLookup) {
663 m_shape.hashLookup = hashLookup;
664 }
665
671 void enable_entity(Chunk* pChunk, uint16_t row, bool enableEntity, EntityContainers& recs) {
672 pChunk->enable_entity(row, enableEntity, recs);
673 // m_disabledMask.set(pChunk->idx(), enableEntity ? true : pChunk->has_disabled_entities());
674 }
675
678 void del(Chunk* pChunk) {
679 // Make sure there are any chunks to delete
680 GAIA_ASSERT(!m_storage.chunks.empty());
681
682 const auto chunkIndex = pChunk->idx();
683
684 // Make sure the chunk is a part of the chunk array
685 GAIA_ASSERT(chunkIndex == core::get_index(m_storage.chunks, pChunk));
686
687 // Remove the chunk from the chunk array. We are swapping this chunk's entry
688 // with the last one in the array. Therefore, we first update the last item's
689 // index with the current chunk's index and then do the swapping.
690 m_storage.chunks.back()->set_idx(chunkIndex);
691 core::swap_erase(m_storage.chunks, chunkIndex);
692
693 // Delete the chunk now. Otherwise, if the chunk happened to be the last
694 // one we would end up overriding released memory.
695 Chunk::free(pChunk);
696 }
697
701 GAIA_NODISCARD Chunk* foc_free_chunk() {
702 const auto chunkCnt = m_storage.chunks.size();
703
704 if (chunkCnt > 0) {
705 for (uint32_t i = m_storage.firstFreeChunkIdx; i < m_storage.chunks.size(); ++i) {
706 auto* pChunk = m_storage.chunks[i];
707 GAIA_ASSERT(pChunk != nullptr);
708 const auto entityCnt = pChunk->size();
709 if (entityCnt < pChunk->capacity()) {
710 m_storage.firstFreeChunkIdx = i;
711 return pChunk;
712 }
713 }
714 }
715
716 // Make sure not too many chunks are allocated
717 GAIA_ASSERT(chunkCnt < UINT32_MAX);
718
719 // No free space found anywhere. Let's create a new chunk.
720 auto* pChunk = Chunk::create(
721 m_world, m_cc, chunkCnt, //
722 m_shape.properties.capacity, m_shape.properties.cntEntities, //
723 m_shape.properties.genEntities, m_shape.properties.chunkDataBytes, //
724 m_worldVersion, m_shape.dataOffsets, m_shape.ids, m_shape.compItems, m_shape.compOffs);
725
726 m_storage.firstFreeChunkIdx = m_storage.chunks.size();
727 m_storage.chunks.push_back(pChunk);
728 return pChunk;
729 }
730
735 // This is expected to be called only if there are any chunks
736 GAIA_ASSERT(!m_storage.chunks.empty());
737
738 auto* pChunk = m_storage.chunks[m_storage.firstFreeChunkIdx];
739 if (pChunk->size() >= pChunk->capacity())
740 ++m_storage.firstFreeChunkIdx;
741 }
742
746 void try_update_free_chunk_idx(Chunk& chunkThatRemovedEntity) {
747 // This is expected to be called only if there are any chunks
748 GAIA_ASSERT(!m_storage.chunks.empty());
749
750 if (chunkThatRemovedEntity.idx() == m_storage.firstFreeChunkIdx)
751 return;
752
753 if (chunkThatRemovedEntity.idx() < m_storage.firstFreeChunkIdx) {
754 m_storage.firstFreeChunkIdx = chunkThatRemovedEntity.idx();
755 return;
756 }
757
758 auto* pChunk = m_storage.chunks[m_storage.firstFreeChunkIdx];
759 if (pChunk->size() >= pChunk->capacity())
760 ++m_storage.firstFreeChunkIdx;
761 }
762
767 void remove_entity_raw(Chunk& chunk, uint16_t row, EntityContainers& recs) {
768 chunk.remove_entity(row, recs);
769 try_update_free_chunk_idx(chunk);
770 }
771
776 void remove_entity(Chunk& chunk, uint16_t row, EntityContainers& recs) {
777 remove_entity_raw(chunk, row, recs);
778 chunk.update_versions();
779 }
780
781 GAIA_NODISCARD const Properties& props() const {
782 return m_shape.properties;
783 }
784
785 GAIA_NODISCARD const cnt::darray<Chunk*>& chunks() const {
786 return m_storage.chunks;
787 }
788
789 GAIA_NODISCARD LookupHash lookup_hash() const {
790 return m_shape.hashLookup;
791 }
792
793 GAIA_NODISCARD EntitySpan ids_view() const {
794 return {&m_shape.ids[0], m_shape.properties.cntEntities};
795 }
796
797 GAIA_NODISCARD ChunkDataOffsetSpan comp_offs_view() const {
798 return {&m_shape.compOffs[0], m_shape.properties.cntEntities};
799 }
800
803 GAIA_NODISCARD uint32_t pairs() const {
804 return m_pPairIndex != nullptr ? m_pPairIndex->pairCnt : 0;
805 }
806
809 GAIA_NODISCARD uint32_t pairs_is() const {
810 return m_pPairIndex != nullptr ? m_pPairIndex->pairCntIs : 0;
811 }
812
815 GAIA_NODISCARD uint32_t pair_matches(Entity pair) const {
816 return m_pPairIndex != nullptr ? m_pPairIndex->pair_matches(ids_view(), pair) : 0;
817 }
818
819 GAIA_NODISCARD Entity entity_from_pairs_as_idx(uint32_t idx) const {
820 GAIA_ASSERT(m_pPairIndex != nullptr);
821 return m_pPairIndex->entity_from_pairs_as_idx(ids_view(), idx);
822 }
823
824 GAIA_NODISCARD std::span<const uint8_t> pair_indices() const {
825 return m_pPairIndex != nullptr ? m_pPairIndex->pair_indices() : std::span<const uint8_t>{};
826 }
827
828 GAIA_NODISCARD std::span<const uint8_t> pair_rel_indices(Entity relation) const {
829 return m_pPairIndex != nullptr ? m_pPairIndex->pair_rel_indices(relation) : std::span<const uint8_t>{};
830 }
831
832 GAIA_NODISCARD std::span<const uint8_t> pair_tgt_indices(Entity target) const {
833 return m_pPairIndex != nullptr ? m_pPairIndex->pair_tgt_indices(target) : std::span<const uint8_t>{};
834 }
835
839 GAIA_NODISCARD bool has(Entity entity) const {
840 return core::has_if(ids_view(), [&](Entity e) {
841 return e == entity;
842 });
843 }
844
845 void observed_terms_inc() {
846 GAIA_ASSERT(m_runtime.observedTermCnt < m_shape.properties.cntEntities);
847 ++m_runtime.observedTermCnt;
848 }
849
850 void observed_terms_dec() {
851 GAIA_ASSERT(m_runtime.observedTermCnt > 0);
852 --m_runtime.observedTermCnt;
853 }
854
855 GAIA_NODISCARD bool has_observed_terms() const {
856 return m_runtime.observedTermCnt != 0;
857 }
858
862 template <typename T>
863 GAIA_NODISCARD bool has() const {
864 if constexpr (is_pair<T>::value) {
865 const auto rel = m_cc.get<typename T::rel>().entity;
866 const auto tgt = m_cc.get<typename T::tgt>().entity;
867 return has((Entity)Pair(rel, tgt));
868 } else {
869 const auto* pComp = m_cc.find<T>();
870 return pComp != nullptr && has(pComp->entity);
871 }
872 }
873
874 //----------------------------------------------------------------------
875
881 template <bool Enabled>
882 Entity get_flat_entity(size_t flatIdx) const {
883 size_t offset = 0;
884 for (const auto* pChunk: chunks()) {
885 if (pChunk->empty())
886 continue;
887
888 uint32_t cnt = 0;
889 if constexpr (Enabled) {
890 cnt = pChunk->size_enabled();
891 } else {
892 cnt = pChunk->size_disabled();
893 }
894
895 if (flatIdx < offset + cnt) {
896 if constexpr (Enabled) {
897 const auto idx = (uint32_t)(flatIdx - offset) + pChunk->size_disabled();
898 return pChunk->entity_view()[idx];
899 } else {
900 const auto idx = (uint32_t)(flatIdx - offset);
901 return pChunk->entity_view()[idx];
902 }
903 }
904
905 offset += cnt;
906 }
907
908 GAIA_ASSERT(false);
909 return EntityBad;
910 }
911
919 template <bool Enabled>
920 const void* get_flat_comp_ptr(uint32_t compIdx, size_t flatIdx, Entity& outEntity) const {
921 size_t offset = 0;
922 for (const auto* pChunk: chunks()) {
923 if (pChunk->empty())
924 continue;
925
926 uint32_t cnt = 0;
927 if constexpr (Enabled) {
928 cnt = pChunk->size_enabled();
929 } else {
930 cnt = pChunk->size_disabled();
931 }
932
933 if (flatIdx < offset + cnt) {
934 if constexpr (Enabled) {
935 const auto idx = (uint32_t)(flatIdx - offset) + pChunk->size_disabled();
936 const auto* pData = pChunk->comp_ptr(compIdx, idx);
937 outEntity = pChunk->entity_view()[idx];
938 return pData;
939 } else {
940 const auto idx = (uint32_t)(flatIdx - offset);
941 const auto* pData = pChunk->comp_ptr(compIdx, idx);
942 outEntity = pChunk->entity_view()[idx];
943 return pData;
944 }
945 }
946
947 offset += cnt;
948 }
949
950 GAIA_ASSERT(false);
951 return nullptr;
952 }
953
955 template <bool Enabled>
956 void sort_entities_inter(size_t low, size_t high, TSortByFunc func) {
957 if (low >= high)
958 return;
959
960 Entity pivotEntity = get_flat_entity<Enabled>(high);
961
962 size_t i = low;
963 for (size_t j = low; j < high; ++j) {
964 Entity jEntity = get_flat_entity<Enabled>(j);
965 if (func(m_world, &jEntity, &pivotEntity) < 0) {
966 if (i != j) {
967 Entity iEntity = get_flat_entity<Enabled>(i);
968 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, jEntity);
969 }
970 ++i;
971 }
972 }
973
974 {
975 Entity iEntity = get_flat_entity<Enabled>(i);
976 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, pivotEntity);
977 }
978
979 if (i > 0)
980 sort_entities_inter<Enabled>(low, i - 1, func);
981 sort_entities_inter<Enabled>(i + 1, high, func);
982 }
983
985 template <bool Enabled>
987 const ComponentCacheItem* pItem, uint32_t compIdx, size_t low, size_t high, TSortByFunc func) {
988 if (low >= high)
989 return;
990
991 Entity pivotEntity;
992 const void* pPivotData = get_flat_comp_ptr<Enabled>(compIdx, high, pivotEntity);
993
994 size_t i = low;
995 for (size_t j = low; j < high; ++j) {
996 Entity jEntity;
997 const void* jData = get_flat_comp_ptr<Enabled>(compIdx, j, jEntity);
998 if (func(m_world, jData, pPivotData) < 0) {
999 if (i != j) {
1000 Entity iEntity;
1001 (void)get_flat_comp_ptr<Enabled>(compIdx, i, iEntity);
1002 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, jEntity);
1003 }
1004 ++i;
1005 }
1006 }
1007
1008 {
1009 Entity iEntity;
1010 (void)get_flat_comp_ptr<Enabled>(compIdx, i, iEntity);
1011 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, pivotEntity);
1012 }
1013
1014 if (i > 0)
1015 sort_entities_inter<Enabled>(pItem, compIdx, low, i - 1, func);
1016 sort_entities_inter<Enabled>(pItem, compIdx, i + 1, high, func);
1017 }
1018
1022 void sort_entities(Entity entity, TSortByFunc func) {
1023 // TODO: We currently have to calculate the number of entities in the archetype from chunks.
1024 // Additionally, to get the right index we need to loop through chunks again because
1025 // the entities are not spread evenly among chunks (we can't just divide the index by
1026 // the number of chunks and module with the same number to get the index inside a chunk).
1027 // This is not optimal, and makes sorting more expensive.
1028
1029 if (entity == EntityBad) {
1030 {
1031 uint32_t entities = 0;
1032 for (const auto* pChunk: m_storage.chunks)
1033 entities += pChunk->size_enabled();
1034 if (entities != 0)
1035 sort_entities_inter<true>(0, entities - 1, func);
1036 }
1037 {
1038 uint32_t entities = 0;
1039 for (const auto* pChunk: m_storage.chunks)
1040 entities += pChunk->size_disabled();
1041 if (entities != 0)
1042 sort_entities_inter<false>(0, entities - 1, func);
1043 }
1044 } else {
1045 const auto* pItem = m_cc.find(entity);
1046 GAIA_ASSERT(pItem != nullptr && "Trying to sort by a component that has not been registered");
1047 if (pItem == nullptr)
1048 return;
1049
1050 const auto compIdx = chunks()[0]->comp_idx(entity);
1051 {
1052 uint32_t entities = 0;
1053 for (const auto* pChunk: m_storage.chunks)
1054 entities += pChunk->size_enabled();
1055 if (entities != 0)
1056 sort_entities_inter<true>(pItem, compIdx, 0, entities - 1, func);
1057 }
1058 {
1059 uint32_t entities = 0;
1060 for (const auto* pChunk: m_storage.chunks)
1061 entities += pChunk->size_disabled();
1062 if (entities != 0)
1063 sort_entities_inter<false>(pItem, compIdx, 0, entities - 1, func);
1064 }
1065 }
1066 }
1067
1068 //----------------------------------------------------------------------
1069
1073 void build_graph_edges(Archetype* pArchetypeRight, Entity entity) {
1074 // Loops can't happen
1075 GAIA_ASSERT(pArchetypeRight != this);
1076
1077 m_edges.graph.add_edge_right(entity, pArchetypeRight->id(), pArchetypeRight->id_hash());
1078 pArchetypeRight->build_graph_edges_left(this, entity);
1079 }
1080
1081 void build_graph_edges_left(Archetype* pArchetypeLeft, Entity entity) {
1082 // Loops can't happen
1083 GAIA_ASSERT(pArchetypeLeft != this);
1084
1085 m_edges.graph.add_edge_left(entity, pArchetypeLeft->id(), pArchetypeLeft->id_hash());
1086 }
1087
1088 void del_graph_edges(Archetype* pArchetypeRight, Entity entity) {
1089 // Loops can't happen
1090 GAIA_ASSERT(pArchetypeRight != this);
1091
1092 m_edges.graph.del_edge_right(entity);
1093 pArchetypeRight->del_graph_edges_left(this, entity);
1094 }
1095
1096 void del_graph_edges_left([[maybe_unused]] Archetype* pArchetypeLeft, Entity entity) {
1097 // Loops can't happen
1098 GAIA_ASSERT(pArchetypeLeft != this);
1099
1100 m_edges.graph.del_edge_left(entity);
1101 }
1102
1107 m_edges.graph.del_edge_right(entity);
1108 }
1109
1114 m_edges.graph.del_edge_left(entity);
1115 }
1116
1120 GAIA_NODISCARD ArchetypeGraphEdge find_edge_right(Entity entity) const {
1121 return m_edges.graph.find_edge_right(entity);
1122 }
1123
1127 GAIA_NODISCARD ArchetypeGraphEdge find_edge_left(Entity entity) const {
1128 return m_edges.graph.find_edge_left(entity);
1129 }
1130
1131 GAIA_NODISCARD auto& right_edges() {
1132 return m_edges.graph.right_edges();
1133 }
1134
1135 GAIA_NODISCARD const auto& right_edges() const {
1136 return m_edges.graph.right_edges();
1137 }
1138
1139 GAIA_NODISCARD auto& left_edges() {
1140 return m_edges.graph.left_edges();
1141 }
1142
1143 GAIA_NODISCARD const auto& left_edges() const {
1144 return m_edges.graph.left_edges();
1145 }
1146
1148 GAIA_NODISCARD bool empty() const {
1149 return m_storage.chunks.empty();
1150 }
1151
1153 void req_del() {
1154 m_runtime.deleteReq = 1;
1155 }
1156
1158 GAIA_NODISCARD bool is_req_del() const {
1159 return m_runtime.deleteReq;
1160 }
1161
1165 void set_max_lifespan(uint32_t lifespan) {
1166 GAIA_ASSERT(lifespan <= MAX_ARCHETYPE_LIFESPAN);
1167
1168 m_runtime.lifespanCountdownMax = lifespan;
1169 }
1170
1173 GAIA_NODISCARD uint32_t max_lifespan() const {
1174 return m_runtime.lifespanCountdownMax;
1175 }
1176
1178 GAIA_NODISCARD bool dying() const {
1179 return m_runtime.lifespanCountdown > 0;
1180 }
1181
1183 void die() {
1184 m_runtime.dead = 1;
1185 }
1186
1188 GAIA_NODISCARD bool dead() const {
1189 return m_runtime.dead == 1;
1190 }
1191
1194 GAIA_ASSERT(!dead());
1195 m_runtime.lifespanCountdown = m_runtime.lifespanCountdownMax;
1196 }
1197
1199 void revive() {
1200 GAIA_ASSERT(!dead());
1201 m_runtime.lifespanCountdown = 0;
1202 m_runtime.deleteReq = 0;
1203 }
1204
1207 GAIA_NODISCARD bool progress_death() {
1208 GAIA_ASSERT(dying());
1209 GAIA_ASSERT(m_runtime.lifespanCountdownMax > 0);
1210 --m_runtime.lifespanCountdown;
1211 return dying();
1212 }
1213
1215 GAIA_NODISCARD bool ready_to_die() const {
1216 return m_runtime.lifespanCountdownMax > 0 && !dying() && empty();
1217 }
1218
1219 static void diag_entity(const World& world, Entity entity) {
1220 if (entity.entity()) {
1221 const auto name = entity_name(world, entity);
1222 GAIA_LOG_N(
1223 " ent [%u:%u] %.*s [%s]", entity.id(), entity.gen(), (int)name.size(), name.empty() ? "" : name.data(),
1224 EntityKindString[entity.kind()]);
1225 } else if (entity.pair()) {
1226 const auto rel = entity_name(world, entity.id());
1227 const auto tgt = entity_name(world, entity.gen());
1228 GAIA_LOG_N(
1229 " pair [%u:%u] %.*s -> %.*s", entity.id(), entity.gen(), (int)rel.size(),
1230 rel.empty() ? "" : rel.data(), (int)tgt.size(), tgt.empty() ? "" : tgt.data());
1231 } else {
1232 const auto& cc = comp_cache(world);
1233 const auto& desc = cc.get(entity);
1234 GAIA_LOG_N(
1235 " hash:%016" PRIx64 ", size:%3u B, align:%3u B, [%u:%u] %s [%s]", desc.hashLookup.hash,
1236 desc.comp.size(), desc.comp.alig(), desc.entity.id(), desc.entity.gen(), desc.name.str(),
1237 EntityKindString[entity.kind()]);
1238 }
1239 }
1240
1241 static void diag_basic_info(const World& world, const Archetype& archetype) {
1242 auto ids = archetype.ids_view();
1243
1244 // Calculate the number of entities in archetype
1245 uint32_t entCnt = 0;
1246 uint32_t entCntDisabled = 0;
1247 for (const auto* chunk: archetype.m_storage.chunks) {
1248 entCnt += chunk->size();
1249 entCntDisabled += chunk->size_disabled();
1250 }
1251
1252 // Calculate the number of components
1253 uint32_t genCompsSize = 0;
1254 uint32_t uniCompsSize = 0;
1255 {
1256 const auto& p = archetype.props();
1257 GAIA_FOR(p.genEntities) genCompsSize += comp_from_item(archetype.m_shape.compItems[i]).size();
1258 GAIA_FOR2(p.genEntities, p.cntEntities)
1259 uniCompsSize += comp_from_item(archetype.m_shape.compItems[i]).size();
1260 }
1261
1262 const auto chunkBytes = Chunk::chunk_total_bytes(archetype.props().chunkDataBytes);
1263 const auto sizeType = mem_block_size_type(chunkBytes);
1264 const auto allocSize = mem_block_size(sizeType) / 1024;
1265
1266 GAIA_LOG_N(
1267 "aid:%u, "
1268 "hash:%016" PRIx64 ", "
1269 "chunks:%u (%uK), data:%u/%u/%u B, "
1270 "entities:%u/%u/%u",
1271 archetype.id(), archetype.lookup_hash().hash, (uint32_t)archetype.chunks().size(), allocSize, genCompsSize,
1272 uniCompsSize, archetype.props().chunkDataBytes, entCnt, entCntDisabled, archetype.props().capacity);
1273
1274 if (!ids.empty()) {
1275 GAIA_LOG_N(" Components - count:%u", (uint32_t)ids.size());
1276 for (const auto ent: ids)
1277 diag_entity(world, ent);
1278 }
1279 }
1280
1281 static void diag_graph_info(const World& world, const Archetype& archetype) {
1282 archetype.m_edges.graph.diag(world);
1283 }
1284
1285 static void diag_chunk_info(const Archetype& archetype) {
1286 const auto& chunks = archetype.m_storage.chunks;
1287 if (chunks.empty())
1288 return;
1289
1290 GAIA_LOG_N(" Chunks");
1291 for (const auto* pChunk: chunks)
1292 pChunk->diag();
1293 }
1294
1295 static void diag_entity_info(const World& world, const Archetype& archetype) {
1296 const auto& chunks = archetype.m_storage.chunks;
1297 if (chunks.empty())
1298 return;
1299
1300 GAIA_LOG_N(" Entities");
1301 bool noEntities = true;
1302 for (const auto* pChunk: chunks) {
1303 if (pChunk->empty())
1304 continue;
1305 noEntities = false;
1306
1307 auto ev = pChunk->entity_view();
1308 for (auto entity: ev)
1309 diag_entity(world, entity);
1310 }
1311 if (noEntities)
1312 GAIA_LOG_N(" N/A");
1313 }
1314
1318 static void diag(const World& world, const Archetype& archetype) {
1319 diag_basic_info(world, archetype);
1320 diag_graph_info(world, archetype);
1321 diag_chunk_info(archetype);
1322 diag_entity_info(world, archetype);
1323 }
1324 };
1325
1326 class GAIA_API ArchetypeLookupKey final {
1327 Archetype::LookupHash m_hash;
1328 const ArchetypeBase* m_pArchetypeBase;
1329
1330 public:
1331 static constexpr bool IsDirectHashKey = true;
1332
1333 ArchetypeLookupKey(): m_hash({0}), m_pArchetypeBase(nullptr) {}
1334 explicit ArchetypeLookupKey(Archetype::LookupHash hash, const ArchetypeBase* pArchetypeBase):
1335 m_hash(hash), m_pArchetypeBase(pArchetypeBase) {}
1336
1337 GAIA_NODISCARD size_t hash() const {
1338 return (size_t)m_hash.hash;
1339 }
1340
1341 GAIA_NODISCARD Archetype* archetype() const {
1342 return (Archetype*)m_pArchetypeBase;
1343 }
1344
1345 GAIA_NODISCARD bool operator==(const ArchetypeLookupKey& other) const {
1346 // Hash doesn't match we don't have a match.
1347 // Hash collisions are expected to be very unlikely so optimize for this case.
1348 if GAIA_LIKELY (m_hash != other.m_hash)
1349 return false;
1350
1351 const auto id = m_pArchetypeBase->id();
1352 if (id == ArchetypeIdBad) {
1353 const auto* pArchetype = (const Archetype*)other.m_pArchetypeBase;
1354 const auto* pArchetypeLookupChecker = (const ArchetypeLookupChecker*)m_pArchetypeBase;
1355 return pArchetype->cmp_comps(*pArchetypeLookupChecker);
1356 }
1357
1358 // Real ArchetypeID is given. Compare the pointers.
1359 // Normally we'd compare archetype IDs but because we do not allow archetype copies and all archetypes are
1360 // unique it's guaranteed that if pointers are the same we have a match.
1361 // This also saves a pointer indirection because we do not access the memory the pointer points to.
1362 return m_pArchetypeBase == other.m_pArchetypeBase;
1363 }
1364 };
1365
1367 } // namespace ecs
1368} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
Definition span_impl.h:99
Definition archetype.h:58
ArchetypeId m_archetypeId
Archetype ID - used to address the archetype directly in the world's list or archetypes.
Definition archetype.h:61
Definition archetype.h:69
Definition archetype.h:1326
Definition archetype.h:83
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:882
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:701
void set_max_lifespan(uint32_t lifespan)
Sets maximal lifespan of an archetype.
Definition archetype.h:1165
void build_graph_edges(Archetype *pArchetypeRight, Entity entity)
Builds a graph edge from this archetype to the right archetype.
Definition archetype.h:1073
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:986
void set_hashes(LookupHash hashLookup)
Sets hashes for each component type and lookup.
Definition archetype.h:662
GAIA_NODISCARD uint32_t pairs() const
Returns the number of pairs registered in the archetype.
Definition archetype.h:803
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:1113
void remove_entity(Chunk &chunk, uint16_t row, EntityContainers &recs)
Removes an entity from the chunk and updates the chunk versions.
Definition archetype.h:776
void del(Chunk *pChunk)
Removes a chunk from the list of chunks managed by their archetype and deletes its memory.
Definition archetype.h:678
GAIA_NODISCARD uint32_t pairs_is() const
Returns the number of Is pairs registered in the archetype.
Definition archetype.h:809
void sort_entities_inter(size_t low, size_t high, TSortByFunc func)
Generic in-place quicksort across chunks.
Definition archetype.h:956
GAIA_NODISCARD bool ready_to_die() const
Tells whether archetype is ready to be deleted.
Definition archetype.h:1215
GAIA_NODISCARD bool dead() const
Checks is this chunk is dying.
Definition archetype.h:1188
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:920
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:671
void die()
Marks the chunk as dead.
Definition archetype.h:1183
GAIA_NODISCARD ArchetypeGraphEdge find_edge_left(Entity entity) const
Checks if an archetype graph "del" edge with entity entity exists.
Definition archetype.h:1127
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:1318
GAIA_NODISCARD bool progress_death()
Updates internal lifespan.
Definition archetype.h:1207
GAIA_NODISCARD bool empty() const
Checks is there are no chunk in the archetype.
Definition archetype.h:1148
GAIA_NODISCARD bool has(Entity entity) const
Checks if an entity is a part of the archetype.
Definition archetype.h:839
GAIA_NODISCARD bool is_req_del() const
Returns true if this archetype is requested to be deleted.
Definition archetype.h:1158
GAIA_NODISCARD bool dying() const
Checks is this chunk is dying.
Definition archetype.h:1178
GAIA_NODISCARD bool has() const
Checks if component.
Definition archetype.h:863
void revive()
Makes the archetype alive again.
Definition archetype.h:1199
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:734
GAIA_NODISCARD ArchetypeGraphEdge find_edge_right(Entity entity) const
Checks if an archetype graph "add" edge with entity entity exists.
Definition archetype.h:1120
void req_del()
Request deleting the archetype.
Definition archetype.h:1153
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:746
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:815
void remove_entity_raw(Chunk &chunk, uint16_t row, EntityContainers &recs)
Removes an entity from the chunk.
Definition archetype.h:767
void start_dying()
Starts the process of dying.
Definition archetype.h:1193
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:1106
GAIA_NODISCARD uint32_t max_lifespan() const
Returns the maximal lifespan of the archetype. If zero, the archetype it kept indefinitely.
Definition archetype.h:1173
void sort_entities(Entity entity, TSortByFunc func)
Sorts all entities in the archetypes according to the given function.
Definition archetype.h:1022
Definition chunk.h:35
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:1199
GAIA_NODISCARD uint32_t idx() const
Returns the index of this chunk in its archetype's storage.
Definition chunk.h:1715
void update_versions()
Updates the version numbers for this chunk.
Definition chunk.h:602
GAIA_NODISCARD uint16_t size() const
Returns the total number of entities in the chunk (both enabled and disabled)
Definition chunk.h:1799
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:1326
Definition world.h:174
Wrapper for two Entities forming a relationship pair.
Definition id.h:529
Wrapper for two types forming a relationship pair. Depending on what types are used to form a pair it...
Definition id.h:224
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
constexpr uint32_t BadIndex
Sentinel index value returned by helpers when a lookup fails.
Definition utility.h:20
Definition archetype.h:49
Definition archetype_common.h:19
Definition archetype.h:94
uint16_t capacity
The number of data entities this archetype can take (e.g 5 = 5 entities with all their components)
Definition archetype.h:96
uint8_t cntEntities
Total number of entities/components.
Definition archetype.h:102
ChunkDataOffset chunkDataBytes
How many bytes of data is needed for a fully utilized chunk.
Definition archetype.h:98
uint8_t genEntities
The number of generic entities/components.
Definition archetype.h:100
Definition component_cache_item.h:26
Definition id.h:35
Definition entity_container.h:142
Definition id.h:247
Definition id.h:239