Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
ilist.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cstddef>
5#include <cstdint>
6#include <type_traits>
7
8#include "gaia/cnt/bitset.h"
9#include "gaia/cnt/darray.h"
10#include "gaia/core/utility.h"
11#include "gaia/mem/mem_alloc.h"
12#include "gaia/mem/raw_data_holder.h"
13
14namespace gaia {
15 namespace cnt {
16 template <typename TListItem>
17 struct ilist_item_traits;
18
19 namespace detail {
20 template <typename T, typename = void>
21 struct ilist_has_idx_member: std::false_type {};
22 template <typename T>
23 struct ilist_has_idx_member<T, std::void_t<decltype(std::declval<T&>().idx)>>: std::true_type {};
24
25 template <typename T, typename = void>
26 struct ilist_has_gen_member: std::false_type {};
27 template <typename T>
28 struct ilist_has_gen_member<T, std::void_t<decltype(std::declval<T&>().gen)>>: std::true_type {};
29
30 template <typename T, typename = void>
31 struct ilist_has_data_gen_member: std::false_type {};
32 template <typename T>
33 struct ilist_has_data_gen_member<T, std::void_t<decltype(std::declval<T&>().data.gen)>>: std::true_type {};
34
35 template <typename T, typename = void>
36 struct ilist_has_id_mask: std::false_type {};
37 template <typename T>
38 struct ilist_has_id_mask<T, std::void_t<decltype(T::IdMask)>>: std::true_type {};
39
40 template <typename T, typename = void>
41 struct ilist_has_handle_id: std::false_type {};
42 template <typename T>
43 struct ilist_has_handle_id<T, std::void_t<decltype(std::declval<const T&>().id())>>: std::true_type {};
44
45 template <typename T, typename = void>
46 struct ilist_has_handle_gen: std::false_type {};
47 template <typename T>
48 struct ilist_has_handle_gen<T, std::void_t<decltype(std::declval<const T&>().gen())>>: std::true_type {};
49
50 template <typename T, typename = void>
51 struct ilist_has_create: std::false_type {};
52 template <typename T>
54 T, std::void_t<decltype(T::create(std::declval<uint32_t>(), std::declval<uint32_t>(), (void*)nullptr))>>:
55 std::true_type {};
56
57 template <typename T, typename THandle, typename = void>
58 struct ilist_has_handle: std::false_type {};
59 template <typename T, typename THandle>
60 struct ilist_has_handle<T, THandle, std::void_t<decltype(T::handle(std::declval<const T&>()))>>:
61 std::bool_constant<std::is_convertible_v<decltype(T::handle(std::declval<const T&>())), THandle>> {};
62
63 template <typename T, typename = void>
64 struct ilist_traits_has_idx: std::false_type {};
65 template <typename T>
66 struct ilist_traits_has_idx<T, std::void_t<decltype(ilist_item_traits<T>::idx(std::declval<const T&>()))>>:
67 std::true_type {};
68
69 template <typename T, typename = void>
70 struct ilist_traits_has_set_idx: std::false_type {};
71 template <typename T>
73 T, std::void_t<decltype(ilist_item_traits<T>::set_idx(std::declval<T&>(), std::declval<uint32_t>()))>>:
74 std::true_type {};
75
76 template <typename T, typename = void>
77 struct ilist_traits_has_gen: std::false_type {};
78 template <typename T>
79 struct ilist_traits_has_gen<T, std::void_t<decltype(ilist_item_traits<T>::gen(std::declval<const T&>()))>>:
80 std::true_type {};
81
82 template <typename T, typename = void>
83 struct ilist_traits_has_set_gen: std::false_type {};
84 template <typename T>
86 T, std::void_t<decltype(ilist_item_traits<T>::set_gen(std::declval<T&>(), std::declval<uint32_t>()))>>:
87 std::true_type {};
88 } // namespace detail
89
90 template <typename TListItem>
92 static_assert(
94 "ilist item type must expose idx or specialize ilist_item_traits");
95 static_assert(
97 "ilist item type must expose gen/data.gen or specialize ilist_item_traits");
98
99 GAIA_NODISCARD static uint32_t idx(const TListItem& item) noexcept {
100 return (uint32_t)item.idx;
101 }
102
103 static void set_idx(TListItem& item, uint32_t value) noexcept {
104 item.idx = value;
105 }
106
107 GAIA_NODISCARD static uint32_t gen(const TListItem& item) noexcept {
109 return (uint32_t)item.gen;
110 else
111 return (uint32_t)item.data.gen;
112 }
113
114 static void set_gen(TListItem& item, uint32_t value) noexcept {
116 item.gen = value;
117 else
118 item.data.gen = value;
119 }
120 };
121
122 struct ilist_item {
123 struct ItemData {
126 };
127
133
134 ilist_item() = default;
135 ilist_item(uint32_t index, uint32_t generation): idx(index) {
136 data.gen = generation;
137 }
138
139 ilist_item(const ilist_item& other) {
140 idx = other.idx;
141 data.gen = other.data.gen;
142 }
143 ilist_item& operator=(const ilist_item& other) {
144 GAIA_ASSERT(core::addressof(other) != this);
145 idx = other.idx;
146 data.gen = other.data.gen;
147 return *this;
148 }
149
150 ilist_item(ilist_item&& other) {
151 idx = other.idx;
152 data.gen = other.data.gen;
153
154 other.idx = (uint32_t)-1;
155 other.data.gen = (uint32_t)-1;
156 }
157 ilist_item& operator=(ilist_item&& other) {
158 GAIA_ASSERT(core::addressof(other) != this);
159 idx = other.idx;
160 data.gen = other.data.gen;
161
162 other.idx = (uint32_t)-1;
163 other.data.gen = (uint32_t)-1;
164 return *this;
165 }
166 };
167
168 template <typename TListItem>
169 struct darray_ilist_storage: public cnt::darray<TListItem> {
170 void add_item(TListItem&& container) {
171 this->push_back(GAIA_MOV(container));
172 }
173
174 void del_item([[maybe_unused]] TListItem& container) {}
175 };
176
183 template <typename TListItem, typename TItemHandle, typename TInternalStorage = darray_ilist_storage<TListItem>>
184 struct ilist {
186
187 using value_type = TListItem;
188 using reference = TListItem&;
189 using const_reference = const TListItem&;
190 using pointer = TListItem*;
191 using const_pointer = const TListItem*;
192 using difference_type = typename internal_storage::difference_type;
193 using size_type = typename internal_storage::size_type;
194
195 // TODO: replace this iterator with a real list iterator
196 using iterator = typename internal_storage::iterator;
197 using const_iterator = typename internal_storage::const_iterator;
198 using iterator_category = typename internal_storage::iterator_category;
199
200 static_assert(detail::ilist_traits_has_idx<TListItem>::value, "ilist_item_traits<T> must expose idx(const T&)");
201 static_assert(
202 detail::ilist_traits_has_set_idx<TListItem>::value, "ilist_item_traits<T> must expose set_idx(T&, uint32_t)");
203 static_assert(detail::ilist_traits_has_gen<TListItem>::value, "ilist_item_traits<T> must expose gen(const T&)");
204 static_assert(
205 detail::ilist_traits_has_set_gen<TListItem>::value, "ilist_item_traits<T> must expose set_gen(T&, uint32_t)");
206 static_assert(
208 "ilist item type must expose static create(index, generation, ctx)");
209 static_assert(
211 "ilist item type must expose static handle(const item) returning the handle type");
212 static_assert(detail::ilist_has_id_mask<TItemHandle>::value, "ilist handle type must expose IdMask");
213 static_assert(detail::ilist_has_handle_id<TItemHandle>::value, "ilist handle type must expose id()");
214 static_assert(detail::ilist_has_handle_gen<TItemHandle>::value, "ilist handle type must expose gen()");
218 size_type m_nextFreeIdx = (size_type)-1;
220 size_type m_freeItems = 0;
221
222 GAIA_NODISCARD pointer data() noexcept {
223 return reinterpret_cast<pointer>(m_items.data());
224 }
225
226 GAIA_NODISCARD const_pointer data() const noexcept {
227 return reinterpret_cast<const_pointer>(m_items.data());
228 }
229
230 GAIA_NODISCARD reference operator[](size_type index) {
231 return m_items[index];
232 }
233 GAIA_NODISCARD const_reference operator[](size_type index) const {
234 return m_items[index];
235 }
236
237 void clear() {
238 m_items.clear();
239 m_nextFreeIdx = (size_type)-1;
240 m_freeItems = 0;
241 }
242
243 GAIA_NODISCARD size_type get_next_free_item() const noexcept {
244 return m_nextFreeIdx;
245 }
246
247 GAIA_NODISCARD size_type get_free_items() const noexcept {
248 return m_freeItems;
249 }
250
251 GAIA_NODISCARD size_type item_count() const noexcept {
252 return size() - m_freeItems;
253 }
254
255 GAIA_NODISCARD size_type size() const noexcept {
256 return (size_type)m_items.size();
257 }
258
259 GAIA_NODISCARD bool empty() const noexcept {
260 return size() == 0;
261 }
262
263 GAIA_NODISCARD size_type capacity() const noexcept {
264 return (size_type)m_items.capacity();
265 }
266
267 GAIA_NODISCARD iterator begin() noexcept {
268 return m_items.begin();
269 }
270
271 GAIA_NODISCARD const_iterator begin() const noexcept {
272 return m_items.begin();
273 }
274
275 GAIA_NODISCARD const_iterator cbegin() const noexcept {
276 return m_items.begin();
277 }
278
279 GAIA_NODISCARD iterator end() noexcept {
280 return m_items.end();
281 }
282
283 GAIA_NODISCARD const_iterator end() const noexcept {
284 return m_items.end();
285 }
286
287 GAIA_NODISCARD const_iterator cend() const noexcept {
288 return m_items.end();
289 }
290
291 void reserve(size_type cap) {
292 m_items.reserve(cap);
293 }
294
297 GAIA_NODISCARD TItemHandle alloc(void* ctx) {
298 if GAIA_UNLIKELY (m_freeItems == 0U) {
299 // We don't want to go out of range for new item
300 const auto itemCnt = (size_type)m_items.size();
301 GAIA_ASSERT(itemCnt < TItemHandle::IdMask && "Trying to allocate too many items!");
302
303 GAIA_GCC_WARNING_PUSH()
304 GAIA_CLANG_WARNING_PUSH()
305 GAIA_GCC_WARNING_DISABLE("-Wstringop-overflow");
306 GAIA_GCC_WARNING_DISABLE("-Wmissing-field-initializers");
307 GAIA_CLANG_WARNING_DISABLE("-Wmissing-field-initializers");
308 m_items.add_item(TListItem::create(itemCnt, 0U, ctx));
309 return TListItem::handle(m_items.back());
310 GAIA_GCC_WARNING_POP()
311 GAIA_CLANG_WARNING_POP()
312 }
313
314 // Make sure the list is not broken
315 GAIA_ASSERT(m_nextFreeIdx < (size_type)m_items.size() && "Item recycle list broken!");
316
317 --m_freeItems;
318 const auto index = m_nextFreeIdx;
319 auto& j = m_items[m_nextFreeIdx];
321 j = TListItem::create(index, ilist_item_traits<TListItem>::gen(j), ctx);
322 return TListItem::handle(j);
323 }
324
327 GAIA_NODISCARD TItemHandle alloc() {
328 if GAIA_UNLIKELY (m_freeItems == 0U) {
329 // We don't want to go out of range for new item
330 const auto itemCnt = (size_type)m_items.size();
331 GAIA_ASSERT(itemCnt < TItemHandle::IdMask && "Trying to allocate too many items!");
332
333 GAIA_GCC_WARNING_PUSH()
334 GAIA_CLANG_WARNING_PUSH()
335 GAIA_GCC_WARNING_DISABLE("-Wstringop-overflow");
336 GAIA_GCC_WARNING_DISABLE("-Wmissing-field-initializers");
337 GAIA_CLANG_WARNING_DISABLE("-Wmissing-field-initializers");
338 m_items.add_item(TListItem(itemCnt, 0U));
339 return {itemCnt, 0U};
340 GAIA_GCC_WARNING_POP()
341 GAIA_CLANG_WARNING_POP()
342 }
343
344 // Make sure the list is not broken
345 GAIA_ASSERT(m_nextFreeIdx < (size_type)m_items.size() && "Item recycle list broken!");
346
347 --m_freeItems;
348 const auto index = m_nextFreeIdx;
349 auto& j = m_items[m_nextFreeIdx];
351 return {index, ilist_item_traits<TListItem>::gen(m_items[index])};
352 }
353
358 auto& item = m_items[handle.id()];
359 m_items.del_item(item);
360
361 // Update our implicit list
362 if GAIA_UNLIKELY (m_freeItems == 0)
363 ilist_item_traits<TListItem>::set_idx(item, TItemHandle::IdMask);
364 else
367
368 m_nextFreeIdx = handle.id();
369 ++m_freeItems;
370
371 return item;
372 }
373
375 void validate() const {
378 return;
379
380 // If there's something to remove there has to be at least one entity left
381 GAIA_ASSERT(!m_items.empty());
382
383 auto freeItems = m_freeItems;
385 while (freeItems > 0) {
386 GAIA_ASSERT(nextFreeItem < m_items.size() && "Item recycle list broken!");
387
389 --freeItems;
390 }
391
392 // At this point the index of the last item in list should
393 // point to -1 because that's the tail of our implicit list.
394 GAIA_ASSERT(nextFreeItem == TItemHandle::IdMask);
395 }
396 };
397
398 template <typename TItemHandle, typename = void>
405 static TItemHandle make(uint32_t id, uint32_t gen, const TItemHandle& prev) {
406 (void)prev;
407 return TItemHandle(id, gen);
408 }
409 };
410
411 template <typename TItemHandle>
412 struct ilist_handle_traits<TItemHandle, std::void_t<decltype(std::declval<const TItemHandle&>().prio())>> {
418 static TItemHandle make(uint32_t id, uint32_t gen, const TItemHandle& prev) {
419 return TItemHandle(id, gen, prev.prio());
420 }
421 };
422
429 template <typename TListItem, typename TItemHandle, uint32_t MaxPages = 0>
430 struct paged_ilist;
431
436 template <typename TPagedIList, bool IsConst>
438 using owner_type = std::conditional_t<IsConst, const TPagedIList, TPagedIList>;
439 using owner_pointer = owner_type*;
440
441 owner_pointer m_pOwner = nullptr;
442 typename TPagedIList::size_type m_index = 0;
443
444 void skip_dead() {
445 while (m_pOwner != nullptr && m_index < m_pOwner->size() && !m_pOwner->has(m_index))
446 ++m_index;
447 }
448
449 public:
450 using value_type = typename TPagedIList::value_type;
451 using reference =
452 std::conditional_t<IsConst, typename TPagedIList::const_reference, typename TPagedIList::reference>;
453 using pointer = std::conditional_t<IsConst, typename TPagedIList::const_pointer, typename TPagedIList::pointer>;
454 using difference_type = typename TPagedIList::difference_type;
456
457 paged_ilist_iterator() = default;
458 paged_ilist_iterator(owner_pointer pOwner, typename TPagedIList::size_type index):
459 m_pOwner(pOwner), m_index(index) {
460 skip_dead();
461 }
462
463 GAIA_NODISCARD reference operator*() const {
464 return (*m_pOwner)[m_index];
465 }
466
467 GAIA_NODISCARD pointer operator->() const {
468 return &(*m_pOwner)[m_index];
469 }
470
471 paged_ilist_iterator& operator++() {
472 ++m_index;
473 skip_dead();
474 return *this;
475 }
476
477 paged_ilist_iterator operator++(int) {
478 auto tmp = *this;
479 ++(*this);
480 return tmp;
481 }
482
483 GAIA_NODISCARD bool operator==(const paged_ilist_iterator& other) const {
484 return m_pOwner == other.m_pOwner && m_index == other.m_index;
485 }
486
487 GAIA_NODISCARD bool operator!=(const paged_ilist_iterator& other) const {
488 return !(*this == other);
489 }
490 };
491
501 template <typename TListItem, typename TItemHandle, uint32_t MaxPages>
502 struct paged_ilist {
503 using value_type = TListItem;
504 using reference = TListItem&;
505 using const_reference = const TListItem&;
506 using pointer = TListItem*;
507 using const_pointer = const TListItem*;
508 using difference_type = std::ptrdiff_t;
509 using size_type = uint32_t;
511
512 static_assert(detail::ilist_traits_has_idx<TListItem>::value, "ilist_item_traits<T> must expose idx(const T&)");
513 static_assert(
514 detail::ilist_traits_has_set_idx<TListItem>::value, "ilist_item_traits<T> must expose set_idx(T&, uint32_t)");
515 static_assert(detail::ilist_traits_has_gen<TListItem>::value, "ilist_item_traits<T> must expose gen(const T&)");
516 static_assert(
517 detail::ilist_traits_has_set_gen<TListItem>::value, "ilist_item_traits<T> must expose set_gen(T&, uint32_t)");
518 static_assert(
520 "paged_ilist item type must expose static create(index, generation, ctx)");
521 static_assert(
523 "paged_ilist item type must expose static handle(const item) returning the handle type");
524 static_assert(detail::ilist_has_id_mask<TItemHandle>::value, "paged_ilist handle type must expose IdMask");
525 static_assert(detail::ilist_has_handle_id<TItemHandle>::value, "paged_ilist handle type must expose id()");
526 static_assert(detail::ilist_has_handle_gen<TItemHandle>::value, "paged_ilist handle type must expose gen()");
527
528 private:
529 static constexpr size_type target_payload_bytes() noexcept {
530 return 16384;
531 }
532
533 static constexpr size_type min_page_capacity() noexcept {
534 return 8;
535 }
536
537 static constexpr size_type max_page_capacity() noexcept {
538 return 256;
539 }
540
541 static constexpr size_type calc_page_capacity() noexcept {
542 const size_type payloadItemBytes = sizeof(TListItem) == 0 ? 1U : (size_type)sizeof(TListItem);
543 const size_type desired = target_payload_bytes() / payloadItemBytes;
544 if (desired < min_page_capacity())
545 return min_page_capacity();
546 if (desired > max_page_capacity())
547 return max_page_capacity();
548 return desired;
549 }
550
551 static constexpr size_type PageCapacity = calc_page_capacity();
552 static constexpr bool FixedPageTable = MaxPages != 0;
553 static constexpr size_type StaticPageCount = MaxPages == 0 ? 1U : MaxPages;
554
555 struct page_type {
556 using alive_mask_type = cnt::bitset<PageCapacity>;
557 using storage_type = mem::raw_data_holder<TListItem, sizeof(TListItem) * PageCapacity>;
558
559 alive_mask_type aliveMask;
560 TItemHandle handles[PageCapacity]{};
561 uint32_t nextFree[PageCapacity];
562 uint32_t liveCount = 0;
563 storage_type* pStorage = nullptr;
564
565 page_type() {
566 GAIA_FOR(PageCapacity)
567 nextFree[i] = TItemHandle::IdMask;
568 }
569
570 ~page_type() {
571 clear();
572 }
573
574 page_type(const page_type&) = delete;
575 page_type& operator=(const page_type&) = delete;
576
577 page_type(page_type&& other) noexcept:
578 aliveMask(other.aliveMask), liveCount(other.liveCount), pStorage(other.pStorage) {
579 GAIA_FOR(PageCapacity) {
580 handles[i] = other.handles[i];
581 nextFree[i] = other.nextFree[i];
582 }
583
584 other.aliveMask.reset();
585 other.liveCount = 0;
586 other.pStorage = nullptr;
587 }
588
589 page_type& operator=(page_type&& other) noexcept {
590 GAIA_ASSERT(core::addressof(other) != this);
591
592 clear();
593 aliveMask = other.aliveMask;
594 liveCount = other.liveCount;
595 pStorage = other.pStorage;
596 GAIA_FOR(PageCapacity) {
597 handles[i] = other.handles[i];
598 nextFree[i] = other.nextFree[i];
599 }
600
601 other.aliveMask.reset();
602 other.liveCount = 0;
603 other.pStorage = nullptr;
604 return *this;
605 }
606
607 GAIA_NODISCARD pointer data() noexcept {
608 return pStorage == nullptr ? nullptr : reinterpret_cast<pointer>((uint8_t*)*pStorage);
609 }
610
611 GAIA_NODISCARD const_pointer data() const noexcept {
612 return pStorage == nullptr ? nullptr : reinterpret_cast<const_pointer>((const uint8_t*)*pStorage);
613 }
614
615 GAIA_NODISCARD pointer ptr(size_type slot) noexcept {
616 GAIA_ASSERT(slot < PageCapacity);
617 GAIA_ASSERT(pStorage != nullptr);
618 return data() + slot;
619 }
620
621 GAIA_NODISCARD const_pointer ptr(size_type slot) const noexcept {
622 GAIA_ASSERT(slot < PageCapacity);
623 GAIA_ASSERT(pStorage != nullptr);
624 return data() + slot;
625 }
626
627 void ensure_storage() {
628 if GAIA_LIKELY (pStorage != nullptr)
629 return;
630
631 constexpr auto StorageAlignment =
632 alignof(storage_type) < sizeof(void*) ? sizeof(void*) : alignof(storage_type);
633 pStorage = mem::AllocHelper::alloc_alig<storage_type>("PagedIListPage", StorageAlignment);
634 }
635
636 void maybe_release_storage() {
637 if (liveCount != 0 || pStorage == nullptr)
638 return;
639
640 mem::AllocHelper::free_alig("PagedIListPage", pStorage);
641 pStorage = nullptr;
642 }
643
644 void clear() {
645 if (pStorage != nullptr) {
646 for (auto slot: aliveMask)
647 core::call_dtor(ptr(slot));
648 mem::AllocHelper::free_alig("PagedIListPage", pStorage);
649 pStorage = nullptr;
650 }
651
652 aliveMask.reset();
653 liveCount = 0;
654 GAIA_FOR(PageCapacity)
655 nextFree[i] = TItemHandle::IdMask;
656 }
657
658 template <typename... Args>
659 void construct(size_type slot, Args&&... args) {
660 GAIA_ASSERT(slot < PageCapacity);
661 ensure_storage();
662 core::call_ctor(ptr(slot), GAIA_FWD(args)...);
663 aliveMask.set(slot);
664 ++liveCount;
665 }
666
667 void destroy(size_type slot) {
668 GAIA_ASSERT(slot < PageCapacity);
669 GAIA_ASSERT(aliveMask.test(slot));
670 core::call_dtor(ptr(slot));
671 aliveMask.reset(slot);
672 GAIA_ASSERT(liveCount > 0);
673 --liveCount;
674 maybe_release_storage();
675 }
676 };
677
682 page_type* m_staticPages[StaticPageCount]{};
683 size_type m_size = 0;
684
685 public:
687 size_type m_nextFreeIdx = (size_type)-1;
689 size_type m_freeItems = 0;
690
691 private:
692 GAIA_NODISCARD static constexpr size_type page_index(size_type index) noexcept {
693 return index / PageCapacity;
694 }
695
696 GAIA_NODISCARD static constexpr size_type slot_index(size_type index) noexcept {
697 return index % PageCapacity;
698 }
699
700 GAIA_NODISCARD static constexpr size_type page_count_for_slots(size_type slotCnt) noexcept {
701 return slotCnt == 0 ? 0U : (slotCnt + PageCapacity - 1) / PageCapacity;
702 }
703
704 public:
707 GAIA_NODISCARD static constexpr size_type page_capacity() noexcept {
708 return PageCapacity;
709 }
710
714 GAIA_NODISCARD static constexpr size_type page_count_for_capacity(size_type slotCnt) noexcept {
715 return page_count_for_slots(slotCnt);
716 }
717
718 private:
719 GAIA_NODISCARD size_type page_table_size() const noexcept {
720 if constexpr (FixedPageTable)
721 return MaxPages;
722 else
723 return (size_type)m_pages.size();
724 }
725
726 GAIA_NODISCARD page_type*& page_slot(size_type pageIdx) noexcept {
727 if constexpr (FixedPageTable) {
728 GAIA_ASSERT(pageIdx < MaxPages);
729 return m_staticPages[pageIdx];
730 } else {
731 GAIA_ASSERT(pageIdx < (size_type)m_pages.size());
732 return m_pages[pageIdx];
733 }
734 }
735
736 GAIA_NODISCARD const page_type* page_slot(size_type pageIdx) const noexcept {
737 if constexpr (FixedPageTable) {
738 GAIA_ASSERT(pageIdx < MaxPages);
739 return m_staticPages[pageIdx];
740 } else {
741 GAIA_ASSERT(pageIdx < (size_type)m_pages.size());
742 return m_pages[pageIdx];
743 }
744 }
745
746 void clear_pages() {
747 const auto pageCnt = page_table_size();
748 GAIA_FOR(pageCnt) {
749 auto*& pPage = page_slot(i);
750 if (pPage == nullptr)
751 continue;
752 delete pPage;
753 pPage = nullptr;
754 }
755
756 if constexpr (!FixedPageTable)
757 m_pages.clear();
758 }
759
760 void ensure_page_count(size_type slotCnt) {
761 const auto pageCnt = page_count_for_slots(slotCnt);
762 if constexpr (FixedPageTable) {
763 GAIA_ASSERT(pageCnt <= MaxPages && "Trying to allocate too many paged_ilist pages!");
764 } else {
765 if (pageCnt > (size_type)m_pages.size())
766 m_pages.resize(pageCnt, nullptr);
767 }
768 }
769
770 GAIA_NODISCARD page_type& ensure_page(size_type index) {
771 ensure_page_count(index + 1);
772 auto*& pPage = page_slot(page_index(index));
773 if (pPage == nullptr)
774 pPage = new page_type();
775 return *pPage;
776 }
777
778 GAIA_NODISCARD page_type* try_page(size_type index) noexcept {
779 const auto pageIdx = page_index(index);
780 if (pageIdx >= page_table_size())
781 return nullptr;
782 return page_slot(pageIdx);
783 }
784
785 GAIA_NODISCARD const page_type* try_page(size_type index) const noexcept {
786 const auto pageIdx = page_index(index);
787 if (pageIdx >= page_table_size())
788 return nullptr;
789 return page_slot(pageIdx);
790 }
791
792 GAIA_NODISCARD reference slot_ref(size_type index) {
793 auto* pPage = try_page(index);
794 GAIA_ASSERT(pPage != nullptr);
795 return *pPage->ptr(slot_index(index));
796 }
797
798 GAIA_NODISCARD const_reference slot_ref(size_type index) const {
799 auto* pPage = try_page(index);
800 GAIA_ASSERT(pPage != nullptr);
801 return *pPage->ptr(slot_index(index));
802 }
803
804 public:
805 using iterator = paged_ilist_iterator<paged_ilist, false>;
806 using const_iterator = paged_ilist_iterator<paged_ilist, true>;
807
808 ~paged_ilist() {
809 clear_pages();
810 }
811
812 paged_ilist() = default;
813 paged_ilist(const paged_ilist&) = delete;
814 paged_ilist& operator=(const paged_ilist&) = delete;
815 paged_ilist(paged_ilist&& other) noexcept:
816 m_size(other.m_size), m_nextFreeIdx(other.m_nextFreeIdx), m_freeItems(other.m_freeItems) {
817 if constexpr (FixedPageTable) {
818 GAIA_FOR(MaxPages) {
819 m_staticPages[i] = other.m_staticPages[i];
820 other.m_staticPages[i] = nullptr;
821 }
822 } else {
823 m_pages = GAIA_MOV(other.m_pages);
824 }
825
826 other.m_size = 0;
827 other.m_nextFreeIdx = (size_type)-1;
828 other.m_freeItems = 0;
829 }
830 paged_ilist& operator=(paged_ilist&& other) noexcept {
831 GAIA_ASSERT(core::addressof(other) != this);
832 clear_pages();
833
834 if constexpr (FixedPageTable) {
835 GAIA_FOR(MaxPages) {
836 m_staticPages[i] = other.m_staticPages[i];
837 other.m_staticPages[i] = nullptr;
838 }
839 } else {
840 m_pages = GAIA_MOV(other.m_pages);
841 }
842
843 m_size = other.m_size;
844 m_nextFreeIdx = other.m_nextFreeIdx;
845 m_freeItems = other.m_freeItems;
846
847 other.m_size = 0;
848 other.m_nextFreeIdx = (size_type)-1;
849 other.m_freeItems = 0;
850 return *this;
851 }
852
853 GAIA_NODISCARD pointer data() noexcept {
854 return nullptr;
855 }
856
857 GAIA_NODISCARD const_pointer data() const noexcept {
858 return nullptr;
859 }
860
861 GAIA_NODISCARD bool has(size_type index) const noexcept {
862 if (index >= m_size)
863 return false;
864
865 const auto* pPage = try_page(index);
866 return pPage != nullptr && pPage->aliveMask.test(slot_index(index));
867 }
868
869 GAIA_NODISCARD bool has(TItemHandle handle) const noexcept {
870 return has(handle.id()) && this->handle(handle.id()) == handle;
871 }
872
873 GAIA_NODISCARD TItemHandle handle(size_type index) const noexcept {
874 GAIA_ASSERT(index < m_size);
875 const auto* pPage = try_page(index);
876 GAIA_ASSERT(pPage != nullptr);
877 return pPage->handles[slot_index(index)];
878 }
879
880 GAIA_NODISCARD uint32_t generation(size_type index) const noexcept {
881 return handle(index).gen();
882 }
883
884 GAIA_NODISCARD uint32_t next_free(size_type index) const noexcept {
885 GAIA_ASSERT(index < m_size);
886 const auto* pPage = try_page(index);
887 GAIA_ASSERT(pPage != nullptr);
888 return pPage->nextFree[slot_index(index)];
889 }
890
891 GAIA_NODISCARD reference operator[](size_type index) {
892 GAIA_ASSERT(has(index));
893 return slot_ref(index);
894 }
895
896 GAIA_NODISCARD const_reference operator[](size_type index) const {
897 GAIA_ASSERT(has(index));
898 return slot_ref(index);
899 }
900
901 void clear() {
902 clear_pages();
903 m_size = 0;
904 m_nextFreeIdx = (size_type)-1;
905 m_freeItems = 0;
906 }
907
908 GAIA_NODISCARD size_type get_next_free_item() const noexcept {
909 return m_nextFreeIdx;
910 }
911
912 GAIA_NODISCARD size_type get_free_items() const noexcept {
913 return m_freeItems;
914 }
915
916 GAIA_NODISCARD size_type item_count() const noexcept {
917 return m_size - m_freeItems;
918 }
919
920 GAIA_NODISCARD size_type size() const noexcept {
921 return m_size;
922 }
923
924 GAIA_NODISCARD bool empty() const noexcept {
925 return m_size == 0;
926 }
927
928 GAIA_NODISCARD size_type capacity() const noexcept {
929 if constexpr (FixedPageTable)
930 return MaxPages * PageCapacity;
931 else
932 return (size_type)m_pages.capacity() * PageCapacity;
933 }
934
936 GAIA_NODISCARD iterator begin() noexcept {
937 return iterator(this, 0);
938 }
939
940 GAIA_NODISCARD const_iterator begin() const noexcept {
941 return const_iterator(this, 0);
942 }
943
944 GAIA_NODISCARD const_iterator cbegin() const noexcept {
945 return const_iterator(this, 0);
946 }
947
948 GAIA_NODISCARD iterator end() noexcept {
949 return iterator(this, m_size);
950 }
951
952 GAIA_NODISCARD const_iterator end() const noexcept {
953 return const_iterator(this, m_size);
954 }
955
956 GAIA_NODISCARD const_iterator cend() const noexcept {
957 return const_iterator(this, m_size);
958 }
959
964 void reserve(size_type cap) {
965 const auto pageCnt = page_count_for_slots(cap);
966 if constexpr (FixedPageTable) {
967 GAIA_ASSERT(pageCnt <= MaxPages);
968 } else {
969 m_pages.reserve(pageCnt);
970 }
971 }
972
978 void reserve_slot_table(size_type cap) {
979 const auto pageCnt = page_count_for_slots(cap);
980 if constexpr (FixedPageTable) {
981 GAIA_ASSERT(pageCnt <= MaxPages);
982 } else {
983 ensure_page_count(cap);
984 }
985 }
986
992 GAIA_NODISCARD reference live_unsafe(size_type index) {
993 auto* pPage = try_page(index);
994 GAIA_ASSERT(pPage != nullptr);
995 const auto slot = slot_index(index);
996 GAIA_ASSERT(pPage->aliveMask.test(slot));
997 return *pPage->ptr(slot);
998 }
999
1005 GAIA_NODISCARD const_reference live_unsafe(size_type index) const {
1006 const auto* pPage = try_page(index);
1007 GAIA_ASSERT(pPage != nullptr);
1008 const auto slot = slot_index(index);
1009 GAIA_ASSERT(pPage->aliveMask.test(slot));
1010 return *pPage->ptr(slot);
1011 }
1012
1013 GAIA_NODISCARD pointer try_get(size_type index) noexcept {
1014 if (!has(index))
1015 return nullptr;
1016 return &slot_ref(index);
1017 }
1018
1019 GAIA_NODISCARD const_pointer try_get(size_type index) const noexcept {
1020 if (!has(index))
1021 return nullptr;
1022 return &slot_ref(index);
1023 }
1024
1029 void add_live(TListItem&& item) {
1030 const auto index = (size_type)ilist_item_traits<TListItem>::idx(item);
1031 auto& page = ensure_page(index);
1032 const auto slot = slot_index(index);
1033 const bool existed = index < m_size;
1034 const bool wasAlive = existed && page.aliveMask.test(slot);
1035 const bool wasFree = existed && !wasAlive;
1036
1037 if (index >= m_size)
1038 m_size = index + 1;
1039 else if (wasAlive)
1040 page.destroy(slot);
1041 else if (wasFree && m_freeItems > 0)
1042 --m_freeItems;
1043
1044 page.construct(slot, GAIA_MOV(item));
1045 page.handles[slot] = TListItem::handle(*page.ptr(slot));
1046 page.nextFree[slot] = TItemHandle::IdMask;
1047 }
1048
1053 const auto index = (size_type)handle.id();
1054 auto& page = ensure_page(index);
1055 const auto slot = slot_index(index);
1056 const bool existed = index < m_size;
1057 const bool wasAlive = existed && page.aliveMask.test(slot);
1058 const bool wasFree = existed && !wasAlive;
1059
1060 if (index >= m_size)
1061 m_size = index + 1;
1062 else if (wasAlive)
1063 page.destroy(slot);
1064
1065 page.handles[slot] = handle;
1066 page.nextFree[slot] = nextFreeIdx;
1067 if (!wasFree)
1068 ++m_freeItems;
1069 if (m_nextFreeIdx == (size_type)-1)
1070 m_nextFreeIdx = index;
1071 }
1072
1077 void add_free(size_type index, uint32_t generation, uint32_t nextFreeIdx) {
1079 }
1080
1085 GAIA_NODISCARD TItemHandle alloc(void* ctx) {
1086 size_type index = 0;
1087 uint32_t generation = 0;
1088
1089 if GAIA_UNLIKELY (m_freeItems == 0U) {
1090 index = m_size;
1091 GAIA_ASSERT(index < TItemHandle::IdMask && "Trying to allocate too many items!");
1092 ++m_size;
1093 } else {
1094 GAIA_ASSERT(m_nextFreeIdx < m_size && "Item recycle list broken!");
1095 index = m_nextFreeIdx;
1096 auto& page = ensure_page(index);
1097 const auto slot = slot_index(index);
1098 m_nextFreeIdx = page.nextFree[slot];
1099 page.nextFree[slot] = TItemHandle::IdMask;
1100 generation = page.handles[slot].gen();
1101 --m_freeItems;
1102 if (page.aliveMask.test(slot))
1103 page.destroy(slot);
1104 }
1105
1106 auto& page = ensure_page(index);
1107 const auto slot = slot_index(index);
1108 page.construct(slot, TListItem::create(index, generation, ctx));
1109 page.handles[slot] = TListItem::handle(*page.ptr(slot));
1110 page.nextFree[slot] = TItemHandle::IdMask;
1111 return page.handles[slot];
1112 }
1113
1117 GAIA_NODISCARD TItemHandle alloc() {
1118 size_type index = 0;
1119 uint32_t generation = 0;
1120
1121 if GAIA_UNLIKELY (m_freeItems == 0U) {
1122 index = m_size;
1123 GAIA_ASSERT(index < TItemHandle::IdMask && "Trying to allocate too many items!");
1124 ++m_size;
1125 } else {
1126 GAIA_ASSERT(m_nextFreeIdx < m_size && "Item recycle list broken!");
1127 index = m_nextFreeIdx;
1128 auto& page = ensure_page(index);
1129 const auto slot = slot_index(index);
1130 m_nextFreeIdx = page.nextFree[slot];
1131 page.nextFree[slot] = TItemHandle::IdMask;
1132 generation = page.handles[slot].gen();
1133 --m_freeItems;
1134 if (page.aliveMask.test(slot))
1135 page.destroy(slot);
1136 }
1137
1138 auto& page = ensure_page(index);
1139 const auto slot = slot_index(index);
1140 page.construct(slot, TListItem(index, generation));
1141 page.handles[slot] = ilist_handle_traits<TItemHandle>::make(index, generation, TItemHandle{});
1142 page.nextFree[slot] = TItemHandle::IdMask;
1143 return page.handles[slot];
1144 }
1145
1149 void free(TItemHandle handle) {
1150 GAIA_ASSERT(has(handle));
1151
1152 auto& page = ensure_page(handle.id());
1153 const auto slot = slot_index(handle.id());
1154 page.destroy(slot);
1155 page.handles[slot] = ilist_handle_traits<TItemHandle>::make(handle.id(), handle.gen() + 1, page.handles[slot]);
1156 page.nextFree[slot] = m_freeItems == 0 ? TItemHandle::IdMask : m_nextFreeIdx;
1157 m_nextFreeIdx = handle.id();
1158 ++m_freeItems;
1159 }
1160
1170 GAIA_ASSERT(has(handle));
1171
1172 auto& page = ensure_page(handle.id());
1173 const auto slot = slot_index(handle.id());
1174 page.handles[slot] = ilist_handle_traits<TItemHandle>::make(handle.id(), handle.gen() + 1, page.handles[slot]);
1175 page.nextFree[slot] = m_freeItems == 0 ? TItemHandle::IdMask : m_nextFreeIdx;
1176 m_nextFreeIdx = handle.id();
1177 ++m_freeItems;
1178 }
1179
1181 void validate() const {
1182 if (m_freeItems == 0)
1183 return;
1184
1185 auto freeItems = m_freeItems;
1187 while (freeItems > 0) {
1188 GAIA_ASSERT(nextFreeItem < m_size && "Item recycle list broken!");
1189
1190 nextFreeItem = next_free(nextFreeItem);
1191 --freeItems;
1192 }
1193
1194 GAIA_ASSERT(nextFreeItem == TItemHandle::IdMask);
1195 }
1196 };
1197 } // namespace cnt
1198} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
Forward iterator used by paged_ilist. Kept outside the container template so the container body stays...
Definition ilist.h:437
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition ilist.h:169
static TItemHandle make(uint32_t id, uint32_t gen, const TItemHandle &prev)
Rebuilds a handle after its generation changes while preserving packed priority bits.
Definition ilist.h:418
Definition ilist.h:399
static TItemHandle make(uint32_t id, uint32_t gen, const TItemHandle &prev)
Rebuilds a handle after its generation changes.
Definition ilist.h:405
Definition ilist.h:123
uint32_t gen
Generation ID.
Definition ilist.h:125
Definition ilist.h:91
Definition ilist.h:122
uint32_t idx
Allocated items: Index in the list. Deleted items: Index of the next deleted item in the list.
Definition ilist.h:130
ItemData data
Item data.
Definition ilist.h:132
Implicit list. Rather than with pointers, items.
Definition ilist.h:184
TListItem & free(TItemHandle handle)
Invalidates handle. Every time an item is deallocated its generation is increased by one.
Definition ilist.h:357
size_type m_nextFreeIdx
Index of the next item to recycle.
Definition ilist.h:218
size_type m_freeItems
Number of items to recycle.
Definition ilist.h:220
void validate() const
Verifies that the implicit linked list is valid.
Definition ilist.h:375
GAIA_NODISCARD TItemHandle alloc()
Allocates a new item in the list.
Definition ilist.h:327
internal_storage m_items
Implicit list items.
Definition ilist.h:216
GAIA_NODISCARD TItemHandle alloc(void *ctx)
Allocates a new item in the list.
Definition ilist.h:297
Paged implicit list declaration.
Definition ilist.h:502
static GAIA_NODISCARD constexpr size_type page_capacity() noexcept
Returns the compile-time number of payload slots stored in one page.
Definition ilist.h:707
GAIA_NODISCARD reference live_unsafe(size_type index)
Returns a live payload slot without consulting list-wide size metadata.
Definition ilist.h:992
static GAIA_NODISCARD constexpr size_type page_count_for_capacity(size_type slotCnt) noexcept
Calculates how many pages are needed to address slotCnt slots.
Definition ilist.h:714
void add_free(TItemHandle handle, uint32_t nextFreeIdx)
Restores a free slot with a preassigned id/generation and free-list link.
Definition ilist.h:1052
size_type m_freeItems
Number of slots currently linked through the implicit free-list.
Definition ilist.h:689
GAIA_NODISCARD TItemHandle alloc()
Allocates a new item in the list.
Definition ilist.h:1117
void validate() const
Verifies that the implicit free-list links are well formed.
Definition ilist.h:1181
size_type m_nextFreeIdx
Head of the implicit free-list, or TItemHandle::IdMask when no slots are free.
Definition ilist.h:687
GAIA_NODISCARD TItemHandle alloc(void *ctx)
Allocates a new item in the list.
Definition ilist.h:1085
GAIA_NODISCARD const_reference live_unsafe(size_type index) const
Returns a live payload slot without consulting list-wide size metadata.
Definition ilist.h:1005
GAIA_NODISCARD iterator begin() noexcept
Returns an iterator over live payload objects only.
Definition ilist.h:936
void add_free(size_type index, uint32_t generation, uint32_t nextFreeIdx)
Restores a free slot with a preassigned id/generation and free-list link.
Definition ilist.h:1077
void reserve(size_type cap)
Reserves page-table capacity for at least cap slots.
Definition ilist.h:964
void add_live(TListItem &&item)
Restores a live slot with a preassigned id/generation.
Definition ilist.h:1029
void free(TItemHandle handle)
Frees a live item and destroys its payload immediately.
Definition ilist.h:1149
void free_keep_live(TItemHandle handle)
Frees a handle while keeping the payload alive until slot reuse or clear().
Definition ilist.h:1169
void reserve_slot_table(size_type cap)
Ensures the page pointer table can address cap slots without resizing later.
Definition ilist.h:978
Definition iterator.h:12
Definition raw_data_holder.h:12