Sane C++ Libraries
C++ Platform Abstraction Libraries
ArenaMap.h
1// Copyright (c) Stefano Cristiano
2// SPDX-License-Identifier: MIT
3#pragma once
4#include "../Foundation/Assert.h"
5#include "../Foundation/Memory.h"
6#include "ArenaMapKey.h"
7
8namespace SC
9{
10template <typename T>
11struct ArenaMap;
12} // namespace SC
13
16
27template <typename T>
29{
30 using Key = ArenaMapKey<T>;
31
32 ArenaMap() {}
33
34 ~ArenaMap() { clear(); }
35
36 ArenaMap(const ArenaMap& other) { *this = other; }
37
38 ArenaMap(ArenaMap&& other) { *this = move(other); }
39
40 ArenaMap& operator=(const ArenaMap& other)
41 {
42 clear();
43 T* newItems = reinterpret_cast<T*>(Memory::allocate(other.itemsSize * sizeof(T)));
44
45 typename Key::Generation* newGenerations = reinterpret_cast<typename Key::Generation*>(
46 Memory::allocate(other.itemsSize * sizeof(typename Key::Generation)));
47 SC_ASSERT_RELEASE(newItems);
48 SC_ASSERT_RELEASE(newGenerations);
49 ::memset(newGenerations, 0, other.itemsSize * sizeof(typename Key::Generation));
50 items = newItems;
51 generations = newGenerations;
52 itemsSize = other.itemsSize;
53 for (size_t idx = 0; idx < other.itemsSize; ++idx)
54 {
55 if (other.generations[idx].used)
56 {
57 new (&items[idx], PlacementNew()) T(other.items[idx]);
58 }
59 generations[idx] = other.generations[idx];
60 }
61 numUsed = other.numUsed;
62 return *this;
63 }
64
65 ArenaMap& operator=(ArenaMap&& other)
66 {
67 clear();
68 items = other.items;
69 itemsSize = other.itemsSize;
70 other.items = nullptr;
71 other.itemsSize = 0;
72 generations = other.generations;
73 other.generations = nullptr;
74 numUsed = other.numUsed;
75 other.numUsed = 0;
76 return *this;
77 }
78
80 uint32_t getNumAllocated() const { return static_cast<uint32_t>(itemsSize); }
81
82 template <typename MapType>
84 {
85 MapType* map = nullptr;
86 uint32_t index = 0;
87
88 void operator++()
89 {
90 const auto numAllocated = map->getNumAllocated();
91 for (++index; index < numAllocated; ++index)
92 {
93 if (map->generations[index].used)
94 {
95 break;
96 }
97 }
98 }
99
100 bool operator==(ArenaMapIterator it) const
101 {
102 SC_ASSERT_DEBUG(it.map == map and map != nullptr);
103 return it.index == index;
104 }
105 bool operator!=(ArenaMapIterator it) const
106 {
107 SC_ASSERT_DEBUG(it.map == map and map != nullptr);
108 return it.index != index;
109 }
110
111 auto& operator*() const { return map->items[index]; }
112 auto* operator->() const { return &map->items[index]; }
113 };
116
117 ConstIterator cbegin() const { return begin(); }
118 ConstIterator cend() const { return end(); }
119 ConstIterator begin() const
120 {
121 for (size_t idx = 0; idx < getNumAllocated(); ++idx)
122 {
123 if (generations[idx].used)
124 {
125 return {this, static_cast<uint32_t>(idx)};
126 }
127 }
128 return end();
129 }
130
131 ConstIterator end() const { return {this, getNumAllocated()}; }
132
133 Iterator begin()
134 {
135 for (size_t idx = 0; idx < getNumAllocated(); ++idx)
136 {
137 if (generations[idx].used)
138 {
139 return {this, static_cast<uint32_t>(idx)};
140 }
141 }
142 return end();
143 }
144
145 Iterator end() { return {this, getNumAllocated()}; }
146
147 void clear()
148 {
149 for (size_t idx = 0; idx < itemsSize; ++idx)
150 {
151 if (generations[idx].used)
152 {
153 items[idx].~T();
154 }
155 generations[idx].used = 0;
156 }
157 if (items)
158 Memory::release(items);
159 items = nullptr;
160 itemsSize = 0;
161 if (generations)
162 Memory::release(generations);
163 generations = nullptr;
164 numUsed = 0;
165 }
166
168 [[nodiscard]] size_t size() const { return numUsed; }
169
171 [[nodiscard]] size_t capacity() const { return itemsSize; }
172
174 [[nodiscard]] bool isFull() const { return itemsSize == numUsed; }
175
180 [[nodiscard]] bool resize(size_t newSize)
181 {
182 if (numUsed != 0)
183 return false;
184 if (newSize > Key::MaxIndex)
185 return false;
186 if (items)
187 Memory::release(items);
188 items = nullptr;
189 if (generations)
190 Memory::release(generations);
191 generations = nullptr;
192 T* newItems = reinterpret_cast<T*>(Memory::allocate(newSize * sizeof(T)));
193 if (not newItems)
194 return false;
195 items = newItems;
196 typename Key::Generation* newGenerations =
197 reinterpret_cast<typename Key::Generation*>(Memory::allocate(newSize * sizeof(typename Key::Generation)));
198 if (not newGenerations)
199 return false;
200 ::memset(newGenerations, 0, newSize * sizeof(typename Key::Generation));
201 generations = newGenerations;
202 itemsSize = newSize;
203 numUsed = 0;
204 return true;
205 }
206
207 template <typename Value>
208 [[nodiscard]] Key insert(const Value& object)
209 {
210 Key key = allocateNewKeySlot();
211 if (key.isValid())
212 {
213 new (&items[key.index], PlacementNew()) T(object);
214 }
215 return key;
216 }
217
218 [[nodiscard]] Key allocate()
219 {
220 Key key = allocateNewKeySlot();
221 if (key.isValid())
222 {
223 new (&items[key.index], PlacementNew()) T();
224 }
225 return key;
226 }
227
228 template <typename Value>
229 [[nodiscard]] Key insert(Value&& object)
230 {
231 Key key = allocateNewKeySlot();
232 if (key.isValid())
233 {
234 new (&items[key.index], PlacementNew()) T(move(object));
235 }
236 return key;
237 }
238
239 [[nodiscard]] bool containsKey(Key key) const
240 {
241 return key.isValid() and generations[key.index].used != 0 and
242 generations[key.index].generation == key.generation.generation;
243 }
244
245 template <typename ComparableToValue>
246 [[nodiscard]] bool containsValue(const ComparableToValue& value, Key* optionalKey = nullptr) const
247 {
248 for (size_t idx = 0; idx < itemsSize; ++idx)
249 {
250 if (generations[idx].used and items[idx] == value)
251 {
252 if (optionalKey)
253 {
254 optionalKey->index = static_cast<uint32_t>(idx);
255 optionalKey->generation = generations[idx];
256 }
257 return true;
258 }
259 }
260 return false;
261 }
262
263 [[nodiscard]] bool remove(Key key)
264 {
265 if (generations[key.index] != key.generation)
266 return false;
267 if (generations[key.index].generation + 1 <= Key::MaxGenerations)
268 {
269 generations[key.index].generation++;
270 }
271 else
272 {
273 // TODO: Should we give option of failing on generation overflow?
274 generations[key.index].generation++;
275 }
276 generations[key.index].used = 0;
277 items[key.index].~T();
278 numUsed--;
279 return true;
280 }
281
282 [[nodiscard]] T* get(Key key)
283 {
284 if (generations[key.index] != key.generation)
285 return nullptr;
286 return &items[key.index];
287 }
288
289 [[nodiscard]] const T* get(Key key) const
290 {
291 if (generations[key.index] != key.generation)
292 return nullptr;
293 return &items[key.index];
294 }
295
296 private:
297 T* items = nullptr;
298 size_t itemsSize = 0;
299
300 typename Key::Generation* generations = nullptr;
301
302 uint32_t numUsed = 0;
303
304 [[nodiscard]] Key allocateNewKeySlot()
305 {
306 for (size_t idx = 0; idx < itemsSize; ++idx)
307 {
308 if (generations[idx].used == 0)
309 {
310 generations[idx].used = 1;
311
312 Key key;
313 key.generation = generations[idx];
314 key.index = static_cast<uint32_t>(idx);
315 numUsed++;
316 return key;
317 }
318 }
319 return {};
320 }
321};
#define SC_ASSERT_DEBUG(e)
Assert expression e to be true.
Definition: Assert.h:82
#define SC_ASSERT_RELEASE(e)
Assert expression e to be true.
Definition: Assert.h:66
constexpr T && move(T &value)
Converts an lvalue to an rvalue reference.
Definition: Compiler.h:269
unsigned int uint32_t
Platform independent (4) bytes unsigned int.
Definition: PrimitiveTypes.h:38
Definition: ArenaMap.h:84
A sparse vector keeping objects at a stable memory location.
Definition: ArenaMap.h:29
bool resize(size_t newSize)
Changes the size of the arena.
Definition: ArenaMap.h:180
size_t size() const
Get the number of used slots in the arena.
Definition: ArenaMap.h:168
uint32_t getNumAllocated() const
Get the maximum number of objects that can be stored in this map.
Definition: ArenaMap.h:80
size_t capacity() const
Get the total number slots in the arena.
Definition: ArenaMap.h:171
bool isFull() const
Returns true if size() == capacity(), that means the arena is full.
Definition: ArenaMap.h:174
A sparse vector keeping objects at a stable memory location.
Definition: ArenaMapKey.h:22
static SC_COMPILER_EXPORT void * allocate(size_t numBytes)
Allocates numBytes bytes of memory.
static SC_COMPILER_EXPORT void release(void *allocatedMemory)
Free memory allocated by Memory::allocate and / or reallocated by Memory::reallocate.