vkmEngine 1.0.0
A C++ game engine · vkmengine.com
Loading...
Searching...
No Matches
slot_allocator.h
1#pragma once
2
3#include <algorithm>
4#include <cstdint>
5#include <vector>
6
7#include "l_assert.h"
8#include "core/memory/types.h"
9
10namespace Vkm::Engine {
11
18class SlotAllocator {
19 public:
26 static constexpr uint32_t MAX_CLAIMED_INDEX = 1u << 22;
27
28 public:
29 SlotAllocator() : m_generation({GenerationIndex{}}) {}
30 ~SlotAllocator() = default;
31
32 SlotAllocator(const SlotAllocator& other) = delete;
33 SlotAllocator& operator=(const SlotAllocator& other) = delete;
34
35 SlotAllocator(SlotAllocator && other) = delete;
36 SlotAllocator& operator=(SlotAllocator && other) = delete;
37
38 public:
47 StorageIndex allocate(bool* recycled = nullptr) {
48 bool reused = false;
49 const uint32_t idx = allocateSlot(reused);
50 if (recycled) *recycled = reused;
51 m_generation[idx].setAlive(true);
52 ++m_liveCount;
53 return StorageIndex{idx, m_generation[idx].generation()};
54 }
55
63 void free(StorageIndex id) {
64 VKM_ASSERT(has(id), "SlotAllocator::free called with invalid handle");
65 if (!has(id)) return;
66
67 m_generation[id.index].setAlive(false);
68 m_generation[id.index].bumpGeneration();
69 m_freeList.push_back(id.index);
70 --m_liveCount;
71 }
72
78 bool has(StorageIndex id) const {
79 return id
80 && id.index < m_generation.size()
81 && m_generation[id.index].alive()
82 && m_generation[id.index].generation() == id.generation;
83 }
84
93 StorageIndex handleAt(uint32_t index) const {
94 if (index >= m_generation.size()) return {};
95 return StorageIndex{index, m_generation[index].generation()};
96 }
97
104 bool isAliveAtIndex(uint32_t index) const {
105 return index > 0
106 && index < m_generation.size()
107 && m_generation[index].alive();
108 }
109
122 StorageIndex allocateAt(uint32_t index) {
123 if (index == 0 || index > MAX_CLAIMED_INDEX) return {};
124
125 // Highest first, so popping from the back hands the gap out lowest first.
126 const uint32_t oldSize = static_cast<uint32_t>(m_generation.size());
127 while (m_generation.size() <= index) m_generation.push_back({});
128 for (uint32_t gap = index; gap-- > oldSize;) m_freeList.push_back(gap);
129
130 VKM_ASSERT(
131 !m_generation[index].alive(),
132 "SlotAllocator::allocateAt: slot %u already alive",
133 index
134 );
135 // Guarded too, so without asserts two owners never share a slot.
136 if (m_generation[index].alive()) return {};
137
138 m_generation[index].setAlive(true);
139 ++m_liveCount;
140 return StorageIndex{index, m_generation[index].generation()};
141 }
142
149 template<typename Fn>
150 void forEach(Fn&& fn) const {
151 for (uint32_t i = 1; i < m_generation.size(); ++i) {
152 if (m_generation[i].alive()) fn(i);
153 }
154 }
155
159 void clear() {
160 m_freeList.clear();
161 m_freeList.reserve(m_generation.size());
162 // Highest first, so slot 1 comes out next, as from a new allocator.
163 for (uint32_t i = static_cast<uint32_t>(m_generation.size()); i-- > 1;) {
164 if (m_generation[i].alive()) {
165 m_generation[i].setAlive(false);
166 m_generation[i].bumpGeneration();
167 }
168 m_freeList.push_back(i);
169 }
170 m_liveCount = 0;
171 }
172
180 void swap(SlotAllocator& other) noexcept {
181 using std::swap;
182 swap(m_generation, other.m_generation);
183 swap(m_freeList, other.m_freeList);
184 swap(m_liveCount, other.m_liveCount);
185 }
186
192 size_t size() const { return m_liveCount; }
193
199 size_t extent() const { return m_generation.size(); }
200
201 private:
208 uint32_t allocateSlot(bool& reused) {
209 // Skip slots allocateAt claimed; each is discarded once, so amortised O(1).
210 while (!m_freeList.empty()) {
211 const uint32_t idx = m_freeList.back();
212 m_freeList.pop_back();
213 if (!m_generation[idx].alive()) {
214 reused = true;
215 return idx;
216 }
217 }
218
219 reused = false;
220 const uint32_t idx = static_cast<uint32_t>(m_generation.size());
221 m_generation.push_back({});
222 return idx;
223 }
224
225 private:
226 std::vector<GenerationIndex> m_generation;
227 std::vector<uint32_t> m_freeList;
228 size_t m_liveCount = 0;
229};
230
231} // namespace Vkm::Engine
void free(StorageIndex id)
Free a handle, bumping its generation and recycling the slot.
Definition slot_allocator.h:63
void forEach(Fn &&fn) const
Invoke fn(index) for every currently-alive slot.
Definition slot_allocator.h:150
StorageIndex handleAt(uint32_t index) const
Rebuild the handle naming a sparse slot, generation included.
Definition slot_allocator.h:93
void swap(SlotAllocator &other) noexcept
Swap internal state with another allocator.
Definition slot_allocator.h:180
bool isAliveAtIndex(uint32_t index) const
Check whether index currently holds a live slot.
Definition slot_allocator.h:104
size_t extent() const
Number of slots the table spans, live or free, slot 0 included.
Definition slot_allocator.h:199
StorageIndex allocate(bool *recycled=nullptr)
Allocate a new handle with a unique index and current generation.
Definition slot_allocator.h:47
size_t size() const
Number of live slots.
Definition slot_allocator.h:192
void clear()
Reset every slot to dead, bumping generations so outstanding handles go stale.
Definition slot_allocator.h:159
bool has(StorageIndex id) const
Test whether a handle is still valid.
Definition slot_allocator.h:78
StorageIndex allocateAt(uint32_t index)
Allocate a slot at a specific index.
Definition slot_allocator.h:122
static constexpr uint32_t MAX_CLAIMED_INDEX
The highest index allocateAt will claim.
Definition slot_allocator.h:26
Per-slot alive flag (bit 31) and generation counter (bits 0-30) in one uint32_t.
Definition types.h:40
Opaque handle pairing a sparse-array index with a generation counter.
Definition types.h:14