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/ecs/api.h"
11#include "gaia/ecs/archetype_common.h"
12#include "gaia/ecs/archetype_graph.h"
13#include "gaia/ecs/chunk.h"
14#include "gaia/ecs/chunk_allocator.h"
15#include "gaia/ecs/chunk_header.h"
16#include "gaia/ecs/component.h"
17#include "gaia/ecs/component_cache.h"
18#include "gaia/ecs/id.h"
19#include "gaia/ecs/query_mask.h"
20#include "gaia/ecs/ser_binary.h"
21#include "gaia/mem/mem_alloc.h"
22
23namespace gaia {
24 namespace ecs {
25 class World;
26 class Archetype;
27 struct EntityContainer;
28
29 namespace detail {
30 GAIA_NODISCARD inline bool cmp_comps(EntitySpan comps, EntitySpan compsOther) {
31 const auto s0 = comps.size();
32 const auto s1 = compsOther.size();
33
34 // Size has to match
35 if (s0 != s1)
36 return false;
37
38 // Elements have to match
39 GAIA_FOR(s0) {
40 if (comps[i] != compsOther[i])
41 return false;
42 }
43
44 return true;
45 }
46 } // namespace detail
47
49 Archetype* pArchetype;
50 Chunk* pChunk;
51
52 GAIA_NODISCARD bool operator==(const ArchetypeChunkPair& other) const {
53 return pArchetype == other.pArchetype && pChunk == other.pChunk;
54 }
55 };
56
58 protected:
60 ArchetypeId m_archetypeId = ArchetypeIdBad;
61
62 public:
63 GAIA_NODISCARD ArchetypeId id() const {
64 return m_archetypeId;
65 }
66 };
67
69 friend class Archetype;
70
72 EntitySpan m_comps;
73
74 public:
75 ArchetypeLookupChecker(EntitySpan comps): m_comps(comps) {}
76
77 GAIA_NODISCARD bool cmp_comps(const ArchetypeLookupChecker& other) const {
78 return detail::cmp_comps(m_comps, other.m_comps);
79 }
80 };
81
82 class GAIA_API Archetype final: public ArchetypeBase {
83 public:
85
87 static constexpr uint16_t ARCHETYPE_LIFESPAN_BITS = 7;
89 static_assert(ARCHETYPE_LIFESPAN_BITS >= ChunkHeader::CHUNK_LIFESPAN_BITS);
91 static constexpr uint16_t MAX_ARCHETYPE_LIFESPAN = (1 << ARCHETYPE_LIFESPAN_BITS) - 1;
92
93 struct Properties {
95 uint16_t capacity;
97 ChunkDataOffset chunkDataBytes;
99 uint8_t genEntities;
101 uint8_t cntEntities;
102 };
103
104 private:
105 ArchetypeIdLookupKey::LookupHash m_archetypeIdHash;
107 LookupHash m_hashLookup = {0};
109 QueryMask m_queryMask{};
110
111 Properties m_properties{};
113 const World& m_world;
115 const ComponentCache& m_cc;
117 uint32_t& m_worldVersion;
118
120 cnt::darray<Chunk*> m_chunks;
122 // cnt::dbitset m_disabledMask;
124 ArchetypeGraph m_graph;
125
127 ChunkDataOffsets m_dataOffsets;
129 Entity m_ids[ChunkHeader::MAX_COMPONENTS];
131 uint8_t m_pairs_as_index_buffer[ChunkHeader::MAX_COMPONENTS];
133 Component m_comps[ChunkHeader::MAX_COMPONENTS];
135 ChunkDataOffset m_compOffs[ChunkHeader::MAX_COMPONENTS];
136
138 uint32_t m_firstFreeChunkIdx = 0;
140 uint32_t m_listIdx;
141
143 uint32_t m_deleteReq : 1;
145 uint32_t m_dead : 1;
147 uint32_t m_lifespanCountdownMax : ARCHETYPE_LIFESPAN_BITS;
149 uint32_t m_lifespanCountdown : ARCHETYPE_LIFESPAN_BITS;
151 uint32_t m_pairCnt : ChunkHeader::MAX_COMPONENTS_BITS;
153 uint32_t m_pairCnt_is : ChunkHeader::MAX_COMPONENTS_BITS;
155 // uint32_t m_unused : 6;
156
158 Archetype(const World& world, const ComponentCache& cc, uint32_t& worldVersion):
159 m_world(world), m_cc(cc), m_worldVersion(worldVersion), m_listIdx(BadIndex), //
160 m_deleteReq(0), m_dead(0), //
161 m_lifespanCountdownMax(1), m_lifespanCountdown(0), //
162 m_pairCnt(0), m_pairCnt_is(0) {}
163
164 ~Archetype() {
165 // Delete all archetype chunks
166 for (auto* pChunk: m_chunks)
167 Chunk::free(pChunk);
168 }
169
173 void update_data_offsets(uintptr_t memoryAddress) {
174 uintptr_t offset = 0;
175
176 // Versions
177 // We expect versions to fit in the first 256 bytes.
178 // With 32 components per archetype this gives us some headroom.
179 {
180 offset += mem::padding<alignof(ComponentVersion)>(memoryAddress);
181
182 const auto cnt = comps_view().size() + 1; // + 1 for entities
183 GAIA_ASSERT(offset < 256);
184 m_dataOffsets.firstByte_Versions = (ChunkDataVersionOffset)offset;
185 offset += sizeof(ComponentVersion) * cnt;
186 }
187
188 // Entity ids
189 {
190 offset += mem::padding<alignof(Entity)>(offset);
191
192 const auto cnt = comps_view().size();
193 if (cnt != 0) {
194 m_dataOffsets.firstByte_CompEntities = (ChunkDataOffset)offset;
195
196 // Storage-wise, treat the component array as it it were MAX_COMPONENTS long.
197 offset += sizeof(Entity) * ChunkHeader::MAX_COMPONENTS;
198 }
199 }
200
201 // Component records
202 {
203 offset += mem::padding<alignof(ComponentRecord)>(offset);
204
205 const auto cnt = comps_view().size();
206 if (cnt != 0) {
207
208 m_dataOffsets.firstByte_Records = (ChunkDataOffset)offset;
209
210 // Storage-wise, treat the component array as it it were MAX_COMPONENTS long.
211 offset += sizeof(ComponentRecord) * cnt;
212 }
213 }
214
215 // First entity offset
216 {
217 offset += mem::padding<alignof(Entity)>(offset);
218 m_dataOffsets.firstByte_EntityData = (ChunkDataOffset)offset;
219 }
220 }
221
223 static bool est_max_entities_per_chunk(
224 const ComponentCache& cc, uint32_t offs, ComponentSpan comps, uint32_t cap, uint32_t maxDataOffset) {
225 for (const auto comp: comps) {
226 if (comp.alig() == 0)
227 continue;
228
229 const auto& desc = cc.get(comp.id());
230
231 // If we're beyond what the chunk could take, subtract one entity
232 offs = desc.calc_new_mem_offset(offs, cap);
233 if (offs >= maxDataOffset)
234 return false;
235 }
236
237 return true;
238 }
239
240 static void reg_components(
241 Archetype& arch, EntitySpan ids, ComponentSpan comps, uint8_t from, uint8_t to, uint32_t& currOff,
242 uint32_t count) {
243 auto& ofs = arch.m_compOffs;
244
245 // Set component ids
246 GAIA_FOR2(from, to) arch.m_ids[i] = ids[i];
247
248 // Calculate offsets and assign them indices according to our mappings
249 GAIA_FOR2(from, to) {
250 const auto comp = comps[i];
251 const auto compIdx = i;
252
253 const auto alig = comp.alig();
254 if (alig == 0) {
255 ofs[compIdx] = {};
256 } else {
257 currOff = mem::align(currOff, alig);
258 ofs[compIdx] = (ChunkDataOffset)currOff;
259
260 // Make sure the following component list is properly aligned
261 currOff += comp.size() * count;
262 }
263 }
264 }
265
266 public:
267 Archetype(Archetype&&) = delete;
268 Archetype(const Archetype&) = delete;
269 Archetype& operator=(Archetype&&) = delete;
270 Archetype& operator=(const Archetype&) = delete;
271
272 void save(ser::ISerializer& s) {
273 s.save(m_firstFreeChunkIdx);
274 s.save(m_listIdx);
275
276 s.save((uint32_t)m_chunks.size());
277 for (auto* pChunk: m_chunks) {
278 s.save((uint32_t)pChunk->idx());
279 pChunk->save(s);
280 }
281 }
282
283 void load(ser::ISerializer& s) {
284 s.load(m_firstFreeChunkIdx);
285 s.load(m_listIdx);
286
287 uint32_t chunkCnt = 0;
288 s.load(chunkCnt);
289 {
290 const auto chunkCnt0 = (uint32_t)m_chunks.size();
291 m_chunks.resize(chunkCnt);
292 // Make sure new chunks are set to nullptr
293 GAIA_FOR2(chunkCnt0, chunkCnt) m_chunks[i] = nullptr;
294 }
295
296 GAIA_FOR(chunkCnt) {
297 uint32_t chunkIdx = 0;
298 s.load(chunkIdx);
299
300 auto* pChunk = m_chunks[chunkIdx];
301 // If the chunk doesn't exist it means it's not a part of the initial setup.
302 if (pChunk == nullptr) {
303 pChunk = Chunk::create(
304 m_world, m_cc, chunkIdx, //
305 m_properties.capacity, m_properties.cntEntities, //
306 m_properties.genEntities, m_properties.chunkDataBytes, //
307 m_worldVersion, m_dataOffsets, m_ids, m_comps, m_compOffs);
308 m_chunks[chunkIdx] = pChunk;
309 }
310
311 pChunk->set_idx(chunkIdx);
312 pChunk->load(s);
313 }
314 }
315
316 void list_idx(uint32_t idx) {
317 m_listIdx = idx;
318 }
319
320 uint32_t list_idx() const {
321 return m_listIdx;
322 }
323
324 GAIA_NODISCARD bool cmp_comps(const ArchetypeLookupChecker& other) const {
325 return detail::cmp_comps(ids_view(), other.m_comps);
326 }
327
328 GAIA_NODISCARD static Archetype*
329 create(const World& world, ArchetypeId archetypeId, uint32_t& worldVersion, EntitySpan ids) {
330 const auto& cc = comp_cache(world);
331
332 auto* newArch = mem::AllocHelper::alloc<Archetype>("Archetype");
333 (void)new (newArch) Archetype(world, cc, worldVersion);
334
335 newArch->m_archetypeId = archetypeId;
336 newArch->m_archetypeIdHash = ArchetypeIdLookupKey::calc(archetypeId);
337
338 // Calculate component mask. This will be used to early exit matching archetypes in simple queries.
339 // TODO: Performance could be improved if we're an archetype comming from one already known.
340 // We could simply take the predecessor's mask and update it with the new ids.
341 newArch->m_queryMask = build_entity_mask({ids.data(), ids.size()});
342
343 const auto cnt = (uint32_t)ids.size();
344 newArch->m_properties.cntEntities = (uint8_t)ids.size();
345
346 auto as_comp = [&](Entity entity) {
347 const auto* pDesc = cc.find(entity);
348 return pDesc == nullptr //
349 ? Component(IdentifierIdBad, 0, 0, 0) //
350 : pDesc->comp;
351 };
352
353 // Prepare m_comps array
354 auto comps = std::span(&newArch->m_comps[0], cnt);
355 GAIA_FOR(cnt) {
356 if (ids[i].pair()) {
357 // When using pairs we need to decode the storage type from them.
358 // This is what pair<Rel, Tgt>::type actually does to determine what type to use at compile-time.
359 Entity pairEntities[] = {entity_from_id(world, ids[i].id()), entity_from_id(world, ids[i].gen())};
360 Component pairComponents[] = {as_comp(pairEntities[0]), as_comp(pairEntities[1])};
361 const uint32_t idx = (pairComponents[0].size() != 0U || pairComponents[1].size() == 0U) ? 0 : 1;
362 comps[i] = pairComponents[idx];
363 } else {
364 comps[i] = as_comp(ids[i]);
365 }
366 }
367
368 // Calculate offsets
369 static auto ChunkDataAreaOffset = Chunk::chunk_data_area_offset();
370 newArch->update_data_offsets(
371 // This is not a real memory address.
372 // Chunk memory is organized as header+data. The offsets we calculate here belong to
373 // the data area.
374 // Every allocated chunk is going to have the same relative offset from the header part
375 // which is why providing a fictional relative offset is enough.
376 ChunkDataAreaOffset);
377 const auto& offs = newArch->m_dataOffsets;
378
379 // Calculate the number of pairs
380 GAIA_FOR(cnt) {
381 if (!ids[i].pair())
382 continue;
383
384 ++newArch->m_pairCnt;
385
386 // If it is an Is relationship, count it separately
387 if (ids[i].id() == Is.id())
388 newArch->m_pairs_as_index_buffer[newArch->m_pairCnt_is++] = (uint8_t)i;
389 }
390
391 // Find the index of the last generic component in both arrays
392 const auto entsCnt = (uint32_t)ids.size();
393 uint32_t entsGeneric = entsCnt;
394 if (entsCnt > 0) {
395 for (auto i = entsCnt - 1; i != (uint32_t)-1; --i) {
396 if (ids[i].kind() != EntityKind::EK_Uni)
397 break;
398 --entsGeneric;
399 }
400 }
401
402 uint32_t genCompsSize = 0;
403 uint32_t uniCompsSize = 0;
404 GAIA_FOR(entsGeneric) genCompsSize += newArch->m_comps[i].size();
405 GAIA_FOR2(entsGeneric, cnt) uniCompsSize += newArch->m_comps[i].size();
406
407 auto compute_max_entities_for_chunk = [&](uint32_t maxEntities, uint32_t dataLimit) -> uint32_t {
408 uint32_t low = 1;
409 uint32_t high = maxEntities;
410 uint32_t best = 1;
411
412 // Helper to test if a given entity count fits in the chunk
413 auto try_fit = [&](uint32_t count) -> bool {
414 const uint32_t currOff = offs.firstByte_EntityData + (count * sizeof(Entity));
415
416 if (!est_max_entities_per_chunk(cc, currOff, comps.subspan(0, entsGeneric), count, dataLimit))
417 return false;
418 if (!est_max_entities_per_chunk(cc, currOff, comps.subspan(entsGeneric), 1, dataLimit))
419 return false;
420
421 return true;
422 };
423
424 // Binary search for the lookup
425 while (low <= high) {
426 uint32_t mid = (low + high) / 2;
427 if (try_fit(mid)) {
428 best = mid;
429 low = mid + 1;
430 } else {
431 high = mid - 1;
432 }
433 }
434
435 return best;
436 };
437
438 // Calculate the number of entities per chunks precisely so we can fit as many of them into chunk as possible.
439 // There are multiple chunk size we can pick from. We start at the smallest one, and try do upsize if we can't
440 // fit at least MinEntitiesPerChunk.
441 constexpr uint32_t MinEntitiesPerChunk = 384;
442 uint32_t maxGenItemsInArchetype = 0;
443
444 // Always go big for the root archetype so we can fit as many entities as possible into it
445 if (archetypeId == 0) {
446 const uint32_t size2 = Chunk::chunk_data_bytes(mem_block_size(2));
447 maxGenItemsInArchetype =
448 (size2 - offs.firstByte_EntityData - uniCompsSize - 1) / (genCompsSize + (uint32_t)sizeof(Entity));
449 maxGenItemsInArchetype = compute_max_entities_for_chunk(maxGenItemsInArchetype, size2);
450 if (maxGenItemsInArchetype > ChunkHeader::MAX_CHUNK_ENTITIES)
451 maxGenItemsInArchetype = ChunkHeader::MAX_CHUNK_ENTITIES;
452 } else {
453 // Theoretical maximum number of components we can fit into one chunk.
454 // This can be further reduced due to alignment and padding.
455 const uint32_t size0 = Chunk::chunk_data_bytes(mem_block_size(0));
456 maxGenItemsInArchetype =
457 (size0 - offs.firstByte_EntityData - uniCompsSize - 1) / (genCompsSize + (uint32_t)sizeof(Entity));
458 maxGenItemsInArchetype = compute_max_entities_for_chunk(maxGenItemsInArchetype, size0);
459
460 // If we can't fit MinEntitiesPerChunk, go with a larger one
461 if (maxGenItemsInArchetype < MinEntitiesPerChunk) {
462 const uint32_t size1 = Chunk::chunk_data_bytes(mem_block_size(1));
463 maxGenItemsInArchetype =
464 (size1 - offs.firstByte_EntityData - uniCompsSize - 1) / (genCompsSize + (uint32_t)sizeof(Entity));
465 maxGenItemsInArchetype = compute_max_entities_for_chunk(maxGenItemsInArchetype, size1);
466 }
467
468 // If we still can't fit MinEntitiesPerChunk, go with the largest one
469 if (maxGenItemsInArchetype < MinEntitiesPerChunk) {
470 const uint32_t size2 = Chunk::chunk_data_bytes(mem_block_size(2));
471 maxGenItemsInArchetype =
472 (size2 - offs.firstByte_EntityData - uniCompsSize - 1) / (genCompsSize + (uint32_t)sizeof(Entity));
473 maxGenItemsInArchetype = compute_max_entities_for_chunk(maxGenItemsInArchetype, size2);
474
475 // NOTE:
476 // No we only check against MAX_CHUNK_ENTITIES for the largest size chunk because MAX_CHUNK_ENTITIES is
477 // calculated relative to its size. Therefore, smaller chunks can't possibly fit more.
478 if (maxGenItemsInArchetype > ChunkHeader::MAX_CHUNK_ENTITIES)
479 maxGenItemsInArchetype = ChunkHeader::MAX_CHUNK_ENTITIES;
480 }
481 }
482
483 // Update the offsets according to the recalculated maxGenItemsInArchetype
484 auto currOff = offs.firstByte_EntityData + ((uint32_t)sizeof(Entity) * maxGenItemsInArchetype);
485 reg_components(*newArch, ids, comps, (uint8_t)0, (uint8_t)entsGeneric, currOff, maxGenItemsInArchetype);
486 reg_components(*newArch, ids, comps, (uint8_t)entsGeneric, (uint8_t)ids.size(), currOff, 1);
487
488 newArch->m_properties.capacity = (uint16_t)maxGenItemsInArchetype;
489 newArch->m_properties.chunkDataBytes = (ChunkDataOffset)currOff;
490 newArch->m_properties.genEntities = (uint8_t)entsGeneric;
491
492 return newArch;
493 }
494
495 void static destroy(Archetype* pArchetype) {
496 GAIA_ASSERT(pArchetype != nullptr);
497 pArchetype->~Archetype();
498 mem::AllocHelper::free("Archetype", pArchetype);
499 }
500
501 QueryMask queryMask() const {
502 return m_queryMask;
503 }
504
505 ArchetypeIdLookupKey::LookupHash id_hash() const {
506 return m_archetypeIdHash;
507 }
508
511 void set_hashes(LookupHash hashLookup) {
512 m_hashLookup = hashLookup;
513 }
514
520 void enable_entity(Chunk* pChunk, uint16_t row, bool enableEntity, EntityContainers& recs) {
521 pChunk->enable_entity(row, enableEntity, recs);
522 // m_disabledMask.set(pChunk->idx(), enableEntity ? true : pChunk->has_disabled_entities());
523 }
524
527 void del(Chunk* pChunk) {
528 // Make sure there are any chunks to delete
529 GAIA_ASSERT(!m_chunks.empty());
530
531 const auto chunkIndex = pChunk->idx();
532
533 // Make sure the chunk is a part of the chunk array
534 GAIA_ASSERT(chunkIndex == core::get_index(m_chunks, pChunk));
535
536 // Remove the chunk from the chunk array. We are swapping this chunk's entry
537 // with the last one in the array. Therefore, we first update the last item's
538 // index with the current chunk's index and then do the swapping.
539 m_chunks.back()->set_idx(chunkIndex);
540 core::swap_erase(m_chunks, chunkIndex);
541
542 // Delete the chunk now. Otherwise, if the chunk happened to be the last
543 // one we would end up overriding released memory.
544 Chunk::free(pChunk);
545 }
546
550 GAIA_NODISCARD Chunk* foc_free_chunk() {
551 const auto chunkCnt = m_chunks.size();
552
553 if (chunkCnt > 0) {
554 for (uint32_t i = m_firstFreeChunkIdx; i < m_chunks.size(); ++i) {
555 auto* pChunk = m_chunks[i];
556 GAIA_ASSERT(pChunk != nullptr);
557 const auto entityCnt = pChunk->size();
558 if (entityCnt < pChunk->capacity()) {
559 m_firstFreeChunkIdx = i;
560 return pChunk;
561 }
562 }
563 }
564
565 // Make sure not too many chunks are allocated
566 GAIA_ASSERT(chunkCnt < UINT32_MAX);
567
568 // No free space found anywhere. Let's create a new chunk.
569 auto* pChunk = Chunk::create(
570 m_world, m_cc, chunkCnt, //
571 m_properties.capacity, m_properties.cntEntities, //
572 m_properties.genEntities, m_properties.chunkDataBytes, //
573 m_worldVersion, m_dataOffsets, m_ids, m_comps, m_compOffs);
574
575 m_firstFreeChunkIdx = m_chunks.size();
576 m_chunks.push_back(pChunk);
577 return pChunk;
578 }
579
584 // This is expected to be called only if there are any chunks
585 GAIA_ASSERT(!m_chunks.empty());
586
587 auto* pChunk = m_chunks[m_firstFreeChunkIdx];
588 if (pChunk->size() >= pChunk->capacity())
589 ++m_firstFreeChunkIdx;
590 }
591
595 void try_update_free_chunk_idx(Chunk& chunkThatRemovedEntity) {
596 // This is expected to be called only if there are any chunks
597 GAIA_ASSERT(!m_chunks.empty());
598
599 if (chunkThatRemovedEntity.idx() == m_firstFreeChunkIdx)
600 return;
601
602 if (chunkThatRemovedEntity.idx() < m_firstFreeChunkIdx) {
603 m_firstFreeChunkIdx = chunkThatRemovedEntity.idx();
604 return;
605 }
606
607 auto* pChunk = m_chunks[m_firstFreeChunkIdx];
608 if (pChunk->size() >= pChunk->capacity())
609 ++m_firstFreeChunkIdx;
610 }
611
616 void remove_entity_raw(Chunk& chunk, uint16_t row, EntityContainers& recs) {
617 chunk.remove_entity(row, recs);
618 try_update_free_chunk_idx(chunk);
619 }
620
625 void remove_entity(Chunk& chunk, uint16_t row, EntityContainers& recs) {
626 remove_entity_raw(chunk, row, recs);
627 chunk.update_versions();
628 }
629
630 GAIA_NODISCARD const Properties& props() const {
631 return m_properties;
632 }
633
634 GAIA_NODISCARD const cnt::darray<Chunk*>& chunks() const {
635 return m_chunks;
636 }
637
638 GAIA_NODISCARD LookupHash lookup_hash() const {
639 return m_hashLookup;
640 }
641
642 GAIA_NODISCARD EntitySpan ids_view() const {
643 return {&m_ids[0], m_properties.cntEntities};
644 }
645
646 GAIA_NODISCARD ComponentSpan comps_view() const {
647 return {&m_comps[0], m_properties.cntEntities};
648 }
649
650 GAIA_NODISCARD ChunkDataOffsetSpan comp_offs_view() const {
651 return {&m_compOffs[0], m_properties.cntEntities};
652 }
653
656 GAIA_NODISCARD uint32_t pairs() const {
657 return m_pairCnt;
658 }
659
662 GAIA_NODISCARD uint32_t pairs_is() const {
663 return m_pairCnt_is;
664 }
665
666 GAIA_NODISCARD Entity entity_from_pairs_as_idx(uint32_t idx) const {
667 const auto ids_idx = m_pairs_as_index_buffer[idx];
668 return m_ids[ids_idx];
669 }
670
674 GAIA_NODISCARD bool has(Entity entity) const {
675 return core::has_if(ids_view(), [&](Entity e) {
676 return e == entity;
677 });
678 }
679
683 template <typename T>
684 GAIA_NODISCARD bool has() const {
685 if constexpr (is_pair<T>::value) {
686 const auto rel = m_cc.get<typename T::rel>().entity;
687 const auto tgt = m_cc.get<typename T::tgt>().entity;
688 return has((Entity)Pair(rel, tgt));
689 } else {
690 const auto* pComp = m_cc.find<T>();
691 return pComp != nullptr && has(pComp->entity);
692 }
693 }
694
695 //----------------------------------------------------------------------
696
698 template <bool Enabled>
699 Entity getValue(size_t flatIndex) const {
700 size_t offset = 0;
701 for (Chunk* pChunk: chunks()) {
702 if (pChunk->empty())
703 continue;
704
705 uint32_t cnt = 0;
706 if constexpr (Enabled) {
707 cnt = pChunk->size_enabled();
708 } else {
709 cnt = pChunk->size_disabled();
710 }
711
712 if (flatIndex < offset + cnt) {
713 if constexpr (Enabled) {
714 const auto idx = (uint32_t)(flatIndex - offset) + pChunk->size_disabled();
715 return pChunk->entity_view()[idx];
716 } else {
717 const auto idx = (uint32_t)(flatIndex - offset);
718 return pChunk->entity_view()[idx];
719 }
720 }
721
722 offset += cnt;
723 }
724
725 GAIA_ASSERT(false);
726 return EntityBad;
727 }
728
730 template <bool Enabled>
731 const void* getValue(uint32_t compIdx, size_t flatIndex, Entity& outEntity) const {
732 size_t offset = 0;
733 for (Chunk* pChunk: chunks()) {
734 if (pChunk->empty())
735 continue;
736
737 uint32_t cnt = 0;
738 if constexpr (Enabled) {
739 cnt = pChunk->size_enabled();
740 } else {
741 cnt = pChunk->size_disabled();
742 }
743
744 if (flatIndex < offset + cnt) {
745 if constexpr (Enabled) {
746 const auto idx = (uint32_t)(flatIndex - offset) + pChunk->size_disabled();
747 const auto* pData = pChunk->comp_ptr_mut(compIdx, idx);
748 outEntity = pChunk->entity_view()[idx];
749 return pData;
750 } else {
751 const auto idx = (uint32_t)(flatIndex - offset);
752 const auto* pData = pChunk->comp_ptr_mut(compIdx, idx);
753 outEntity = pChunk->entity_view()[idx];
754 return pData;
755 }
756 }
757
758 offset += cnt;
759 }
760
761 GAIA_ASSERT(false);
762 return nullptr;
763 }
764
766 template <bool Enabled>
767 void sort_entities_inter(size_t low, size_t high, TSortByFunc func) {
768 if (low >= high)
769 return;
770
771 Entity pivotEntity = getValue<Enabled>(high);
772
773 size_t i = low;
774 for (size_t j = low; j < high; ++j) {
775 Entity jEntity = getValue<Enabled>(j);
776 if (func(m_world, &jEntity, &pivotEntity) < 0) {
777 if (i != j) {
778 Entity iEntity = getValue<Enabled>(i);
779 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, jEntity);
780 }
781 ++i;
782 }
783 }
784
785 {
786 Entity iEntity = getValue<Enabled>(i);
787 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, pivotEntity);
788 }
789
790 if (i > 0)
791 sort_entities_inter<Enabled>(low, i - 1, func);
792 sort_entities_inter<Enabled>(i + 1, high, func);
793 }
794
796 template <bool Enabled>
798 const ComponentCacheItem* pItem, uint32_t compIdx, size_t low, size_t high, TSortByFunc func) {
799 if (low >= high)
800 return;
801
802 Entity pivotEntity;
803 const void* pPivotData = getValue<Enabled>(compIdx, high, pivotEntity);
804
805 size_t i = low;
806 for (size_t j = low; j < high; ++j) {
807 Entity jEntity;
808 const void* jData = getValue<Enabled>(compIdx, j, jEntity);
809 if (func(m_world, jData, pPivotData) < 0) {
810 if (i != j) {
811 Entity iEntity;
812 (void)getValue<Enabled>(compIdx, i, iEntity);
813 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, jEntity);
814 }
815 ++i;
816 }
817 }
818
819 {
820 Entity iEntity;
821 (void)getValue<Enabled>(compIdx, i, iEntity);
822 Chunk::swap_chunk_entities(const_cast<World&>(m_world), iEntity, pivotEntity);
823 }
824
825 if (i > 0)
826 sort_entities_inter<Enabled>(pItem, compIdx, low, i - 1, func);
827 sort_entities_inter<Enabled>(pItem, compIdx, i + 1, high, func);
828 }
829
833 void sort_entities(Entity entity, TSortByFunc func) {
834 // TODO: We currently have to calculate the number of entities in the archetype from chunks.
835 // Additionally, to get the right index we need to loop through chunks again because
836 // the entities are not spread evenly among chunks (we can't just divide the index by
837 // the number of chunks and module with the same number to get the index inside a chunk).
838 // This is not optimal, and makes sorting quite expensive.
839
840 if (entity == EntityBad) {
841 {
842 uint32_t entities = 0;
843 for (const auto* pChunk: m_chunks)
844 entities += pChunk->size_enabled();
845 if (entities != 0)
846 sort_entities_inter<true>(0, entities - 1, func);
847 }
848 {
849 uint32_t entities = 0;
850 for (const auto* pChunk: m_chunks)
851 entities += pChunk->size_disabled();
852 if (entities != 0)
853 sort_entities_inter<false>(0, entities - 1, func);
854 }
855 } else {
856 const auto* pItem = m_cc.find(entity);
857 GAIA_ASSERT(pItem != nullptr && "Trying to sort by a component that has not been registered");
858 if (pItem == nullptr)
859 return;
860
861 const auto compIdx = chunks()[0]->comp_idx(entity);
862 {
863 uint32_t entities = 0;
864 for (const auto* pChunk: m_chunks)
865 entities += pChunk->size_enabled();
866 if (entities != 0)
867 sort_entities_inter<true>(pItem, compIdx, 0, entities - 1, func);
868 }
869 {
870 uint32_t entities = 0;
871 for (const auto* pChunk: m_chunks)
872 entities += pChunk->size_disabled();
873 if (entities != 0)
874 sort_entities_inter<false>(pItem, compIdx, 0, entities - 1, func);
875 }
876 }
877 }
878
879 //----------------------------------------------------------------------
880
884 void build_graph_edges(Archetype* pArchetypeRight, Entity entity) {
885 // Loops can't happen
886 GAIA_ASSERT(pArchetypeRight != this);
887
888 m_graph.add_edge_right(entity, pArchetypeRight->id(), pArchetypeRight->id_hash());
889 pArchetypeRight->build_graph_edges_left(this, entity);
890 }
891
892 void build_graph_edges_left(Archetype* pArchetypeLeft, Entity entity) {
893 // Loops can't happen
894 GAIA_ASSERT(pArchetypeLeft != this);
895
896 m_graph.add_edge_left(entity, pArchetypeLeft->id(), pArchetypeLeft->id_hash());
897 }
898
899 void del_graph_edges(Archetype* pArchetypeRight, Entity entity) {
900 // Loops can't happen
901 GAIA_ASSERT(pArchetypeRight != this);
902
903 m_graph.del_edge_right(entity);
904 pArchetypeRight->del_graph_edges_left(this, entity);
905 }
906
907 void del_graph_edges_left([[maybe_unused]] Archetype* pArchetypeLeft, Entity entity) {
908 // Loops can't happen
909 GAIA_ASSERT(pArchetypeLeft != this);
910
911 m_graph.del_edge_left(entity);
912 }
913
916 GAIA_NODISCARD ArchetypeGraphEdge find_edge_right(Entity entity) const {
917 return m_graph.find_edge_right(entity);
918 }
919
922 GAIA_NODISCARD ArchetypeGraphEdge find_edge_left(Entity entity) const {
923 return m_graph.find_edge_left(entity);
924 }
925
926 GAIA_NODISCARD auto& right_edges() {
927 return m_graph.right_edges();
928 }
929
930 GAIA_NODISCARD const auto& right_edges() const {
931 return m_graph.right_edges();
932 }
933
934 GAIA_NODISCARD auto& left_edges() {
935 return m_graph.left_edges();
936 }
937
938 GAIA_NODISCARD const auto& left_edges() const {
939 return m_graph.left_edges();
940 }
941
943 GAIA_NODISCARD bool empty() const {
944 return m_chunks.empty();
945 }
946
948 void req_del() {
949 m_deleteReq = 1;
950 }
951
953 GAIA_NODISCARD bool is_req_del() const {
954 return m_deleteReq;
955 }
956
960 void set_max_lifespan(uint32_t lifespan) {
961 GAIA_ASSERT(lifespan <= MAX_ARCHETYPE_LIFESPAN);
962
963 m_lifespanCountdownMax = lifespan;
964 }
965
968 GAIA_NODISCARD uint32_t max_lifespan() const {
969 return m_lifespanCountdownMax;
970 }
971
973 GAIA_NODISCARD bool dying() const {
974 return m_lifespanCountdown > 0;
975 }
976
978 void die() {
979 m_dead = 1;
980 }
981
983 GAIA_NODISCARD bool dead() const {
984 return m_dead == 1;
985 }
986
988 void start_dying() {
989 GAIA_ASSERT(!dead());
990 m_lifespanCountdown = m_lifespanCountdownMax;
991 }
992
994 void revive() {
995 GAIA_ASSERT(!dead());
996 m_lifespanCountdown = 0;
997 m_deleteReq = 0;
998 }
999
1002 GAIA_NODISCARD bool progress_death() {
1003 GAIA_ASSERT(dying());
1004 GAIA_ASSERT(m_lifespanCountdownMax > 0);
1005 --m_lifespanCountdown;
1006 return dying();
1007 }
1008
1010 GAIA_NODISCARD bool ready_to_die() const {
1011 return m_lifespanCountdownMax > 0 && !dying() && empty();
1012 }
1013
1014 static void diag_entity(const World& world, Entity entity) {
1015 if (entity.entity()) {
1016 GAIA_LOG_N(
1017 " ent [%u:%u] %s [%s]", entity.id(), entity.gen(), entity_name(world, entity),
1018 EntityKindString[entity.kind()]);
1019 } else if (entity.pair()) {
1020 GAIA_LOG_N(
1021 " pair [%u:%u] %s -> %s", entity.id(), entity.gen(), entity_name(world, entity.id()),
1022 entity_name(world, entity.gen()));
1023 } else {
1024 const auto& cc = comp_cache(world);
1025 const auto& desc = cc.get(entity);
1026 GAIA_LOG_N(
1027 " hash:%016" PRIx64 ", size:%3u B, align:%3u B, [%u:%u] %s [%s]", desc.hashLookup.hash,
1028 desc.comp.size(), desc.comp.alig(), desc.entity.id(), desc.entity.gen(), desc.name.str(),
1029 EntityKindString[entity.kind()]);
1030 }
1031 }
1032
1033 static void diag_basic_info(const World& world, const Archetype& archetype) {
1034 auto ids = archetype.ids_view();
1035 auto comps = archetype.comps_view();
1036
1037 // Calculate the number of entities in archetype
1038 uint32_t entCnt = 0;
1039 uint32_t entCntDisabled = 0;
1040 for (const auto* chunk: archetype.m_chunks) {
1041 entCnt += chunk->size();
1042 entCntDisabled += chunk->size_disabled();
1043 }
1044
1045 // Calculate the number of components
1046 uint32_t genCompsSize = 0;
1047 uint32_t uniCompsSize = 0;
1048 {
1049 const auto& p = archetype.props();
1050 GAIA_FOR(p.genEntities) genCompsSize += comps[i].size();
1051 GAIA_FOR2(p.genEntities, comps.size()) uniCompsSize += comps[i].size();
1052 }
1053
1054 const auto chunkBytes = Chunk::chunk_total_bytes(archetype.props().chunkDataBytes);
1055 const auto sizeType = mem_block_size_type(chunkBytes);
1056 const auto allocSize = mem_block_size(sizeType) / 1024;
1057
1058 GAIA_LOG_N(
1059 "aid:%u, "
1060 "hash:%016" PRIx64 ", "
1061 "chunks:%u (%uK), data:%u/%u/%u B, "
1062 "entities:%u/%u/%u",
1063 archetype.id(), archetype.lookup_hash().hash, (uint32_t)archetype.chunks().size(), allocSize, genCompsSize,
1064 uniCompsSize, archetype.props().chunkDataBytes, entCnt, entCntDisabled, archetype.props().capacity);
1065
1066 if (!ids.empty()) {
1067 GAIA_LOG_N(" Components - count:%u", (uint32_t)ids.size());
1068 for (const auto ent: ids)
1069 diag_entity(world, ent);
1070 }
1071 }
1072
1073 static void diag_graph_info(const World& world, const Archetype& archetype) {
1074 archetype.m_graph.diag(world);
1075 }
1076
1077 static void diag_chunk_info(const Archetype& archetype) {
1078 const auto& chunks = archetype.m_chunks;
1079 if (chunks.empty())
1080 return;
1081
1082 GAIA_LOG_N(" Chunks");
1083 for (const auto* pChunk: chunks)
1084 pChunk->diag();
1085 }
1086
1087 static void diag_entity_info(const World& world, const Archetype& archetype) {
1088 const auto& chunks = archetype.m_chunks;
1089 if (chunks.empty())
1090 return;
1091
1092 GAIA_LOG_N(" Entities");
1093 bool noEntities = true;
1094 for (const auto* pChunk: chunks) {
1095 if (pChunk->empty())
1096 continue;
1097 noEntities = false;
1098
1099 auto ev = pChunk->entity_view();
1100 for (auto entity: ev)
1101 diag_entity(world, entity);
1102 }
1103 if (noEntities)
1104 GAIA_LOG_N(" N/A");
1105 }
1106
1110 static void diag(const World& world, const Archetype& archetype) {
1111 diag_basic_info(world, archetype);
1112 diag_graph_info(world, archetype);
1113 diag_chunk_info(archetype);
1114 diag_entity_info(world, archetype);
1115 }
1116 };
1117
1118 class GAIA_API ArchetypeLookupKey final {
1119 Archetype::LookupHash m_hash;
1120 const ArchetypeBase* m_pArchetypeBase;
1121
1122 public:
1123 static constexpr bool IsDirectHashKey = true;
1124
1125 ArchetypeLookupKey(): m_hash({0}), m_pArchetypeBase(nullptr) {}
1126 explicit ArchetypeLookupKey(Archetype::LookupHash hash, const ArchetypeBase* pArchetypeBase):
1127 m_hash(hash), m_pArchetypeBase(pArchetypeBase) {}
1128
1129 GAIA_NODISCARD size_t hash() const {
1130 return (size_t)m_hash.hash;
1131 }
1132
1133 GAIA_NODISCARD Archetype* archetype() const {
1134 return (Archetype*)m_pArchetypeBase;
1135 }
1136
1137 GAIA_NODISCARD bool operator==(const ArchetypeLookupKey& other) const {
1138 // Hash doesn't match we don't have a match.
1139 // Hash collisions are expected to be very unlikely so optimize for this case.
1140 if GAIA_LIKELY (m_hash != other.m_hash)
1141 return false;
1142
1143 const auto id = m_pArchetypeBase->id();
1144 if (id == ArchetypeIdBad) {
1145 const auto* pArchetype = (const Archetype*)other.m_pArchetypeBase;
1146 const auto* pArchetypeLookupChecker = (const ArchetypeLookupChecker*)m_pArchetypeBase;
1147 return pArchetype->cmp_comps(*pArchetypeLookupChecker);
1148 }
1149
1150 // Real ArchetypeID is given. Compare the pointers.
1151 // Normally we'd compare archetype IDs but because we do not allow archetype copies and all archetypes are
1152 // unique it's guaranteed that if pointers are the same we have a match.
1153 // This also saves a pointer indirection because we do not access the memory the pointer points to.
1154 return m_pArchetypeBase == other.m_pArchetypeBase;
1155 }
1156 };
1157
1159 } // namespace ecs
1160} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
Definition span_impl.h:99
Definition archetype.h:57
ArchetypeId m_archetypeId
Archetype ID - used to address the archetype directly in the world's list or archetypes.
Definition archetype.h:60
Definition archetype.h:68
Definition archetype.h:1118
Definition archetype.h:82
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:550
void set_max_lifespan(uint32_t lifespan)
Sets maximal lifespan of an archetype.
Definition archetype.h:960
void build_graph_edges(Archetype *pArchetypeRight, Entity entity)
Builds a graph edge from this archetype to the right archetype.
Definition archetype.h:884
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:797
void set_hashes(LookupHash hashLookup)
Sets hashes for each component type and lookup.
Definition archetype.h:511
GAIA_NODISCARD uint32_t pairs() const
Returns the number of pairs registered in the archetype.
Definition archetype.h:656
void remove_entity(Chunk &chunk, uint16_t row, EntityContainers &recs)
Removes an entity from the chunk and updates the chunk versions.
Definition archetype.h:625
void del(Chunk *pChunk)
Removes a chunk from the list of chunks managed by their archetype and deletes its memory.
Definition archetype.h:527
GAIA_NODISCARD uint32_t pairs_is() const
Returns the number of Is pairs registered in the archetype.
Definition archetype.h:662
void sort_entities_inter(size_t low, size_t high, TSortByFunc func)
Generic in-place quicksort across chunks.
Definition archetype.h:767
GAIA_NODISCARD bool ready_to_die() const
Tells whether archetype is ready to be deleted.
Definition archetype.h:1010
GAIA_NODISCARD bool dead() const
Checks is this chunk is dying.
Definition archetype.h:983
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:520
void die()
Marks the chunk as dead.
Definition archetype.h:978
GAIA_NODISCARD ArchetypeGraphEdge find_edge_left(Entity entity) const
Checks if an archetype graph "del" edge with entity.
Definition archetype.h:922
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:1110
GAIA_NODISCARD bool progress_death()
Updates internal lifespan.
Definition archetype.h:1002
GAIA_NODISCARD bool empty() const
Checks is there are no chunk in the archetype.
Definition archetype.h:943
GAIA_NODISCARD bool has(Entity entity) const
Checks if an entity is a part of the archetype.
Definition archetype.h:674
GAIA_NODISCARD bool is_req_del() const
Returns true if this archetype is requested to be deleted.
Definition archetype.h:953
GAIA_NODISCARD bool dying() const
Checks is this chunk is dying.
Definition archetype.h:973
GAIA_NODISCARD bool has() const
Checks if component.
Definition archetype.h:684
const void * getValue(uint32_t compIdx, size_t flatIndex, Entity &outEntity) const
Given a flat index, return a reference to the value.
Definition archetype.h:731
void revive()
Makes the archetype alive again.
Definition archetype.h:994
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:583
GAIA_NODISCARD ArchetypeGraphEdge find_edge_right(Entity entity) const
Checks if an archetype graph "add" edge with entity.
Definition archetype.h:916
Entity getValue(size_t flatIndex) const
Given a flat index, return a reference to the value.
Definition archetype.h:699
void req_del()
Request deleting the archetype.
Definition archetype.h:948
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:595
void remove_entity_raw(Chunk &chunk, uint16_t row, EntityContainers &recs)
Removes an entity from the chunk.
Definition archetype.h:616
void start_dying()
Starts the process of dying.
Definition archetype.h:988
GAIA_NODISCARD uint32_t max_lifespan() const
Returns the maximal lifespan of the archetype. If zero, the archetype it kept indefinitely.
Definition archetype.h:968
void sort_entities(Entity entity, TSortByFunc func)
Sorts all entities in the archetypes according to the given function.
Definition archetype.h:833
Definition chunk.h:29
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:1001
GAIA_NODISCARD uint32_t idx() const
Returns the index of this chunk in its archetype's storage.
Definition chunk.h:1409
void update_versions()
Updates the version numbers for this chunk.
Definition chunk.h:507
GAIA_NODISCARD uint16_t size() const
Returns the total number of entities in the chunk (both enabled and disabled)
Definition chunk.h:1471
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:1126
Definition world.h:48
Wrapper for two Entities forming a relationship pair.
Definition id.h:395
Definition robin_hood.h:720
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition archetype.h:48
Definition archetype_common.h:18
Definition archetype.h:93
uint16_t capacity
The number of data entities this archetype can take (e.g 5 = 5 entities with all their components)
Definition archetype.h:95
uint8_t cntEntities
Total number of entities/components.
Definition archetype.h:101
ChunkDataOffset chunkDataBytes
How many bytes of data is needed for a fully utilized chunk.
Definition archetype.h:97
uint8_t genEntities
The number of generic entities/components.
Definition archetype.h:99
Definition component_cache_item.h:24
Definition entity_container.h:151
Definition id.h:225
Definition id.h:217