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