Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
chunk.h
1#pragma once
2#include "gaia/config/config.h"
3#include "gaia/config/profiler.h"
4
5#include <cstdint>
6#include <cstring>
7#include <tuple>
8#include <type_traits>
9#include <utility>
10
11#include "gaia/cnt/sarray_ext.h"
12#include "gaia/core/utility.h"
13#include "gaia/ecs/archetype_common.h"
14#include "gaia/ecs/chunk_allocator.h"
15#include "gaia/ecs/chunk_header.h"
16#include "gaia/ecs/common.h"
17#include "gaia/ecs/component.h"
18#include "gaia/ecs/component_cache.h"
19#include "gaia/ecs/component_desc.h"
20#include "gaia/ecs/entity_container.h"
21#include "gaia/ecs/id.h"
22#include "gaia/ecs/ser_binary.h"
23#include "gaia/mem/data_layout_policy.h"
24#include "gaia/mem/mem_alloc.h"
25#include "gaia/ser/ser_rt.h"
26
27namespace gaia {
28 namespace ecs {
29 class GAIA_API Chunk final {
30 public:
34
35 private:
37 ChunkHeader m_header;
39 ChunkRecords m_records;
40
51 uint8_t m_data[1];
52
53 GAIA_MSVC_WARNING_PUSH()
54 GAIA_MSVC_WARNING_DISABLE(26495)
55
56 // Hidden default constructor. Only use to calculate the relative offset of m_data
57 Chunk() = default;
58
59 Chunk(
60 const World& wld, const ComponentCache& cc, //
61 uint32_t chunkIndex, uint16_t capacity, uint8_t genEntities, //
62 uint32_t& worldVersion): //
63 m_header(wld, cc, chunkIndex, capacity, genEntities, worldVersion) {
64 // Chunk data area consist of memory offsets, entities, and component data. Normally, we would need
65 // to in-place construct all of it manually.
66 // However, the memory offsets and entities are all trivial types and components are initialized via
67 // their constructors on-demand (if not trivial) so we do not really need to do any construction here.
68 }
69
70 GAIA_MSVC_WARNING_POP()
71
72 GAIA_CLANG_WARNING_PUSH()
73 // Memory is aligned so we can silence this warning
74 GAIA_CLANG_WARNING_DISABLE("-Wcast-align")
75
76 void init(
77 uint32_t cntEntities, const Entity* ids, const Component* comps, const ChunkDataOffsets& headerOffsets,
78 const ChunkDataOffset* compOffs) {
79 m_header.cntEntities = (uint8_t)cntEntities;
80
81 // Cache pointers to versions
82 m_records.pVersions = (ComponentVersion*)&data(headerOffsets.firstByte_Versions);
83
84 // Cache entity ids
85 if (cntEntities > 0) {
86 auto* dst = m_records.pCompEntities = (Entity*)&data(headerOffsets.firstByte_CompEntities);
87
88 // We treat the entity array as if were MAX_COMPONENTS long.
89 // Real size can be smaller.
90 uint32_t j = 0;
91 for (; j < cntEntities; ++j)
92 dst[j] = ids[j];
93 for (; j < ChunkHeader::MAX_COMPONENTS; ++j)
94 dst[j] = EntityBad;
95 }
96
97 // Cache component records
98 if (cntEntities > 0) {
99 auto* dst = m_records.pRecords = (ComponentRecord*)&data(headerOffsets.firstByte_Records);
100 GAIA_FOR_(cntEntities, j) {
101 dst[j].comp = comps[j];
102 dst[j].pData = &data(compOffs[j]);
103 dst[j].pItem = m_header.cc->find(comps[j].id());
104 }
105 }
106
107 m_records.pEntities = (Entity*)&data(headerOffsets.firstByte_EntityData);
108
109 // Now that records are set, we use the cached component descriptors to set ctor/dtor masks.
110 {
111 auto recs = comp_rec_view();
112 const auto recs_cnt = recs.size();
113 GAIA_FOR(recs_cnt) {
114 const auto& rec = recs[i];
115 if (rec.comp.size() == 0)
116 continue;
117
118 const auto e = m_records.pCompEntities[i];
119 if (e.kind() == EntityKind::EK_Gen) {
120 m_header.hasAnyCustomGenCtor |= (rec.pItem->func_ctor != nullptr);
121 m_header.hasAnyCustomGenDtor |= (rec.pItem->func_dtor != nullptr);
122 } else {
123 m_header.hasAnyCustomUniCtor |= (rec.pItem->func_ctor != nullptr);
124 m_header.hasAnyCustomUniDtor |= (rec.pItem->func_dtor != nullptr);
125
126 // We construct unique components right away if possible
127 call_ctor(0, *rec.pItem);
128 }
129 }
130 }
131
132 // Make sure world versions are set initially.
133 update_world_version_init();
134 }
135
136 GAIA_CLANG_WARNING_POP()
137
138
140 GAIA_NODISCARD std::span<const ComponentVersion> comp_version_view() const {
141 return {(const ComponentVersion*)m_records.pVersions, (size_t)m_header.cntEntities + 1};
142 }
143
146 GAIA_NODISCARD std::span<ComponentVersion> comp_version_view_mut() {
147 return {m_records.pVersions, (size_t)m_header.cntEntities + 1};
148 }
149
150 GAIA_NODISCARD std::span<Entity> entity_view_mut() {
151 return {m_records.pEntities, m_header.count};
152 }
153
158 template <typename T>
159 GAIA_NODISCARD GAIA_FORCEINLINE auto view_inter(uint32_t from, uint32_t to) const //
160 -> decltype(std::span<const uint8_t>{}) {
161
162 if constexpr (std::is_same_v<core::raw_t<T>, Entity>) {
163 GAIA_ASSERT(to <= m_header.count);
164 return {(const uint8_t*)&m_records.pEntities[from], to - from};
165 } else if constexpr (is_pair<T>::value) {
166 using TT = typename T::type;
167 using U = typename component_type_t<TT>::Type;
168 static_assert(!std::is_empty_v<U>, "Attempting to get value of an empty component");
169
170 constexpr auto kind = entity_kind_v<TT>;
171 const auto rel = m_header.cc->get<typename T::rel>().entity;
172 const auto tgt = m_header.cc->get<typename T::tgt>().entity;
173 const auto compIdx = comp_idx((Entity)Pair(rel, tgt));
174
175 if constexpr (mem::is_soa_layout_v<U>) {
176 GAIA_ASSERT(from == 0);
177 GAIA_ASSERT(to == capacity());
178 return {comp_ptr(compIdx), to};
179 } else if constexpr (kind == EntityKind::EK_Gen) {
180 GAIA_ASSERT(to <= m_header.count);
181 return {comp_ptr(compIdx, from), to - from};
182 } else {
183 GAIA_ASSERT(to <= m_header.count);
184 // GAIA_ASSERT(count == 1); we don't really care and always consider 1 for unique components
185 return {comp_ptr(compIdx), 1};
186 }
187 } else {
188 using U = typename component_type_t<T>::Type;
189 static_assert(!std::is_empty_v<U>, "Attempting to get value of an empty component");
190
191 constexpr auto kind = entity_kind_v<T>;
192 const auto comp = m_header.cc->get<T>().entity;
193 GAIA_ASSERT(comp.kind() == kind);
194 const auto compIdx = comp_idx(comp);
195
196 if constexpr (mem::is_soa_layout_v<U>) {
197 GAIA_ASSERT(from == 0);
198 GAIA_ASSERT(to == capacity());
199 return {comp_ptr(compIdx), to};
200 } else if constexpr (kind == EntityKind::EK_Gen) {
201 GAIA_ASSERT(to <= m_header.count);
202 return {comp_ptr(compIdx, from), to - from};
203 } else {
204 GAIA_ASSERT(to <= m_header.count);
205 // GAIA_ASSERT(count == 1); we don't really care and always consider 1 for unique components
206 return {comp_ptr(compIdx), 1};
207 }
208 }
209 }
210
216 template <typename T, bool WorldVersionUpdateWanted>
217 GAIA_NODISCARD GAIA_FORCEINLINE auto view_mut_inter(uint32_t from, uint32_t to) //
218 -> decltype(std::span<uint8_t>{}) {
219 static_assert(!std::is_same_v<core::raw_t<T>, Entity>, "view_mut can't be used to modify Entity");
220
221 if constexpr (is_pair<T>::value) {
222 using TT = typename T::type;
223 using U = typename component_type_t<TT>::Type;
224 static_assert(!std::is_empty_v<U>, "view_mut can't be used to modify tag components");
225
226 constexpr auto kind = entity_kind_v<TT>;
227 const auto rel = m_header.cc->get<typename T::rel>().entity;
228 const auto tgt = m_header.cc->get<typename T::tgt>().entity;
229 const auto compIdx = comp_idx((Entity)Pair(rel, tgt));
230
231 // Update version number if necessary so we know RW access was used on the chunk
232 if constexpr (WorldVersionUpdateWanted) {
233 update_world_version(compIdx);
234
235#if GAIA_ENABLE_SET_HOOKS
236 const auto& rec = m_records.pRecords[compIdx];
237 if GAIA_UNLIKELY (rec.pItem->comp_hooks.func_set != nullptr)
238 rec.pItem->comp_hooks.func_set(*m_header.world, rec, *this);
239#endif
240 }
241
242 if constexpr (mem::is_soa_layout_v<U>) {
243 GAIA_ASSERT(from == 0);
244 GAIA_ASSERT(to == capacity());
245 return {comp_ptr_mut(compIdx), to};
246 } else if constexpr (kind == EntityKind::EK_Gen) {
247 GAIA_ASSERT(to <= m_header.count);
248 return {comp_ptr_mut(compIdx, from), to - from};
249 } else {
250 GAIA_ASSERT(to <= m_header.count);
251 // GAIA_ASSERT(count == 1); we don't really care and always consider 1 for unique components
252 return {comp_ptr_mut(compIdx), 1};
253 }
254 } else {
255 using U = typename component_type_t<T>::Type;
256 static_assert(!std::is_empty_v<U>, "view_mut can't be used to modify tag components");
257
258 constexpr auto kind = entity_kind_v<T>;
259 const auto comp = m_header.cc->get<T>().entity;
260 GAIA_ASSERT(comp.kind() == kind);
261 const auto compIdx = comp_idx(comp);
262
263 // Update version number if necessary so we know RW access was used on the chunk
264 if constexpr (WorldVersionUpdateWanted) {
265 update_world_version(compIdx);
266
267#if GAIA_ENABLE_SET_HOOKS
268 const auto& rec = m_records.pRecords[compIdx];
269 if GAIA_UNLIKELY (rec.pItem->comp_hooks.func_set != nullptr)
270 rec.pItem->comp_hooks.func_set(*m_header.world, rec, *this);
271#endif
272 }
273
274 if constexpr (mem::is_soa_layout_v<U>) {
275 GAIA_ASSERT(from == 0);
276 GAIA_ASSERT(to == capacity());
277 return {comp_ptr_mut(compIdx), to};
278 } else if constexpr (kind == EntityKind::EK_Gen) {
279 GAIA_ASSERT(to <= m_header.count);
280 return {comp_ptr_mut(compIdx, from), to - from};
281 } else {
282 GAIA_ASSERT(to <= m_header.count);
283 // GAIA_ASSERT(count == 1); we don't really care and always consider 1 for unique components
284 return {comp_ptr_mut(compIdx), 1};
285 }
286 }
287 }
288
289 public:
296 template <bool WorldVersionUpdateWanted>
297 GAIA_NODISCARD GAIA_FORCEINLINE auto comp_ptr_mut_gen(uint32_t compIdx, uint32_t row) {
298 // Update version number if necessary so we know RW access was used on the chunk
299 if constexpr (WorldVersionUpdateWanted) {
300 update_world_version(compIdx);
301
302#if GAIA_ENABLE_SET_HOOKS
303 const auto& rec = m_records.pRecords[compIdx];
304 if GAIA_UNLIKELY (rec.pItem->comp_hooks.func_set != nullptr)
305 rec.pItem->comp_hooks.func_set(*m_header.world, rec, *this);
306#endif
307 }
308
309 return comp_ptr_mut(compIdx, row);
310 }
311
312 private:
319 template <typename T>
320 GAIA_NODISCARD decltype(auto) comp_inter(uint16_t row) const {
321 using U = typename actual_type_t<T>::Type;
322 using RetValueType = decltype(view<T>()[0]);
323
324 GAIA_ASSERT(row < m_header.count);
325 if constexpr (mem::is_soa_layout_v<U>)
326 return view<T>(0, capacity())[row];
327 else if constexpr (sizeof(RetValueType) <= 8)
328 return view<T>()[row];
329 else
330 return (const U&)view<T>()[row];
331 }
332
333 public:
334 Chunk(const Chunk& chunk) = delete;
335 Chunk(Chunk&& chunk) = delete;
336 Chunk& operator=(const Chunk& chunk) = delete;
337 Chunk& operator=(Chunk&& chunk) = delete;
338 ~Chunk() = default;
339
340 static constexpr uint16_t chunk_header_size() {
341 const auto dataAreaOffset =
342 // ChunkAllocator reserves the first few bytes for internal purposes
343 MemoryBlockUsableOffset +
344 // Chunk "header" area (before actual entity/component data starts)
345 sizeof(ChunkHeader) + sizeof(ChunkRecords);
346 static_assert(dataAreaOffset < UINT16_MAX);
347 return dataAreaOffset;
348 }
349
350 static constexpr uint16_t chunk_total_bytes(uint16_t dataSize) {
351 return chunk_header_size() + dataSize;
352 }
353
354 static constexpr uint16_t chunk_data_bytes(uint16_t totalSize) {
355 return totalSize - chunk_header_size();
356 }
357
359 static uintptr_t chunk_data_area_offset() {
360 // Note, offsetof is implementation-defined and conditionally-supported since C++17.
361 // Therefore, we instantiate the chunk and calculate the relative address ourselves.
362 Chunk chunk;
363 const auto chunk_offset = (uintptr_t)&chunk;
364 const auto data_offset = (uintptr_t)&chunk.m_data[0];
365 return data_offset - chunk_offset;
366 }
367
370 static Chunk* create(
371 const World& wld, const ComponentCache& cc, //
372 uint32_t chunkIndex, uint16_t capacity, uint8_t cntEntities, uint8_t genEntities, //
373 uint16_t dataBytes, uint32_t& worldVersion,
374 // data offsets
375 const ChunkDataOffsets& offsets,
376 // component entities
377 const Entity* ids,
378 // component
379 const Component* comps,
380 // component offsets
381 const ChunkDataOffset* compOffs) {
382 const auto totalBytes = chunk_total_bytes(dataBytes);
383#if GAIA_ECS_CHUNK_ALLOCATOR
384 auto* pChunk = (Chunk*)ChunkAllocator::get().alloc(totalBytes);
385 (void)new (pChunk) Chunk(wld, cc, chunkIndex, capacity, genEntities, worldVersion);
386#else
387 GAIA_ASSERT(totalBytes <= MaxMemoryBlockSize);
388 const auto sizeType = mem_block_size_type(totalBytes);
389 const auto allocSize = mem_block_size(sizeType);
390 auto* pChunkMem = mem::AllocHelper::alloc<uint8_t>(allocSize);
391 std::memset(pChunkMem, 0, allocSize);
392 auto* pChunk = new (pChunkMem) Chunk(wld, cc, chunkIndex, capacity, genEntities, worldVersion);
393#endif
394
395 pChunk->init((uint32_t)cntEntities, ids, comps, offsets, compOffs);
396 return pChunk;
397 }
398
401 static void free(Chunk* pChunk) {
402 GAIA_ASSERT(pChunk != nullptr);
403 GAIA_ASSERT(!pChunk->dead());
404
405 // Mark as dead
406 pChunk->die();
407
408 // Call destructors for components that need it
409 pChunk->call_all_dtors();
410
411 pChunk->~Chunk();
412#if GAIA_ECS_CHUNK_ALLOCATOR
413 ChunkAllocator::get().free(pChunk);
414#else
415 mem::AllocHelper::free((uint8_t*)pChunk);
416#endif
417 }
418
419 void save(ser::ISerializer& s) {
420 s.save(m_header.count);
421 if (m_header.count == 0)
422 return;
423
424 s.save(m_header.countEnabled);
425
426 const uint16_t dead = m_header.dead;
427 const uint16_t lifespanCountdown = m_header.lifespanCountdown;
428 s.save(dead);
429 s.save(lifespanCountdown);
430
431 const auto cnt = (uint32_t)m_header.count;
432 const auto cap = (uint32_t)m_header.capacity;
433
434 // Store entity data
435 {
436 const auto* pData = m_records.pEntities;
437 GAIA_FOR(cnt) s.save(pData[i]);
438 }
439
440 // Store component data
441 {
442 for (const auto& rec: comp_rec_view()) {
443 // Skip the component if there's no size associated with it
444 if (rec.comp.size() == 0)
445 continue;
446
447 rec.pItem->save(&s, rec.pData, 0, cnt, cap);
448 }
449 }
450 }
451
452 void load(ser::ISerializer& s) {
453 uint16_t prevCount = m_header.count;
454 s.load(m_header.count);
455 if (m_header.count == 0)
456 return;
457
458 s.load(m_header.countEnabled);
459
460 uint16_t dead = 0;
461 uint16_t lifespanCountdown = 0;
462 s.load(dead);
463 s.load(lifespanCountdown);
464 m_header.dead = dead != 0;
465 m_header.lifespanCountdown = lifespanCountdown;
466
467 const auto cnt = (uint32_t)m_header.count;
468 const auto cap = (uint32_t)m_header.capacity;
469
470 // Load entity data
471 {
472 GAIA_FOR(cnt) {
473 Entity e;
474 s.load(e);
475 entity_view_mut()[i] = e;
476 }
477 }
478
479 // Load component data. Call constructors first as necessary.
480 call_gen_ctors(prevCount, cnt);
481 {
482 for (const auto& rec: comp_rec_view()) {
483 // Skip the component if there's no size associated with it
484 if (rec.comp.size() == 0)
485 continue;
486
487 rec.pItem->load(&s, rec.pData, 0, cnt, cap);
488 }
489 }
490 }
491
495 // Should never be called over an empty chunk
496 GAIA_ASSERT(!empty());
497
498#if GAIA_ASSERT_ENABLED
499 // Invalidate the entity in chunk data
500 entity_view_mut()[m_header.count - 1] = EntityBad;
501#endif
502
503 --m_header.count;
504 }
505
508 ::gaia::ecs::update_version(m_header.worldVersion);
509 update_world_version();
510 }
511
518 template <typename T>
519 GAIA_NODISCARD decltype(auto) view(uint16_t from, uint16_t to) const {
520 using U = typename actual_type_t<T>::Type;
521
522 // Always consider full range for SoA
523 if constexpr (mem::is_soa_layout_v<U>)
524 return mem::auto_view_policy_get<U>{view_inter<T>(0, capacity())};
525 else
526 return mem::auto_view_policy_get<U>{view_inter<T>(from, to)};
527 }
528
529 template <typename T>
530 GAIA_NODISCARD decltype(auto) view() const {
531 return view<T>(0, m_header.count);
532 }
533
534 template <typename T>
535 GAIA_NODISCARD decltype(auto) view_raw(const void* ptr, uint32_t size) const {
536 using U = typename actual_type_t<T>::Type;
537 return mem::auto_view_policy_get<U>{std::span{(const uint8_t*)ptr, size}};
538 }
539
546 template <typename T>
547 GAIA_NODISCARD decltype(auto) view_mut(uint16_t from, uint16_t to) {
548 using U = typename actual_type_t<T>::Type;
549 static_assert(!std::is_same_v<U, Entity>, "Modifying chunk entities via view_mut is forbidden");
550
551 // Always consider full range for SoA
552 if constexpr (mem::is_soa_layout_v<U>)
553 return mem::auto_view_policy_set<U>{view_mut_inter<T, true>(0, capacity())};
554 else
555 return mem::auto_view_policy_set<U>{view_mut_inter<T, true>(from, to)};
556 }
557
558 template <typename T>
559 GAIA_NODISCARD decltype(auto) view_mut() {
560 return view_mut<T>(0, m_header.count);
561 }
562
563 template <typename T>
564 GAIA_NODISCARD decltype(auto) view_mut_raw(void* ptr, uint32_t size) const {
565 using U = typename actual_type_t<T>::Type;
566 static_assert(!std::is_same_v<U, Entity>, "Modifying chunk entities via view_mut is forbidden");
567
568 return mem::auto_view_policy_set<U>{std::span{(uint8_t*)ptr, size}};
569 }
570
578 template <typename T>
579 GAIA_NODISCARD decltype(auto) sview_mut(uint16_t from, uint16_t to) {
580 using U = typename actual_type_t<T>::Type;
581 static_assert(!std::is_same_v<U, Entity>, "Modifying chunk entities via sview_mut is forbidden");
582
583 // Always consider full range for SoA
584 if constexpr (mem::is_soa_layout_v<U>)
585 return mem::auto_view_policy_set<U>{view_mut_inter<T, false>(0, capacity())};
586 else
587 return mem::auto_view_policy_set<U>{view_mut_inter<T, false>(from, to)};
588 }
589
590 template <typename T>
591 GAIA_NODISCARD decltype(auto) sview_mut_raw(void* ptr, uint32_t size) const {
592 using U = typename actual_type_t<T>::Type;
593 static_assert(!std::is_same_v<U, Entity>, "Modifying chunk entities via sview_mut is forbidden");
594
595 return mem::auto_view_policy_set<U>{std::span{(uint8_t*)ptr, size}};
596 }
597
598 template <typename T>
599 GAIA_NODISCARD decltype(auto) sview_mut() {
600 return sview_mut<T>(0, m_header.count);
601 }
602
606 template <
607 typename T
608#if GAIA_ENABLE_HOOKS
609 ,
610 bool TriggerHooks
611#endif
612 >
613 GAIA_FORCEINLINE void modify() {
614 static_assert(!std::is_same_v<core::raw_t<T>, Entity>, "mod can't be used to modify Entity");
615
616 if constexpr (is_pair<T>::value) {
617 using TT = typename T::type;
618 using U = typename component_type_t<TT>::Type;
619 static_assert(!std::is_empty_v<U>, "mut can't be used to modify tag components");
620
621#if GAIA_ASSERT_ENABLED
622 // constexpr auto kind = entity_kind_v<TT>;
623#endif
624 const auto rel = m_header.cc->get<typename T::rel>().entity;
625 const auto tgt = m_header.cc->get<typename T::tgt>().entity;
626 const auto compIdx = comp_idx((Entity)Pair(rel, tgt));
627
628 // Update version number if necessary so we know RW access was used on the chunk
629 update_world_version(compIdx);
630
631#if GAIA_ENABLE_SET_HOOKS
632 if constexpr (TriggerHooks) {
633 const auto& rec = m_records.pRecords[compIdx];
634 if GAIA_UNLIKELY (rec.pItem->comp_hooks.func_set != nullptr)
635 rec.pItem->comp_hooks.func_set(*m_header.world, rec, *this);
636 }
637#endif
638 } else {
639 using U = typename component_type_t<T>::Type;
640 static_assert(!std::is_empty_v<U>, "mut can't be used to modify tag components");
641
642#if GAIA_ASSERT_ENABLED
643 constexpr auto kind = entity_kind_v<T>;
644#endif
645 const auto comp = m_header.cc->get<T>().entity;
646 GAIA_ASSERT(comp.kind() == kind);
647 const auto compIdx = comp_idx(comp);
648
649 // Update version number if necessary so we know RW access was used on the chunk
650 update_world_version(compIdx);
651
652#if GAIA_ENABLE_SET_HOOKS
653 if constexpr (TriggerHooks) {
654 const auto& rec = m_records.pRecords[compIdx];
655 if GAIA_UNLIKELY (rec.pItem->comp_hooks.func_set != nullptr)
656 rec.pItem->comp_hooks.func_set(*m_header.world, rec, *this);
657 }
658#endif
659 }
660 }
661
669 template <typename T>
670 GAIA_NODISCARD decltype(auto) view_auto(uint16_t from, uint16_t to) {
671 using UOriginal = typename actual_type_t<T>::TypeOriginal;
672 if constexpr (core::is_mut_v<UOriginal>)
673 return view_mut<T>(from, to);
674 else
675 return view<T>(from, to);
676 }
677
678 template <typename T>
679 GAIA_NODISCARD decltype(auto) view_auto() {
680 return view_auto<T>(0, m_header.count);
681 }
682
691 template <typename T>
692 GAIA_NODISCARD decltype(auto) sview_auto(uint16_t from, uint16_t to) {
693 using UOriginal = typename actual_type_t<T>::TypeOriginal;
694 if constexpr (core::is_mut_v<UOriginal>)
695 return sview_mut<T>(from, to);
696 else
697 return view<T>(from, to);
698 }
699
700 template <typename T>
701 GAIA_NODISCARD decltype(auto) sview_auto() {
702 return sview_auto<T>(0, m_header.count);
703 }
704
705 GAIA_NODISCARD EntitySpan entity_view() const {
706 return {(const Entity*)m_records.pEntities, m_header.count};
707 }
708
709 GAIA_NODISCARD EntitySpan ids_view() const {
710 return {(const Entity*)m_records.pCompEntities, m_header.cntEntities};
711 }
712
713 GAIA_NODISCARD std::span<const ComponentRecord> comp_rec_view() const {
714 return {m_records.pRecords, m_header.cntEntities};
715 }
716
717 GAIA_NODISCARD uint8_t* comp_ptr_mut(uint32_t compIdx) {
718 const auto& rec = m_records.pRecords[compIdx];
719 return rec.pData;
720 }
721
722 GAIA_NODISCARD uint8_t* comp_ptr_mut(uint32_t compIdx, uint32_t offset) {
723 const auto& rec = m_records.pRecords[compIdx];
724 return rec.pData + ((uintptr_t)rec.comp.size() * offset);
725 }
726
727 GAIA_NODISCARD const uint8_t* comp_ptr(uint32_t compIdx) const {
728 const auto& rec = m_records.pRecords[compIdx];
729 return rec.pData;
730 }
731
732 GAIA_NODISCARD const uint8_t* comp_ptr(uint32_t compIdx, uint32_t offset) const {
733 const auto& rec = m_records.pRecords[compIdx];
734 return rec.pData + ((uintptr_t)rec.comp.size() * offset);
735 }
736
739 GAIA_NODISCARD uint16_t add_entity(Entity entity) {
740 const auto row = m_header.count++;
741
742 // Zero after increase of value means an overflow!
743 GAIA_ASSERT(m_header.count != 0);
744
745 ++m_header.countEnabled;
746 entity_view_mut()[row] = entity;
747
748 return row;
749 }
750
755 static void copy_entity_data(Entity srcEntity, Entity dstEntity, EntityContainers& recs) {
756 GAIA_PROF_SCOPE(Chunk::copy_entity_data);
757
758 auto& srcEntityContainer = recs[srcEntity];
759 auto* pSrcChunk = srcEntityContainer.pChunk;
760
761 auto& dstEntityContainer = recs[dstEntity];
762 auto* pDstChunk = dstEntityContainer.pChunk;
763
764 GAIA_ASSERT(srcEntityContainer.pArchetype == dstEntityContainer.pArchetype);
765
766 auto srcRecs = pSrcChunk->comp_rec_view();
767
768 // Copy generic component data from reference entity to our new entity.
769 // Unique components do not change place in the chunk so there is no need to move them.
770 GAIA_FOR(pSrcChunk->m_header.genEntities) {
771 const auto& rec = srcRecs[i];
772 if (rec.comp.size() == 0U)
773 continue;
774
775 const auto* pSrc = (const void*)pSrcChunk->comp_ptr_mut(i);
776 auto* pDst = (void*)pDstChunk->comp_ptr_mut(i);
777 rec.pItem->copy(
778 pDst, pSrc, dstEntityContainer.row, srcEntityContainer.row, pDstChunk->capacity(), pSrcChunk->capacity());
779 }
780 }
781
786 void move_entity_data(Entity entity, uint16_t row, EntityContainers& recs) {
787 GAIA_PROF_SCOPE(Chunk::move_entity_data);
788
789 auto& ec = recs[entity];
790 auto* pSrcChunk = ec.pChunk;
791 auto srcRecs = pSrcChunk->comp_rec_view();
792
793 // Copy generic component data from reference entity to our new entity.
794 // Unique components do not change place in the chunk so there is no need to move them.
795 GAIA_FOR(pSrcChunk->m_header.genEntities) {
796 const auto& rec = srcRecs[i];
797 if (rec.comp.size() == 0U)
798 continue;
799
800 auto* pSrc = (void*)pSrcChunk->comp_ptr_mut(i);
801 auto* pDst = (void*)comp_ptr_mut(i);
802 rec.pItem->ctor_move(pDst, pSrc, row, ec.row, capacity(), pSrcChunk->capacity());
803 }
804 }
805
811 static void copy_foreign_entity_data(Chunk* pSrcChunk, uint32_t srcRow, Chunk* pDstChunk, uint32_t dstRow) {
812 GAIA_PROF_SCOPE(Chunk::copy_foreign_entity_data);
813
814 GAIA_ASSERT(pSrcChunk != nullptr);
815 GAIA_ASSERT(pDstChunk != nullptr);
816 GAIA_ASSERT(srcRow < pSrcChunk->size());
817 GAIA_ASSERT(dstRow < pDstChunk->size());
818
819 auto srcIds = pSrcChunk->ids_view();
820 auto dstIds = pDstChunk->ids_view();
821 auto dstRecs = pDstChunk->comp_rec_view();
822
823 // Find intersection of the two component lists.
824 // Arrays are sorted so we can do linear intersection lookup.
825 // Call constructor on each match.
826 // Unique components do not change place in the chunk so there is no need to move them.
827 {
828 uint32_t i = 0;
829 uint32_t j = 0;
830 while (i < pSrcChunk->m_header.genEntities && j < pDstChunk->m_header.genEntities) {
831 const auto oldId = srcIds[i];
832 const auto newId = dstIds[j];
833
834 if (oldId == newId) {
835 const auto& rec = dstRecs[j];
836 if (rec.comp.size() != 0U) {
837 auto* pSrc = (void*)pSrcChunk->comp_ptr_mut(i);
838 auto* pDst = (void*)pDstChunk->comp_ptr_mut(j);
839 rec.pItem->ctor_copy(pDst, pSrc, dstRow, srcRow, pDstChunk->capacity(), pSrcChunk->capacity());
840 }
841
842 ++i;
843 ++j;
844 } else if (SortComponentCond{}.operator()(oldId, newId)) {
845 ++i;
846 } else {
847 // No match with the old chunk. Construct the component
848 const auto& rec = dstRecs[j];
849 if (rec.pItem != nullptr && rec.pItem->func_ctor != nullptr) {
850 auto* pDst = (void*)pDstChunk->comp_ptr_mut(j, dstRow);
851 rec.pItem->func_ctor(pDst, 1);
852 }
853
854 ++j;
855 }
856 }
857
858 // Initialize the rest of the components if they are generic.
859 for (; j < pDstChunk->m_header.genEntities; ++j) {
860 const auto& rec = dstRecs[j];
861 if (rec.pItem != nullptr && rec.pItem->func_ctor != nullptr) {
862 auto* pDst = (void*)pDstChunk->comp_ptr_mut(j, dstRow);
863 rec.pItem->func_ctor(pDst, 1);
864 }
865 }
866 }
867 }
868
874 static void move_foreign_entity_data(Chunk* pSrcChunk, uint32_t srcRow, Chunk* pDstChunk, uint32_t dstRow) {
875 GAIA_PROF_SCOPE(Chunk::move_foreign_entity_data);
876
877 GAIA_ASSERT(pSrcChunk != nullptr);
878 GAIA_ASSERT(pDstChunk != nullptr);
879 GAIA_ASSERT(srcRow < pSrcChunk->size());
880 GAIA_ASSERT(dstRow < pDstChunk->size());
881
882 auto srcIds = pSrcChunk->ids_view();
883 auto dstIds = pDstChunk->ids_view();
884 auto dstRecs = pDstChunk->comp_rec_view();
885
886 // Find intersection of the two component lists.
887 // Arrays are sorted so we can do linear intersection lookup.
888 // Call constructor on each match.
889 // Unique components do not change place in the chunk so there is no need to move them.
890 {
891 uint32_t i = 0;
892 uint32_t j = 0;
893 while (i < pSrcChunk->m_header.genEntities && j < pDstChunk->m_header.genEntities) {
894 const auto oldId = srcIds[i];
895 const auto newId = dstIds[j];
896
897 if (oldId == newId) {
898 const auto& rec = dstRecs[j];
899 if (rec.comp.size() != 0U) {
900 auto* pSrc = (void*)pSrcChunk->comp_ptr_mut(i);
901 auto* pDst = (void*)pDstChunk->comp_ptr_mut(j);
902 rec.pItem->ctor_move(pDst, pSrc, dstRow, srcRow, pDstChunk->capacity(), pSrcChunk->capacity());
903 }
904
905 ++i;
906 ++j;
907 } else if (SortComponentCond{}.operator()(oldId, newId)) {
908 ++i;
909 } else {
910 // No match with the old chunk. Construct the component
911 const auto& rec = dstRecs[j];
912 if (rec.pItem != nullptr && rec.pItem->func_ctor != nullptr) {
913 auto* pDst = (void*)pDstChunk->comp_ptr_mut(j, dstRow);
914 rec.pItem->func_ctor(pDst, 1);
915 }
916
917 ++j;
918 }
919 }
920
921 // Initialize the rest of the components if they are generic.
922 for (; j < pDstChunk->m_header.genEntities; ++j) {
923 const auto& rec = dstRecs[j];
924 if (rec.pItem != nullptr && rec.pItem->func_ctor != nullptr) {
925 auto* pDst = (void*)pDstChunk->comp_ptr_mut(j, dstRow);
926 rec.pItem->func_ctor(pDst, 1);
927 }
928 }
929 }
930 }
931
938 void remove_entity_inter(uint16_t row, EntityContainers& recs) {
939 GAIA_PROF_SCOPE(Chunk::remove_entity_inter);
940
941 const uint16_t rowA = row;
942 const uint16_t rowB = m_header.count - 1;
943 // The "rowA" entity is the one we are going to destroy so it needs to precede the "rowB"
944 GAIA_ASSERT(rowA <= rowB);
945
946 // To move anything, we need at least 2 entities
947 if GAIA_LIKELY (rowA < rowB) {
948 GAIA_ASSERT(m_header.count > 1);
949
950 auto ev = entity_view_mut();
951
952 // Update entity data
953 const auto entityB = ev[rowB];
954 auto& ecB = recs[entityB];
955#if GAIA_ASSERT_ENABLED
956 const auto entityA = ev[rowA];
957 auto& ecA = recs[entityA];
958
959 GAIA_ASSERT(ecA.pArchetype == ecB.pArchetype);
960 GAIA_ASSERT(ecA.pChunk == ecB.pChunk);
961#endif
962
963 ev[rowA] = entityB;
964
965 // Move component data from entityB to entityA
966 auto recView = comp_rec_view();
967 GAIA_FOR(m_header.genEntities) {
968 const auto& rec = recView[i];
969 if (rec.comp.size() == 0U)
970 continue;
971
972 auto* pSrc = (void*)comp_ptr_mut(i);
973 rec.pItem->move(pSrc, pSrc, rowA, rowB, capacity(), capacity());
974
975 pSrc = (void*)comp_ptr_mut(i, rowB);
976 rec.pItem->dtor(pSrc);
977 }
978
979 // Entity has been replaced with the last one in our chunk. Update its container record.
980 ecB.row = rowA;
981 } else if (m_header.hasAnyCustomGenDtor) {
982 // This is the last entity in the chunk so simply destroy its data
983 auto recView = comp_rec_view();
984 GAIA_FOR(m_header.genEntities) {
985 const auto& rec = recView[i];
986 if (rec.comp.size() == 0U)
987 continue;
988
989 auto* pSrc = (void*)comp_ptr_mut(i, rowA);
990 rec.pItem->dtor(pSrc);
991 }
992 }
993 }
994
1001 void remove_entity(uint16_t row, EntityContainers& recs) {
1002 if GAIA_UNLIKELY (m_header.count == 0)
1003 return;
1004
1005 GAIA_PROF_SCOPE(Chunk::remove_entity);
1006
1007 if (enabled(row)) {
1008 // Entity was previously enabled. Swap with the last entity
1009 remove_entity_inter(row, recs);
1010 // If this was the first enabled entity make sure to update the row
1011 if (m_header.rowFirstEnabledEntity > 0 && row == m_header.rowFirstEnabledEntity)
1012 --m_header.rowFirstEnabledEntity;
1013 // At this point the last entity is no longer valid so remove it
1014 remove_last_entity();
1015 --m_header.countEnabled;
1016 } else {
1017 // Entity was previously disabled. Swap with the last disabled entity
1018 const uint16_t pivot = size_disabled() - 1;
1019 swap_chunk_entities(row, pivot, recs);
1020 // Once swapped, try to swap with the last (enabled) entity in the chunk.
1021 remove_entity_inter(pivot, recs);
1022 --m_header.rowFirstEnabledEntity;
1023 // At this point the last entity is no longer valid so remove it
1024 remove_last_entity();
1025 }
1026 }
1027
1035 void swap_chunk_entities(uint16_t rowA, uint16_t rowB, EntityContainers& recs) {
1036 // If there are at least two different entities inside to swap
1037 if GAIA_UNLIKELY (m_header.count <= 1 || rowA == rowB)
1038 return;
1039
1040 GAIA_PROF_SCOPE(Chunk::swap_chunk_entities);
1041
1042 // Update entity data
1043 auto ev = entity_view_mut();
1044 const auto entityA = ev[rowA];
1045 const auto entityB = ev[rowB];
1046
1047 auto& ecA = recs[entityA];
1048 auto& ecB = recs[entityB];
1049 GAIA_ASSERT(ecA.pArchetype == ecB.pArchetype);
1050 GAIA_ASSERT(ecA.pChunk == ecB.pChunk);
1051
1052 ev[rowA] = entityB;
1053 ev[rowB] = entityA;
1054
1055 // Swap component data
1056 auto recView = comp_rec_view();
1057 GAIA_FOR(m_header.genEntities) {
1058 const auto& rec = recView[i];
1059 if (rec.comp.size() == 0U)
1060 continue;
1061
1062 GAIA_ASSERT(rec.pData == comp_ptr_mut(i));
1063 rec.pItem->swap(rec.pData, rec.pData, rowA, rowB, capacity(), capacity());
1064 }
1065
1066 // Update indices in entity container.
1067 ecA.row = rowB;
1068 ecB.row = rowA;
1069 }
1070
1077 static void swap_chunk_entities(World& world, Entity entityA, Entity entityB) {
1078 // Don't swap if the two entities are the same
1079 if GAIA_UNLIKELY (entityA == entityB)
1080 return;
1081
1082 GAIA_PROF_SCOPE(Chunk::swap_chunk_entities);
1083
1084 auto& ecA = fetch_mut(world, entityA);
1085 auto& ecB = fetch_mut(world, entityB);
1086
1087 // Make sure the two entities are in the same archetype
1088 GAIA_ASSERT(ecA.pArchetype == ecB.pArchetype);
1089 GAIA_ASSERT(ecA.pArchetype == ecB.pArchetype);
1090
1091 auto* pChunkA = ecA.pChunk;
1092 auto* pChunkB = ecB.pChunk;
1093
1094 // Swap entities in the entity data part
1095 pChunkA->entity_view_mut()[ecA.row] = entityB;
1096 pChunkB->entity_view_mut()[ecB.row] = entityA;
1097
1098 // Swap component data
1099 auto recViewA = pChunkA->comp_rec_view();
1100 GAIA_FOR(pChunkA->m_header.genEntities) {
1101 const auto& recA = recViewA[i];
1102 if (recA.comp.size() == 0U)
1103 continue;
1104
1105 auto* pDataA = pChunkA->comp_rec_view()[i].pData;
1106 auto* pDataB = pChunkB->comp_rec_view()[i].pData;
1107 recA.pItem->swap(
1108 // Data pointers
1109 pDataA, pDataB,
1110 // Rows
1111 ecA.row, ecB.row,
1112 // Chunk capacities
1113 pChunkA->capacity(), pChunkA->capacity() //
1114 );
1115 }
1116
1117 // Update indices and chunks in entity container.
1118 core::swap(ecA.row, ecB.row);
1119 core::swap(ecA.pChunk, ecB.pChunk);
1120 }
1121
1126 void enable_entity(uint16_t row, bool enableEntity, EntityContainers& recs) {
1127 GAIA_ASSERT(row < m_header.count && "Entity chunk row out of bounds!");
1128
1129 if (enableEntity) {
1130 // Nothing to enable if there are no disabled entities
1131 if (!m_header.has_disabled_entities())
1132 return;
1133 // Trying to enable an already enabled entity
1134 if (enabled(row))
1135 return;
1136 // Try swapping our entity with the last disabled one
1137 const auto entity = entity_view()[row];
1138 swap_chunk_entities(--m_header.rowFirstEnabledEntity, row, recs);
1139 recs[entity].data.dis = 0;
1140 ++m_header.countEnabled;
1141 } else {
1142 // Nothing to disable if there are no enabled entities
1143 if (!m_header.has_enabled_entities())
1144 return;
1145 // Trying to disable an already disabled entity
1146 if (!enabled(row))
1147 return;
1148 // Try swapping our entity with the last one in our chunk
1149 const auto entity = entity_view()[row];
1150 swap_chunk_entities(m_header.rowFirstEnabledEntity++, row, recs);
1151 recs[entity].data.dis = 1;
1152 --m_header.countEnabled;
1153 }
1154 }
1155
1159 bool enabled(uint16_t row) const {
1160 GAIA_ASSERT(m_header.count > 0);
1161
1162 return row >= (uint16_t)m_header.rowFirstEnabledEntity;
1163 }
1164
1168 uint8_t& data(uint32_t offset) {
1169 return m_data[offset];
1170 }
1171
1175 const uint8_t& data(uint32_t offset) const {
1176 return m_data[offset];
1177 }
1178
1179 //----------------------------------------------------------------------
1180 // Component handling
1181 //----------------------------------------------------------------------
1182
1183 void call_ctor(uint32_t entIdx, const ComponentCacheItem& item) {
1184 if (item.func_ctor == nullptr)
1185 return;
1186
1187 GAIA_PROF_SCOPE(Chunk::call_ctor);
1188
1189 const auto compIdx = comp_idx(item.entity);
1190 auto* pSrc = (void*)comp_ptr_mut(compIdx, entIdx);
1191 item.func_ctor(pSrc, 1);
1192 }
1193
1194 void call_gen_ctors(uint32_t entIdx, uint32_t entCnt) {
1195 if (!m_header.hasAnyCustomGenCtor)
1196 return;
1197
1198 GAIA_PROF_SCOPE(Chunk::call_gen_ctors);
1199
1200 auto recs = comp_rec_view();
1201 GAIA_FOR(m_header.genEntities) {
1202 const auto& rec = recs[i];
1203
1204 const auto* pItem = rec.pItem;
1205 if (pItem == nullptr || pItem->func_ctor == nullptr)
1206 continue;
1207
1208 auto* pSrc = (void*)comp_ptr_mut(i, entIdx);
1209 pItem->func_ctor(pSrc, entCnt);
1210 }
1211 }
1212
1213 void call_all_dtors() {
1214 if (!m_header.hasAnyCustomGenDtor && !m_header.hasAnyCustomUniCtor)
1215 return;
1216
1217 GAIA_PROF_SCOPE(Chunk::call_all_dtors);
1218
1219 auto ids = ids_view();
1220 auto recs = comp_rec_view();
1221 const auto recs_cnt = recs.size();
1222 GAIA_FOR(recs_cnt) {
1223 const auto& rec = recs[i];
1224
1225 const auto* pItem = rec.pItem;
1226 if (pItem == nullptr || pItem->func_dtor == nullptr)
1227 continue;
1228
1229 auto* pSrc = (void*)comp_ptr_mut(i, 0);
1230 const auto e = ids[i];
1231 const auto cnt = (e.kind() == EntityKind::EK_Gen) ? m_header.count : (uint16_t)1;
1232 pItem->func_dtor(pSrc, cnt);
1233 }
1234 };
1235
1236 //----------------------------------------------------------------------
1237 // Check component presence
1238 //----------------------------------------------------------------------
1239
1243 GAIA_NODISCARD bool has(Entity entity) const {
1244 auto ids = ids_view();
1245 return core::has(ids, entity);
1246 }
1247
1251 template <typename T>
1252 GAIA_NODISCARD bool has() const {
1253 if constexpr (is_pair<T>::value) {
1254 const auto rel = m_header.cc->get<typename T::rel>().entity;
1255 const auto tgt = m_header.cc->get<typename T::tgt>().entity;
1256 return has((Entity)Pair(rel, tgt));
1257 } else {
1258 const auto* pComp = m_header.cc->find<T>();
1259 return pComp != nullptr && has(pComp->entity);
1260 }
1261 }
1262
1263 //----------------------------------------------------------------------
1264 // Set component data
1265 //----------------------------------------------------------------------
1266
1271 template <typename T>
1272 decltype(auto) set(uint16_t row) {
1273 verify_comp<T>();
1274
1275 GAIA_ASSERT2(
1276 actual_type_t<T>::Kind == EntityKind::EK_Gen || row == 0,
1277 "Set providing a row can only be used with generic components");
1278
1279 // Update the world version
1280 ::gaia::ecs::update_version(m_header.worldVersion);
1281
1282 GAIA_ASSERT(row < m_header.capacity);
1283 return view_mut<T>()[row];
1284 }
1285
1290 template <typename T>
1291 decltype(auto) set(uint16_t row, Entity type) {
1292 verify_comp<T>();
1293
1294 GAIA_ASSERT2(
1295 type.kind() == EntityKind::EK_Gen || row == 0,
1296 "Set providing a row can only be used with generic components");
1297 GAIA_ASSERT(type.kind() == entity_kind_v<T>);
1298
1299 // Update the world version
1300 ::gaia::ecs::update_version(m_header.worldVersion);
1301
1302 GAIA_ASSERT(row < m_header.capacity);
1303
1304 // TODO: This function works but is useless because it does the same job as
1305 // set(uint16_t row, U&& value).
1306 // This is because T needs to match U anyway for the component lookup to succeed.
1307 (void)type;
1308 // const uint32_t col = comp_idx(type);
1309 //(void)col;
1310
1311 return view_mut<T>()[row];
1312 }
1313
1319 template <typename T>
1320 decltype(auto) sset(uint16_t row) {
1321 GAIA_ASSERT2(
1322 actual_type_t<T>::Kind == EntityKind::EK_Gen || row == 0,
1323 "Set providing a row can only be used with generic components");
1324
1325 GAIA_ASSERT(row < m_header.capacity);
1326 return sview_mut<T>()[row];
1327 }
1328
1335 template <typename T>
1336 decltype(auto) sset(uint16_t row, Entity type) {
1337 static_assert(core::is_raw_v<T>);
1338
1339 GAIA_ASSERT2(
1340 type.kind() == EntityKind::EK_Gen || row == 0,
1341 "Set providing a row can only be used with generic components");
1342
1343 GAIA_ASSERT(row < m_header.capacity);
1344
1345 // TODO: This function works but is useless because it does the same job as
1346 // sset(uint16_t row, U&& value).
1347 // This is because T needs to match U anyway for the component lookup to succeed.
1348 (void)type;
1349 // const uint32_t col = comp_idx(type);
1350 //(void)col;
1351
1352 return sview_mut<T>()[row];
1353 }
1354
1355 //----------------------------------------------------------------------
1356 // Read component data
1357 //----------------------------------------------------------------------
1358
1365 template <typename T>
1366 GAIA_NODISCARD decltype(auto) get(uint16_t row) const {
1367 static_assert(
1368 entity_kind_v<T> == EntityKind::EK_Gen, "Get providing a row can only be used with generic components");
1369
1370 return comp_inter<T>(row);
1371 }
1372
1377 template <typename T>
1378 GAIA_NODISCARD decltype(auto) get() const {
1379 static_assert(
1380 entity_kind_v<T> != EntityKind::EK_Gen,
1381 "Get not providing a row can only be used with non-generic components");
1382
1383 return comp_inter<T>(0);
1384 }
1385
1389 GAIA_NODISCARD uint32_t comp_idx(Entity entity) const {
1390 return ecs::comp_idx<ChunkHeader::MAX_COMPONENTS>(m_records.pCompEntities, entity);
1391 }
1392
1397 GAIA_NODISCARD uint32_t comp_idx(Entity entity, uint32_t offset) const {
1398 return ecs::comp_idx({m_records.pCompEntities + offset, m_header.count - offset}, entity);
1399 }
1400
1401 //----------------------------------------------------------------------
1402
1404 void set_idx(uint32_t value) {
1405 m_header.index = value;
1406 }
1407
1409 GAIA_NODISCARD uint32_t idx() const {
1410 return m_header.index;
1411 }
1412
1414 GAIA_NODISCARD bool has_enabled_entities() const {
1415 return m_header.has_enabled_entities();
1416 }
1417
1419 GAIA_NODISCARD bool has_disabled_entities() const {
1420 return m_header.has_disabled_entities();
1421 }
1422
1424 GAIA_NODISCARD bool dying() const {
1425 return m_header.lifespanCountdown > 0;
1426 }
1427
1429 void die() {
1430 m_header.dead = 1;
1431 }
1432
1434 GAIA_NODISCARD bool dead() const {
1435 return m_header.dead == 1;
1436 }
1437
1440 GAIA_ASSERT(!dead());
1441 m_header.lifespanCountdown = ChunkHeader::MAX_CHUNK_LIFESPAN;
1442 }
1443
1445 void revive() {
1446 GAIA_ASSERT(!dead());
1447 m_header.lifespanCountdown = 0;
1448 }
1449
1453 GAIA_ASSERT(dying());
1454 --m_header.lifespanCountdown;
1455 return dying();
1456 }
1457
1459 GAIA_NODISCARD bool full() const {
1460 return m_header.count >= m_header.capacity;
1461 }
1462
1464 GAIA_NODISCARD bool is_semi() const {
1465 // We want the chunk filled to at least 75% before considering it semi-full
1466 constexpr float Threshold = 0.75f;
1467 return ((float)m_header.count / (float)m_header.capacity) < Threshold;
1468 }
1469
1471 GAIA_NODISCARD uint16_t size() const {
1472 return m_header.count;
1473 }
1474
1476 GAIA_NODISCARD bool empty() const {
1477 return m_header.count == 0;
1478 }
1479
1481 GAIA_NODISCARD uint16_t size_enabled() const {
1482 return m_header.countEnabled;
1483 }
1484
1486 GAIA_NODISCARD uint16_t size_disabled() const {
1487 return (uint16_t)m_header.rowFirstEnabledEntity;
1488 }
1489
1491 GAIA_NODISCARD uint16_t capacity() const {
1492 return m_header.capacity;
1493 }
1494
1496 GAIA_NODISCARD uint8_t size_generic() const {
1497 return m_header.genEntities;
1498 }
1499
1503 GAIA_NODISCARD bool changed(uint32_t requiredVersion) const {
1504 const auto* versions = m_records.pVersions;
1505 const auto changeVersion = versions[0];
1506 return ::gaia::ecs::version_changed(changeVersion, requiredVersion);
1507 }
1508
1510 GAIA_NODISCARD bool changed(uint32_t requiredVersion, uint32_t compIdx) const {
1511 const auto* versions = m_records.pVersions;
1512 // Do +1 because index 0 is reserved for the entity version number.
1513 const auto changeVersion = versions[compIdx + 1];
1514 return ::gaia::ecs::version_changed(changeVersion, requiredVersion);
1515 }
1516
1518 GAIA_FORCEINLINE void update_world_version(uint32_t compIdx) {
1519 auto versions = comp_version_view_mut();
1520 // Automatically treat the entity as changed.
1521 versions[0] = m_header.worldVersion;
1522 // Do +1 because index 0 is reserved for the entity version number.
1523 versions[compIdx + 1] = m_header.worldVersion;
1524 }
1525
1527 GAIA_FORCEINLINE void update_world_version() {
1528 // Edit the version pointer directly. The first elements is always the entity version.
1529 // This area of memory is always present.
1530 auto* versions = m_records.pVersions;
1531 // We update the version of the entity only. If this one changes,
1532 // all other components are considered changed as well.
1533 versions[0] = m_header.worldVersion;
1534 }
1535
1537 GAIA_FORCEINLINE void update_world_version_init() {
1538 auto* versions = m_records.pVersions;
1539 // We update the version of the entity and all components to match the world version.
1540 versions[0] = m_header.worldVersion;
1541 GAIA_FOR(m_header.genEntities) versions[1 + i] = m_header.worldVersion;
1542 }
1543
1544 void diag() const {
1545 GAIA_LOG_N(
1546 " Chunk #%04u, entities:%u/%u, lifespanCountdown:%u", m_header.index, m_header.count, m_header.capacity,
1547 m_header.lifespanCountdown);
1548 }
1549 };
1550 } // namespace ecs
1551} // namespace gaia
Array of elements of type.
Definition sarray_ext_impl.h:27
Definition span_impl.h:99
Definition chunk.h:29
GAIA_NODISCARD decltype(auto) sview_auto(uint16_t from, uint16_t to)
Returns either a mutable or immutable entity/component view based on the requested type....
Definition chunk.h:692
GAIA_NODISCARD uint16_t capacity() const
Returns the number of entities in the chunk.
Definition chunk.h:1491
static void free(Chunk *pChunk)
Releases all memory allocated by pChunk.
Definition chunk.h:401
const uint8_t & data(uint32_t offset) const
Returns an immutable pointer to chunk data.
Definition chunk.h:1175
decltype(auto) sset(uint16_t row, Entity type)
Sets the value of a generic entity type at the position row in the chunk.
Definition chunk.h:1336
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_FORCEINLINE void update_world_version()
Update the version of all components.
Definition chunk.h:1527
GAIA_NODISCARD uint32_t comp_idx(Entity entity, uint32_t offset) const
Returns the internal index of a component based on the provided entity.
Definition chunk.h:1397
bool progress_death()
Updates internal lifespan.
Definition chunk.h:1452
decltype(auto) set(uint16_t row)
Sets the value of the unique component T on row in the chunk.
Definition chunk.h:1272
GAIA_NODISCARD GAIA_FORCEINLINE auto comp_ptr_mut_gen(uint32_t compIdx, uint32_t row)
Returns a read-write span of the component data. Also updates the world version for the component.
Definition chunk.h:297
void die()
Marks the chunk as dead (ready to delete)
Definition chunk.h:1429
GAIA_NODISCARD uint32_t idx() const
Returns the index of this chunk in its archetype's storage.
Definition chunk.h:1409
uint8_t & data(uint32_t offset)
Returns a mutable pointer to chunk data.
Definition chunk.h:1168
void update_versions()
Updates the version numbers for this chunk.
Definition chunk.h:507
GAIA_FORCEINLINE void update_world_version(uint32_t compIdx)
Update the version of a component at the index.
Definition chunk.h:1518
GAIA_NODISCARD bool has_disabled_entities() const
Checks is this chunk has any disabled entities.
Definition chunk.h:1419
GAIA_NODISCARD bool full() const
Checks is the full capacity of the has has been reached.
Definition chunk.h:1459
GAIA_NODISCARD bool is_semi() const
Checks is the chunk is semi-full.
Definition chunk.h:1464
GAIA_NODISCARD decltype(auto) view(uint16_t from, uint16_t to) const
Returns a read-only entity or component view.
Definition chunk.h:519
GAIA_NODISCARD uint16_t add_entity(Entity entity)
Make.
Definition chunk.h:739
GAIA_NODISCARD uint16_t size_enabled() const
Return the number of entities in the chunk which are enabled.
Definition chunk.h:1481
GAIA_NODISCARD decltype(auto) view_auto(uint16_t from, uint16_t to)
Returns either a mutable or immutable entity/component view based on the requested type....
Definition chunk.h:670
void start_dying()
Starts the process of dying (not yet ready to delete, can be revived)
Definition chunk.h:1439
GAIA_NODISCARD bool has() const
Checks if component T is present in the chunk.
Definition chunk.h:1252
GAIA_NODISCARD uint16_t size() const
Returns the total number of entities in the chunk (both enabled and disabled)
Definition chunk.h:1471
GAIA_NODISCARD decltype(auto) get(uint16_t row) const
Returns the value stored in the generic component T on row in the chunk.
Definition chunk.h:1366
static uintptr_t chunk_data_area_offset()
Returns the relative offset of m_data in Chunk.
Definition chunk.h:359
void move_entity_data(Entity entity, uint16_t row, EntityContainers &recs)
Moves all data associated with entity into the chunk so that it is stored at the row row.
Definition chunk.h:786
void set_idx(uint32_t value)
Sets the index of this chunk in its archetype's storage.
Definition chunk.h:1404
GAIA_NODISCARD decltype(auto) get() const
Returns the value stored in the unique component T.
Definition chunk.h:1378
GAIA_NODISCARD bool changed(uint32_t requiredVersion, uint32_t compIdx) const
Returns true if the provided version is newer than the one stored internally.
Definition chunk.h:1510
static void copy_entity_data(Entity srcEntity, Entity dstEntity, EntityContainers &recs)
Copies all data associated with srcEntity into dstEntity.
Definition chunk.h:755
bool enabled(uint16_t row) const
Checks if the entity is enabled.
Definition chunk.h:1159
GAIA_NODISCARD bool dying() const
Checks is this chunk is dying.
Definition chunk.h:1424
GAIA_NODISCARD bool changed(uint32_t requiredVersion) const
Returns true if the provided version is newer than the one stored internally. Use when checking if th...
Definition chunk.h:1503
void remove_last_entity()
Remove the last entity from a chunk. If as a result the chunk becomes empty it is scheduled for delet...
Definition chunk.h:494
GAIA_NODISCARD uint16_t size_disabled() const
Return the number of entities in the chunk which are enabled.
Definition chunk.h:1486
void swap_chunk_entities(uint16_t rowA, uint16_t rowB, EntityContainers &recs)
Tries to swap the entity at row rowA with the one at the row rowB. When swapping, all data associated...
Definition chunk.h:1035
static void move_foreign_entity_data(Chunk *pSrcChunk, uint32_t srcRow, Chunk *pDstChunk, uint32_t dstRow)
Moves all data associated with entity into the chunk so that it is stored at the row row.
Definition chunk.h:874
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
GAIA_NODISCARD uint32_t comp_idx(Entity entity) const
Returns the internal index of a component based on the provided entity.
Definition chunk.h:1389
static void swap_chunk_entities(World &world, Entity entityA, Entity entityB)
Tries to swap entityA with entityB. When swapping, all data associated with the two entities is swapp...
Definition chunk.h:1077
GAIA_FORCEINLINE void update_world_version_init()
Update the version of all components on chunk init.
Definition chunk.h:1537
GAIA_NODISCARD bool dead() const
Checks is this chunk is dead (ready to delete)
Definition chunk.h:1434
decltype(auto) set(uint16_t row, Entity type)
Sets the value of a generic entity type at the position row in the chunk.
Definition chunk.h:1291
GAIA_NODISCARD bool has_enabled_entities() const
Checks is this chunk has any enabled entities.
Definition chunk.h:1414
void revive()
Makes a dying chunk alive again.
Definition chunk.h:1445
GAIA_NODISCARD bool has(Entity entity) const
Checks if a component/entity entity is present in the chunk.
Definition chunk.h:1243
GAIA_NODISCARD bool empty() const
Checks is there are any entities in the chunk.
Definition chunk.h:1476
static void copy_foreign_entity_data(Chunk *pSrcChunk, uint32_t srcRow, Chunk *pDstChunk, uint32_t dstRow)
Copies all data associated with entity into the chunk so that it is stored at the row row.
Definition chunk.h:811
void remove_entity_inter(uint16_t row, EntityContainers &recs)
Tries to remove the entity at row. Removal is done via swapping with last entity in chunk....
Definition chunk.h:938
static Chunk * create(const World &wld, const ComponentCache &cc, uint32_t chunkIndex, uint16_t capacity, uint8_t cntEntities, uint8_t genEntities, uint16_t dataBytes, uint32_t &worldVersion, const ChunkDataOffsets &offsets, const Entity *ids, const Component *comps, const ChunkDataOffset *compOffs)
Allocates memory for a new chunk.
Definition chunk.h:370
GAIA_NODISCARD decltype(auto) view_mut(uint16_t from, uint16_t to)
Returns a mutable entity or component view.
Definition chunk.h:547
GAIA_NODISCARD decltype(auto) sview_mut(uint16_t from, uint16_t to)
Returns a mutable component view. Doesn't update the world version when the access is acquired.
Definition chunk.h:579
GAIA_NODISCARD uint8_t size_generic() const
Returns the total number of generic entities/components in the chunk.
Definition chunk.h:1496
decltype(auto) sset(uint16_t row)
Sets the value of the unique component T on row in the chunk.
Definition chunk.h:1320
GAIA_FORCEINLINE void modify()
Marks the component.
Definition chunk.h:613
Cache for compile-time defined components.
Definition component_cache.h:23
GAIA_NODISCARD const ComponentCacheItem * find(detail::ComponentDescId compDescId) const noexcept
Searches for the component cache item given the compDescId.
Definition component_cache.h:138
GAIA_NODISCARD const ComponentCacheItem & get(detail::ComponentDescId compDescId) const noexcept
Returns the component cache item given the compDescId.
Definition component_cache.h:156
Definition world.h:48
Wrapper for two Entities forming a relationship pair.
Definition id.h:395
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition utility.h:966
Definition chunk_header.h:19
ChunkDataVersionOffset firstByte_Versions
Byte at which the first version number is located.
Definition chunk_header.h:21
ChunkDataOffset firstByte_EntityData
Byte at which the first entity is located.
Definition chunk_header.h:27
ChunkDataOffset firstByte_Records
Byte at which the first component id is located.
Definition chunk_header.h:25
ChunkDataOffset firstByte_CompEntities
Byte at which the first entity id is located.
Definition chunk_header.h:23
Definition chunk_header.h:50
uint32_t & worldVersion
Version of the world (stable pointer to parent world's world version)
Definition chunk_header.h:99
const ComponentCache * cc
Component cache reference.
Definition chunk_header.h:67
uint32_t index
Chunk index in its archetype list.
Definition chunk_header.h:69
uint16_t capacity
Capacity (copied from the owner archetype).
Definition chunk_header.h:75
uint16_t hasAnyCustomGenCtor
True if there's any generic component that requires custom construction.
Definition chunk_header.h:80
uint16_t dead
True if deleted, false otherwise.
Definition chunk_header.h:90
uint16_t hasAnyCustomUniDtor
True if there's any unique component that requires custom destruction.
Definition chunk_header.h:86
uint16_t hasAnyCustomGenDtor
True if there's any generic component that requires custom destruction.
Definition chunk_header.h:84
uint8_t cntEntities
Number of components on the archetype.
Definition chunk_header.h:97
uint8_t genEntities
Number of generic entities/components.
Definition chunk_header.h:95
uint16_t lifespanCountdown
When it hits 0 the chunk is scheduled for deletion.
Definition chunk_header.h:88
uint16_t countEnabled
Number of enabled entities in the chunk.
Definition chunk_header.h:73
const World * world
Parent world.
Definition chunk_header.h:65
uint16_t rowFirstEnabledEntity
Index of the first enabled entity in the chunk.
Definition chunk_header.h:78
uint16_t count
Total number of entities in the chunk.
Definition chunk_header.h:71
uint16_t hasAnyCustomUniCtor
True if there's any unique component that requires custom construction.
Definition chunk_header.h:82
Definition chunk_header.h:39
ComponentRecord * pRecords
Pointer to the array of component records.
Definition chunk_header.h:45
Entity * pCompEntities
Pointer to where (component) entities are stored.
Definition chunk_header.h:43
Entity * pEntities
Pointer to the array of entities.
Definition chunk_header.h:47
ComponentVersion * pVersions
Pointer to where component versions are stored.
Definition chunk_header.h:41
Definition component_cache_item.h:24
Entity entity
Component entity.
Definition component_cache_item.h:45
FuncCtor * func_ctor
Function to call when the component needs to be constructed.
Definition component_cache_item.h:56
Definition chunk_header.h:30
uint8_t * pData
Pointer to where the first instance of the component is stored.
Definition chunk_header.h:34
Definition id.h:25
Definition entity_container.h:151
Definition id.h:225
Definition id.h:217
Definition data_layout_policy.h:90
Definition data_layout_policy.h:92
Definition ser_rt.h:13