vkmEngine 1.0.0
A C++ game engine · vkmengine.com
Loading...
Searching...
No Matches
sparse_set.h
1#pragma once
2
3#include <algorithm>
4#include <cstdint>
5#include <vector>
6#include <type_traits>
7
8#include "l_assert.h"
9
10namespace Vkm::Engine {
11
15class ISparseSet {
16 public:
17 ISparseSet() = default;
18 virtual ~ISparseSet() = default;
19
20 ISparseSet(const ISparseSet& other) = delete;
21 ISparseSet& operator=(const ISparseSet& other) = delete;
22
23 ISparseSet(ISparseSet && other) = delete;
24 ISparseSet& operator=(ISparseSet && other) = delete;
25
31 virtual void removeIfPresent(uint32_t key) = 0;
32
38 virtual void compact() = 0;
39};
40
49template<typename T>
50class SparseSet : public ISparseSet {
51 public:
52 SparseSet() = default;
53 ~SparseSet() override = default;
54
55 SparseSet(const SparseSet& other) = delete;
56 SparseSet& operator=(const SparseSet& other) = delete;
57
58 SparseSet(SparseSet && other) = delete;
59 SparseSet& operator=(SparseSet && other) = delete;
60
61 public:
68 T& add(uint32_t key, T && value) { return addInternal(key, std::move(value)); }
69 T& add(uint32_t key, const T& value) { return addInternal(key, value); }
70
75 void remove(uint32_t key) {
76 VKM_ASSERT(contains(key), "SparseSet::remove called with invalid key");
77 // Guarded too: a build without asserts would index m_data with garbage.
78 if (!contains(key)) return;
79
80 uint32_t dataIdx = m_dataIndex[key];
81 uint32_t lastIdx = static_cast<uint32_t>(m_data.size() - 1);
82
83 if (dataIdx != lastIdx) {
84 m_data[dataIdx] = std::move(m_data[lastIdx]);
85
86 m_dataId[dataIdx] = m_dataId[lastIdx];
87 m_dataIndex[m_dataId[dataIdx]] = dataIdx;
88 }
89
90 m_data.pop_back();
91 m_dataId.pop_back();
92 m_dataIndex[key] = EMPTY;
93 }
94
100 void removeIfPresent(uint32_t key) override {
101 if (contains(key)) remove(key);
102 }
103
109 bool contains(uint32_t key) const {
110 return key < m_dataIndex.size() && m_dataIndex[key] != EMPTY;
111 }
112
118 T& get(uint32_t key) {
119 VKM_ASSERT(contains(key), "SparseSet::get called with invalid key");
120 return m_data[m_dataIndex[key]];
121 }
122
123 const T& get(uint32_t key) const {
124 VKM_ASSERT(contains(key), "SparseSet::get called with invalid key");
125 return m_data[m_dataIndex[key]];
126 }
127
128 public:
139 template<typename Fn>
140 void forEach(Fn&& fn) {
141 for (uint32_t i = 0; i < m_data.size(); ++i) {
142 fn(m_dataId[i], m_data[i]);
143 }
144 }
145
146 template<typename Fn>
147 void forEach(Fn&& fn) const {
148 for (uint32_t i = 0; i < m_data.size(); ++i) {
149 fn(m_dataId[i], m_data[i]);
150 }
151 }
152
158 size_t size() const { return m_data.size(); }
159
165 void clear() {
166 m_data.clear();
167 m_dataId.clear();
168 std::fill(m_dataIndex.begin(), m_dataIndex.end(), EMPTY);
169 }
170
178 void compact() override {
179 if (m_data.empty()) {
180 m_dataIndex.clear();
181 } else {
182 uint32_t maxKey = 0;
183 for (uint32_t i = 0; i < m_dataId.size(); ++i) {
184 if (m_dataId[i] > maxKey) maxKey = m_dataId[i];
185 }
186 if (maxKey + 1 < m_dataIndex.size()) m_dataIndex.resize(maxKey + 1);
187 }
188 m_dataIndex.shrink_to_fit();
189 }
190
199 uint32_t keyAt(uint32_t denseIndex) const { return m_dataId[denseIndex]; }
200
207 T& dataAt(uint32_t denseIndex) { return m_data[denseIndex]; }
208 const T& dataAt(uint32_t denseIndex) const { return m_data[denseIndex]; }
209
210 private:
211 static constexpr uint32_t EMPTY = UINT32_MAX;
212
218 void ensureCapacity(uint32_t key) {
219 if (key >= m_dataIndex.size())
220 m_dataIndex.resize(key + 1, EMPTY);
221 }
222
231 template<typename... Args>
232 T& addInternal(uint32_t key, Args&&... args) {
233 VKM_ASSERT(key != EMPTY, "SparseSet::add key cannot be EMPTY sentinel");
234 VKM_ASSERT(key != 0, "SparseSet::add key 0 is reserved");
235 ensureCapacity(key);
236 VKM_ASSERT(!contains(key), "SparseSet::add key already present");
237 // Guarded too: without asserts the key would strand its first entry.
238 if (contains(key)) return m_data[m_dataIndex[key]];
239
240 uint32_t dataIdx = static_cast<uint32_t>(m_data.size());
241
242 m_data.emplace_back(std::forward<Args>(args)...);
243 m_dataId.push_back(key);
244 m_dataIndex[key] = dataIdx;
245
246 return m_data[dataIdx];
247 }
248
249 private:
250 std::vector<uint32_t> m_dataIndex;
251 std::vector<uint32_t> m_dataId;
252 std::vector<T> m_data;
253};
254
255} // namespace Vkm::Engine
virtual void compact()=0
Release the sparse array's slack, keeping every live element.
virtual void removeIfPresent(uint32_t key)=0
Remove the element at the given key, if this set holds one.
void removeIfPresent(uint32_t key) override
Remove the element at the given key if one is present.
Definition sparse_set.h:100
bool contains(uint32_t key) const
Test whether a key is present.
Definition sparse_set.h:109
void compact() override
Shrink the sparse array to fit only live keys, reclaiming wasted memory.
Definition sparse_set.h:178
uint32_t keyAt(uint32_t denseIndex) const
Access the sparse key stored at a dense index.
Definition sparse_set.h:199
T & dataAt(uint32_t denseIndex)
Access the element stored at a dense index.
Definition sparse_set.h:207
void forEach(Fn &&fn)
Iterate all live elements densely (no holes).
Definition sparse_set.h:140
T & add(uint32_t key, T &&value)
Insert an element at the given key.
Definition sparse_set.h:68
void clear()
Drop every element.
Definition sparse_set.h:165
size_t size() const
Number of live elements.
Definition sparse_set.h:158
void remove(uint32_t key)
Remove the element at the given key via swap-and-pop.
Definition sparse_set.h:75
T & get(uint32_t key)
Access the element at the given key.
Definition sparse_set.h:118