PixelBullet  0.0.1
A C++ game engine
Loading...
Searching...
No Matches
sparse_set.h
1#pragma once
2
4
5#include <concepts>
6#include <cstddef>
7#include <cstdint>
8#include <limits>
9#include <utility>
10#include <vector>
11
12namespace pixelbullet
13{
14template <typename T, std::unsigned_integral Index = std::uint32_t>
16{
17public:
18 using value_type = T;
19 using index_type = Index;
20
21 struct Entry
22 {
23 template <typename... Args>
24 Entry(const Index entry_id, Args&&... args)
25 : id(entry_id)
26 , value(std::forward<Args>(args)...)
27 {
28 }
29
30 Index id;
31 T value;
32 };
33
34 [[nodiscard]] bool contains(const Index id) const
35 {
36 assert_valid_id(id);
37
38 const std::size_t sparse_index = static_cast<std::size_t>(id);
39 if (sparse_index >= sparse_.size())
40 {
41 return false;
42 }
43
44 const Index dense_index = sparse_[sparse_index];
45 const std::size_t dense_position = static_cast<std::size_t>(dense_index);
46 return dense_index != k_invalid_index && dense_position < dense_.size() && dense_[dense_position].id == id;
47 }
48
49 T& insert_or_assign(const Index id, T value)
50 {
51 ensure_sparse_size(id);
52
53 if (contains(id))
54 {
55 T& existing = dense_[static_cast<std::size_t>(sparse_[static_cast<std::size_t>(id)])].value;
56 existing = std::move(value);
57 return existing;
58 }
59
60 assert_dense_index_available();
61 sparse_[static_cast<std::size_t>(id)] = static_cast<Index>(dense_.size());
62 dense_.emplace_back(id, std::move(value));
63 return dense_.back().value;
64 }
65
66 template <typename... Args>
67 T& emplace(const Index id, Args&&... args)
68 {
69 ensure_sparse_size(id);
70
71 if (contains(id))
72 {
73 T& existing = dense_[static_cast<std::size_t>(sparse_[static_cast<std::size_t>(id)])].value;
74 existing = T(std::forward<Args>(args)...);
75 return existing;
76 }
77
78 assert_dense_index_available();
79 sparse_[static_cast<std::size_t>(id)] = static_cast<Index>(dense_.size());
80 dense_.emplace_back(id, std::forward<Args>(args)...);
81 return dense_.back().value;
82 }
83
84 bool erase(const Index id)
85 {
86 if (!contains(id))
87 {
88 return false;
89 }
90
91 const Index dense_index = sparse_[static_cast<std::size_t>(id)];
92 const auto last_index = static_cast<Index>(dense_.size() - 1);
93
94 if (dense_index != last_index)
95 {
96 std::swap(dense_[static_cast<std::size_t>(dense_index)], dense_[static_cast<std::size_t>(last_index)]);
97 sparse_[static_cast<std::size_t>(dense_[static_cast<std::size_t>(dense_index)].id)] = dense_index;
98 }
99
100 dense_.pop_back();
101 sparse_[static_cast<std::size_t>(id)] = k_invalid_index;
102 return true;
103 }
104
105 T& at(const Index id)
106 {
107 ASSERT(contains(id), "SparseSet id {} is not present", static_cast<std::size_t>(id));
108 return dense_[static_cast<std::size_t>(sparse_[static_cast<std::size_t>(id)])].value;
109 }
110
111 const T& at(const Index id) const
112 {
113 ASSERT(contains(id), "SparseSet id {} is not present", static_cast<std::size_t>(id));
114 return dense_[static_cast<std::size_t>(sparse_[static_cast<std::size_t>(id)])].value;
115 }
116
117 [[nodiscard]] std::vector<T> values() const
118 {
119 std::vector<T> out;
120 out.reserve(dense_.size());
121 for (const auto& entry : dense_)
122 {
123 out.push_back(entry.value);
124 }
125 return out;
126 }
127
128 [[nodiscard]] std::vector<Index> ids() const
129 {
130 std::vector<Index> out;
131 out.reserve(dense_.size());
132 for (const auto& entry : dense_)
133 {
134 out.push_back(entry.id);
135 }
136 return out;
137 }
138
139 void clear()
140 {
141 sparse_.clear();
142 dense_.clear();
143 }
144
145 [[nodiscard]] std::size_t size() const
146 {
147 return dense_.size();
148 }
149
150 [[nodiscard]] bool empty() const
151 {
152 return dense_.empty();
153 }
154
155 auto begin()
156 {
157 return dense_.begin();
158 }
159 auto end()
160 {
161 return dense_.end();
162 }
163 auto begin() const
164 {
165 return dense_.begin();
166 }
167 auto end() const
168 {
169 return dense_.end();
170 }
171
172private:
173 static constexpr Index k_invalid_index = std::numeric_limits<Index>::max();
174
175 static void assert_valid_id(const Index id)
176 {
177 ASSERT(id != k_invalid_index, "SparseSet id {} is reserved as the invalid sentinel", static_cast<std::size_t>(id));
178 }
179
180 void ensure_sparse_size(const Index id)
181 {
182 assert_valid_id(id);
183
184 const std::size_t sparse_index = static_cast<std::size_t>(id);
185 if (sparse_index >= sparse_.size())
186 {
187 sparse_.resize(sparse_index + 1, k_invalid_index);
188 }
189 }
190
191 void assert_dense_index_available() const
192 {
193 ASSERT(dense_.size() < static_cast<std::size_t>(k_invalid_index), "SparseSet dense index overflow");
194 }
195
196 std::vector<Index> sparse_;
197 std::vector<Entry> dense_;
198};
199} // namespace pixelbullet
Provides assertion and panic mechanisms with optional custom formatting.
#define ASSERT(condition,...)
Asserts that a condition is true.
Definition assert.h:142
Definition sparse_set.h:16