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