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 {
125 uint32_t gen;
126 };
127
130 uint32_t idx;
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 {
185 using internal_storage = TInternalStorage;
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()");
216 internal_storage m_items;
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
357 TListItem& free(TItemHandle handle) {
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 {
376 bool hasThingsToRemove = m_freeItems > 0;
377 if (!hasThingsToRemove)
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;
384 auto nextFreeItem = m_nextFreeIdx;
385 while (freeItems > 0) {
386 GAIA_ASSERT(nextFreeItem < m_items.size() && "Item recycle list broken!");
387
388 nextFreeItem = ilist_item_traits<TListItem>::idx(m_items[nextFreeItem]);
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>
400 static TItemHandle make(uint32_t id, uint32_t gen, const TItemHandle&) {
401 return TItemHandle(id, gen);
402 }
403 };
404
405 template <typename TListItem, typename TItemHandle>
406 struct paged_ilist;
407
412 template <typename TPagedIList, bool IsConst>
414 using owner_type = std::conditional_t<IsConst, const TPagedIList, TPagedIList>;
415 using owner_pointer = owner_type*;
416
417 owner_pointer m_pOwner = nullptr;
418 typename TPagedIList::size_type m_index = 0;
419
420 void skip_dead() {
421 while (m_pOwner != nullptr && m_index < m_pOwner->size() && !m_pOwner->has(m_index))
422 ++m_index;
423 }
424
425 public:
426 using value_type = typename TPagedIList::value_type;
427 using reference =
428 std::conditional_t<IsConst, typename TPagedIList::const_reference, typename TPagedIList::reference>;
429 using pointer = std::conditional_t<IsConst, typename TPagedIList::const_pointer, typename TPagedIList::pointer>;
430 using difference_type = typename TPagedIList::difference_type;
432
433 paged_ilist_iterator() = default;
434 paged_ilist_iterator(owner_pointer pOwner, typename TPagedIList::size_type index):
435 m_pOwner(pOwner), m_index(index) {
436 skip_dead();
437 }
438
439 GAIA_NODISCARD reference operator*() const {
440 return (*m_pOwner)[m_index];
441 }
442
443 GAIA_NODISCARD pointer operator->() const {
444 return &(*m_pOwner)[m_index];
445 }
446
447 paged_ilist_iterator& operator++() {
448 ++m_index;
449 skip_dead();
450 return *this;
451 }
452
453 paged_ilist_iterator operator++(int) {
454 auto tmp = *this;
455 ++(*this);
456 return tmp;
457 }
458
459 GAIA_NODISCARD bool operator==(const paged_ilist_iterator& other) const {
460 return m_pOwner == other.m_pOwner && m_index == other.m_index;
461 }
462
463 GAIA_NODISCARD bool operator!=(const paged_ilist_iterator& other) const {
464 return !(*this == other);
465 }
466 };
467
474 template <typename TListItem, typename TItemHandle>
475 struct paged_ilist {
476 using value_type = TListItem;
477 using reference = TListItem&;
478 using const_reference = const TListItem&;
479 using pointer = TListItem*;
480 using const_pointer = const TListItem*;
481 using difference_type = std::ptrdiff_t;
482 using size_type = uint32_t;
484
485 static_assert(detail::ilist_traits_has_idx<TListItem>::value, "ilist_item_traits<T> must expose idx(const T&)");
486 static_assert(
487 detail::ilist_traits_has_set_idx<TListItem>::value, "ilist_item_traits<T> must expose set_idx(T&, uint32_t)");
488 static_assert(detail::ilist_traits_has_gen<TListItem>::value, "ilist_item_traits<T> must expose gen(const T&)");
489 static_assert(
490 detail::ilist_traits_has_set_gen<TListItem>::value, "ilist_item_traits<T> must expose set_gen(T&, uint32_t)");
491 static_assert(
493 "paged_ilist item type must expose static create(index, generation, ctx)");
494 static_assert(
496 "paged_ilist item type must expose static handle(const item) returning the handle type");
497 static_assert(detail::ilist_has_id_mask<TItemHandle>::value, "paged_ilist handle type must expose IdMask");
498 static_assert(detail::ilist_has_handle_id<TItemHandle>::value, "paged_ilist handle type must expose id()");
499 static_assert(detail::ilist_has_handle_gen<TItemHandle>::value, "paged_ilist handle type must expose gen()");
500
501 private:
502 static constexpr size_type target_payload_bytes() noexcept {
503 return 16384;
504 }
505
506 static constexpr size_type min_page_capacity() noexcept {
507 return 8;
508 }
509
510 static constexpr size_type max_page_capacity() noexcept {
511 return 256;
512 }
513
514 static constexpr size_type calc_page_capacity() noexcept {
515 const size_type payloadItemBytes = sizeof(TListItem) == 0 ? 1U : (size_type)sizeof(TListItem);
516 const size_type desired = target_payload_bytes() / payloadItemBytes;
517 if (desired < min_page_capacity())
518 return min_page_capacity();
519 if (desired > max_page_capacity())
520 return max_page_capacity();
521 return desired;
522 }
523
524 static constexpr size_type PageCapacity = calc_page_capacity();
525
526 struct page_type {
527 using alive_mask_type = cnt::bitset<PageCapacity>;
528 using storage_type = mem::raw_data_holder<TListItem, sizeof(TListItem) * PageCapacity>;
529
530 alive_mask_type aliveMask;
531 TItemHandle handles[PageCapacity]{};
532 uint32_t nextFree[PageCapacity];
533 uint32_t liveCount = 0;
534 storage_type* pStorage = nullptr;
535
536 page_type() {
537 GAIA_FOR(PageCapacity)
538 nextFree[i] = TItemHandle::IdMask;
539 }
540
541 ~page_type() {
542 clear();
543 }
544
545 page_type(const page_type&) = delete;
546 page_type& operator=(const page_type&) = delete;
547
548 page_type(page_type&& other) noexcept:
549 aliveMask(other.aliveMask), liveCount(other.liveCount), pStorage(other.pStorage) {
550 GAIA_FOR(PageCapacity) {
551 handles[i] = other.handles[i];
552 nextFree[i] = other.nextFree[i];
553 }
554
555 other.aliveMask.reset();
556 other.liveCount = 0;
557 other.pStorage = nullptr;
558 }
559
560 page_type& operator=(page_type&& other) noexcept {
561 GAIA_ASSERT(core::addressof(other) != this);
562
563 clear();
564 aliveMask = other.aliveMask;
565 liveCount = other.liveCount;
566 pStorage = other.pStorage;
567 GAIA_FOR(PageCapacity) {
568 handles[i] = other.handles[i];
569 nextFree[i] = other.nextFree[i];
570 }
571
572 other.aliveMask.reset();
573 other.liveCount = 0;
574 other.pStorage = nullptr;
575 return *this;
576 }
577
578 GAIA_NODISCARD pointer data() noexcept {
579 return pStorage == nullptr ? nullptr : reinterpret_cast<pointer>((uint8_t*)*pStorage);
580 }
581
582 GAIA_NODISCARD const_pointer data() const noexcept {
583 return pStorage == nullptr ? nullptr : reinterpret_cast<const_pointer>((const uint8_t*)*pStorage);
584 }
585
586 GAIA_NODISCARD pointer ptr(size_type slot) noexcept {
587 GAIA_ASSERT(slot < PageCapacity);
588 GAIA_ASSERT(pStorage != nullptr);
589 return data() + slot;
590 }
591
592 GAIA_NODISCARD const_pointer ptr(size_type slot) const noexcept {
593 GAIA_ASSERT(slot < PageCapacity);
594 GAIA_ASSERT(pStorage != nullptr);
595 return data() + slot;
596 }
597
598 void ensure_storage() {
599 if GAIA_LIKELY (pStorage != nullptr)
600 return;
601
602 constexpr auto StorageAlignment =
603 alignof(storage_type) < sizeof(void*) ? sizeof(void*) : alignof(storage_type);
604 pStorage = mem::AllocHelper::alloc_alig<storage_type>("PagedIListPage", StorageAlignment);
605 }
606
607 void maybe_release_storage() {
608 if (liveCount != 0 || pStorage == nullptr)
609 return;
610
611 mem::AllocHelper::free_alig("PagedIListPage", pStorage);
612 pStorage = nullptr;
613 }
614
615 void clear() {
616 if (pStorage != nullptr) {
617 for (auto slot: aliveMask)
618 core::call_dtor(ptr(slot));
619 mem::AllocHelper::free_alig("PagedIListPage", pStorage);
620 pStorage = nullptr;
621 }
622
623 aliveMask.reset();
624 liveCount = 0;
625 GAIA_FOR(PageCapacity)
626 nextFree[i] = TItemHandle::IdMask;
627 }
628
629 template <typename... Args>
630 void construct(size_type slot, Args&&... args) {
631 GAIA_ASSERT(slot < PageCapacity);
632 ensure_storage();
633 core::call_ctor(ptr(slot), GAIA_FWD(args)...);
634 aliveMask.set(slot);
635 ++liveCount;
636 }
637
638 void destroy(size_type slot) {
639 GAIA_ASSERT(slot < PageCapacity);
640 GAIA_ASSERT(aliveMask.test(slot));
641 core::call_dtor(ptr(slot));
642 aliveMask.reset(slot);
643 GAIA_ASSERT(liveCount > 0);
644 --liveCount;
645 maybe_release_storage();
646 }
647 };
648
650 size_type m_size = 0;
651
652 public:
653 size_type m_nextFreeIdx = (size_type)-1;
654 size_type m_freeItems = 0;
655
656 private:
657 GAIA_NODISCARD static constexpr size_type page_index(size_type index) noexcept {
658 return index / PageCapacity;
659 }
660
661 GAIA_NODISCARD static constexpr size_type slot_index(size_type index) noexcept {
662 return index % PageCapacity;
663 }
664
665 GAIA_NODISCARD static constexpr size_type page_count_for_slots(size_type slotCnt) noexcept {
666 return slotCnt == 0 ? 0U : (slotCnt + PageCapacity - 1) / PageCapacity;
667 }
668
669 void clear_pages() {
670 for (auto* pPage: m_pages) {
671 if (pPage == nullptr)
672 continue;
673 delete pPage;
674 }
675 m_pages.clear();
676 }
677
678 void ensure_page_count(size_type slotCnt) {
679 const auto pageCnt = page_count_for_slots(slotCnt);
680 if (pageCnt > (size_type)m_pages.size())
681 m_pages.resize(pageCnt, nullptr);
682 }
683
684 GAIA_NODISCARD page_type& ensure_page(size_type index) {
685 ensure_page_count(index + 1);
686 auto*& pPage = m_pages[page_index(index)];
687 if (pPage == nullptr)
688 pPage = new page_type();
689 return *pPage;
690 }
691
692 GAIA_NODISCARD page_type* try_page(size_type index) noexcept {
693 const auto pageIdx = page_index(index);
694 if (pageIdx >= (size_type)m_pages.size())
695 return nullptr;
696 return m_pages[pageIdx];
697 }
698
699 GAIA_NODISCARD const page_type* try_page(size_type index) const noexcept {
700 const auto pageIdx = page_index(index);
701 if (pageIdx >= (size_type)m_pages.size())
702 return nullptr;
703 return m_pages[pageIdx];
704 }
705
706 GAIA_NODISCARD reference slot_ref(size_type index) {
707 auto* pPage = try_page(index);
708 GAIA_ASSERT(pPage != nullptr);
709 return *pPage->ptr(slot_index(index));
710 }
711
712 GAIA_NODISCARD const_reference slot_ref(size_type index) const {
713 auto* pPage = try_page(index);
714 GAIA_ASSERT(pPage != nullptr);
715 return *pPage->ptr(slot_index(index));
716 }
717
718 public:
721
722 ~paged_ilist() {
723 clear_pages();
724 }
725
726 paged_ilist() = default;
727 paged_ilist(const paged_ilist&) = delete;
728 paged_ilist& operator=(const paged_ilist&) = delete;
729 paged_ilist(paged_ilist&& other) noexcept:
730 m_pages(GAIA_MOV(other.m_pages)), m_size(other.m_size), m_nextFreeIdx(other.m_nextFreeIdx),
731 m_freeItems(other.m_freeItems) {
732 other.m_size = 0;
733 other.m_nextFreeIdx = (size_type)-1;
734 other.m_freeItems = 0;
735 }
736 paged_ilist& operator=(paged_ilist&& other) noexcept {
737 GAIA_ASSERT(core::addressof(other) != this);
738 clear_pages();
739 m_pages = GAIA_MOV(other.m_pages);
740 m_size = other.m_size;
741 m_nextFreeIdx = other.m_nextFreeIdx;
742 m_freeItems = other.m_freeItems;
743
744 other.m_size = 0;
745 other.m_nextFreeIdx = (size_type)-1;
746 other.m_freeItems = 0;
747 return *this;
748 }
749
750 GAIA_NODISCARD pointer data() noexcept {
751 return nullptr;
752 }
753
754 GAIA_NODISCARD const_pointer data() const noexcept {
755 return nullptr;
756 }
757
758 GAIA_NODISCARD bool has(size_type index) const noexcept {
759 if (index >= m_size)
760 return false;
761
762 const auto* pPage = try_page(index);
763 return pPage != nullptr && pPage->aliveMask.test(slot_index(index));
764 }
765
766 GAIA_NODISCARD bool has(TItemHandle handle) const noexcept {
767 return has(handle.id()) && this->handle(handle.id()) == handle;
768 }
769
770 GAIA_NODISCARD TItemHandle handle(size_type index) const noexcept {
771 GAIA_ASSERT(index < m_size);
772 const auto* pPage = try_page(index);
773 GAIA_ASSERT(pPage != nullptr);
774 return pPage->handles[slot_index(index)];
775 }
776
777 GAIA_NODISCARD uint32_t generation(size_type index) const noexcept {
778 return handle(index).gen();
779 }
780
781 GAIA_NODISCARD uint32_t next_free(size_type index) const noexcept {
782 GAIA_ASSERT(index < m_size);
783 const auto* pPage = try_page(index);
784 GAIA_ASSERT(pPage != nullptr);
785 return pPage->nextFree[slot_index(index)];
786 }
787
788 GAIA_NODISCARD reference operator[](size_type index) {
789 GAIA_ASSERT(has(index));
790 return slot_ref(index);
791 }
792
793 GAIA_NODISCARD const_reference operator[](size_type index) const {
794 GAIA_ASSERT(has(index));
795 return slot_ref(index);
796 }
797
798 void clear() {
799 clear_pages();
800 m_size = 0;
801 m_nextFreeIdx = (size_type)-1;
802 m_freeItems = 0;
803 }
804
805 GAIA_NODISCARD size_type get_next_free_item() const noexcept {
806 return m_nextFreeIdx;
807 }
808
809 GAIA_NODISCARD size_type get_free_items() const noexcept {
810 return m_freeItems;
811 }
812
813 GAIA_NODISCARD size_type item_count() const noexcept {
814 return m_size - m_freeItems;
815 }
816
817 GAIA_NODISCARD size_type size() const noexcept {
818 return m_size;
819 }
820
821 GAIA_NODISCARD bool empty() const noexcept {
822 return m_size == 0;
823 }
824
825 GAIA_NODISCARD size_type capacity() const noexcept {
826 return (size_type)m_pages.capacity() * PageCapacity;
827 }
828
830 GAIA_NODISCARD iterator begin() noexcept {
831 return iterator(this, 0);
832 }
833
834 GAIA_NODISCARD const_iterator begin() const noexcept {
835 return const_iterator(this, 0);
836 }
837
838 GAIA_NODISCARD const_iterator cbegin() const noexcept {
839 return const_iterator(this, 0);
840 }
841
842 GAIA_NODISCARD iterator end() noexcept {
843 return iterator(this, m_size);
844 }
845
846 GAIA_NODISCARD const_iterator end() const noexcept {
847 return const_iterator(this, m_size);
848 }
849
850 GAIA_NODISCARD const_iterator cend() const noexcept {
851 return const_iterator(this, m_size);
852 }
853
854 void reserve(size_type cap) {
855 m_pages.reserve(page_count_for_slots(cap));
856 }
857
858 GAIA_NODISCARD pointer try_get(size_type index) noexcept {
859 if (!has(index))
860 return nullptr;
861 return &slot_ref(index);
862 }
863
864 GAIA_NODISCARD const_pointer try_get(size_type index) const noexcept {
865 if (!has(index))
866 return nullptr;
867 return &slot_ref(index);
868 }
869
871 void add_live(TListItem&& item) {
872 const auto index = (size_type)ilist_item_traits<TListItem>::idx(item);
873 auto& page = ensure_page(index);
874 const auto slot = slot_index(index);
875 const bool existed = index < m_size;
876 const bool wasAlive = existed && page.aliveMask.test(slot);
877 const bool wasFree = existed && !wasAlive;
878
879 if (index >= m_size)
880 m_size = index + 1;
881 else if (wasAlive)
882 page.destroy(slot);
883 else if (wasFree && m_freeItems > 0)
884 --m_freeItems;
885
886 page.construct(slot, GAIA_MOV(item));
887 page.handles[slot] = TListItem::handle(*page.ptr(slot));
888 page.nextFree[slot] = TItemHandle::IdMask;
889 }
890
892 void add_free(TItemHandle handle, uint32_t nextFreeIdx) {
893 const auto index = (size_type)handle.id();
894 auto& page = ensure_page(index);
895 const auto slot = slot_index(index);
896 const bool existed = index < m_size;
897 const bool wasAlive = existed && page.aliveMask.test(slot);
898 const bool wasFree = existed && !wasAlive;
899
900 if (index >= m_size)
901 m_size = index + 1;
902 else if (wasAlive)
903 page.destroy(slot);
904
905 page.handles[slot] = handle;
906 page.nextFree[slot] = nextFreeIdx;
907 if (!wasFree)
908 ++m_freeItems;
909 if (m_nextFreeIdx == (size_type)-1)
910 m_nextFreeIdx = index;
911 }
912
914 void add_free(size_type index, uint32_t generation, uint32_t nextFreeIdx) {
915 add_free(ilist_handle_traits<TItemHandle>::make(index, generation, TItemHandle{}), nextFreeIdx);
916 }
917
919 GAIA_NODISCARD TItemHandle alloc(void* ctx) {
920 size_type index = 0;
921 uint32_t generation = 0;
922
923 if GAIA_UNLIKELY (m_freeItems == 0U) {
924 index = m_size;
925 GAIA_ASSERT(index < TItemHandle::IdMask && "Trying to allocate too many items!");
926 ++m_size;
927 } else {
928 GAIA_ASSERT(m_nextFreeIdx < m_size && "Item recycle list broken!");
929 index = m_nextFreeIdx;
930 auto& page = ensure_page(index);
931 const auto slot = slot_index(index);
932 m_nextFreeIdx = page.nextFree[slot];
933 page.nextFree[slot] = TItemHandle::IdMask;
934 generation = page.handles[slot].gen();
935 --m_freeItems;
936 }
937
938 auto& page = ensure_page(index);
939 const auto slot = slot_index(index);
940 page.construct(slot, TListItem::create(index, generation, ctx));
941 page.handles[slot] = TListItem::handle(*page.ptr(slot));
942 page.nextFree[slot] = TItemHandle::IdMask;
943 return page.handles[slot];
944 }
945
947 GAIA_NODISCARD TItemHandle alloc() {
948 size_type index = 0;
949 uint32_t generation = 0;
950
951 if GAIA_UNLIKELY (m_freeItems == 0U) {
952 index = m_size;
953 GAIA_ASSERT(index < TItemHandle::IdMask && "Trying to allocate too many items!");
954 ++m_size;
955 } else {
956 GAIA_ASSERT(m_nextFreeIdx < m_size && "Item recycle list broken!");
957 index = m_nextFreeIdx;
958 auto& page = ensure_page(index);
959 const auto slot = slot_index(index);
960 m_nextFreeIdx = page.nextFree[slot];
961 page.nextFree[slot] = TItemHandle::IdMask;
962 generation = page.handles[slot].gen();
963 --m_freeItems;
964 }
965
966 auto& page = ensure_page(index);
967 const auto slot = slot_index(index);
968 page.construct(slot, TListItem(index, generation));
969 page.handles[slot] = ilist_handle_traits<TItemHandle>::make(index, generation, TItemHandle{});
970 page.nextFree[slot] = TItemHandle::IdMask;
971 return page.handles[slot];
972 }
973
974 void free(TItemHandle handle) {
975 GAIA_ASSERT(has(handle));
976
977 auto& page = ensure_page(handle.id());
978 const auto slot = slot_index(handle.id());
979 page.destroy(slot);
980 page.handles[slot] = ilist_handle_traits<TItemHandle>::make(handle.id(), handle.gen() + 1, page.handles[slot]);
981 page.nextFree[slot] = m_freeItems == 0 ? TItemHandle::IdMask : m_nextFreeIdx;
982 m_nextFreeIdx = handle.id();
983 ++m_freeItems;
984 }
985
986 void validate() const {
987 if (m_freeItems == 0)
988 return;
989
990 auto freeItems = m_freeItems;
991 auto nextFreeItem = m_nextFreeIdx;
992 while (freeItems > 0) {
993 GAIA_ASSERT(nextFreeItem < m_size && "Item recycle list broken!");
994 GAIA_ASSERT(!has(nextFreeItem));
995
996 nextFreeItem = next_free(nextFreeItem);
997 --freeItems;
998 }
999
1000 GAIA_ASSERT(nextFreeItem == TItemHandle::IdMask);
1001 }
1002 };
1003 } // namespace cnt
1004} // namespace gaia
Bitset iterator.
Definition bitset_iterator.h:14
Definition bitset.h:12
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:413
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition ilist.h:169
Definition ilist.h:399
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 with page-local slot metadata and lazily allocated page payloads....
Definition ilist.h:475
GAIA_NODISCARD TItemHandle alloc()
Allocates a new item in the list.
Definition ilist.h:947
void add_free(size_type index, uint32_t generation, uint32_t nextFreeIdx)
Restores a free slot with a preassigned id/generation and freelist link.
Definition ilist.h:914
void add_live(TListItem &&item)
Restores a live slot with a preassigned id/generation.
Definition ilist.h:871
GAIA_NODISCARD TItemHandle alloc(void *ctx)
Allocates a new item in the list.
Definition ilist.h:919
GAIA_NODISCARD iterator begin() noexcept
Returns an iterator over live payload objects only.
Definition ilist.h:830
void add_free(TItemHandle handle, uint32_t nextFreeIdx)
Restores a free slot with a preassigned id/generation and freelist link.
Definition ilist.h:892
Definition iterator.h:12
Definition raw_data_holder.h:12