jubilant-funicular
SlotMap.h
1 #ifndef NTA_SLOTMAP_H_INCLUDED
2 #define NTA_SLOTMAP_H_INCLUDED
3 
4 #include "nta/Option.h"
5 #include "nta/IDFactory.h"
6 #include "nta/format.h"
7 #include "nta/utils.h"
8 
9 namespace nta {
10  namespace utils {
12  template<typename IndexType = std::size_t, typename GenType = uint16_t>
13  struct SlotMapKey {
14  using index_type = IndexType;
15  using gen_type = GenType;
16 
17  bool operator==(const SlotMapKey<IndexType, GenType> rhs) const {
18  return idx == rhs.idx && gen == rhs.gen;
19  }
20  bool operator!=(const SlotMapKey<IndexType, GenType> rhs) const {
21  return !(*this == rhs);
22  }
23 
24  index_type idx;
25  gen_type gen;
26  };
27  template<typename IndexType, typename GenType>
28  struct Formatter<SlotMapKey<IndexType, GenType>> {
29  std::string operator()(const SlotMapKey<IndexType, GenType>& arg) {
30  return format("{ .idx = {}, .gen = {} }", arg.idx, arg.gen);
31  }
32  };
33  template<typename IndexType, typename GenType>
38  template<typename IndexType, typename GenType>
39  class IDFactory<GenIndex<IndexType, GenType>> {
40  private:
43  std::vector<value_type> m_free;
45  IndexType m_last_id;
46  public:
47  IDFactory() : m_last_id(NTA_INVALID_ID) {}
49  void clear() { m_free.clear(); m_last_id = NTA_INVALID_ID; }
51  void reset() { clear(); }
52 
54  value_type gen_id();
58  void free_id(value_type id);
60  void free(value_type id) { free_id(id); }
62  bool is_free(value_type id) const;
64  bool is_in_use(value_type id) const { return id.idx != NTA_INVALID_ID && !is_free(id); }
65 
67  std::size_t get_count() const { return (std::size_t)m_last_id - m_free.size() - NTA_INVALID_ID; }
69  IndexType get_last_id() const { return m_last_id; }
70 
72  value_type operator()() { return gen_id(); }
73  };
74  template<>
75  template<typename IndexType, typename GenType>
77  if (m_free.empty()) {
78  return { .idx = ++m_last_id, .gen = 0 };
79  } else {
80  value_type ret = m_free.back();
81  m_free.pop_back();
82  ret.gen++;
83  return ret;
84  }
85  }
86  template<>
87  template<typename IndexType, typename GenType>
89  m_free.push_back(id);
90  }
91  template<>
92  template<typename IndexType, typename GenType>
94  if (id.idx > m_last_id) return true;
95  for (const auto& id2 : m_free) {
96  if (id == id2) return true;
97  }
98  return false;
99  }
104  template<typename T, typename IndexType = std::size_t, typename GenType = uint16_t>
105  class SlotMap {
106  public:
107  using value_type = T;
108  using iterator = T*;
109  using const_iterator = const T*;
110  using reverse_iterator = std::reverse_iterator<iterator>;
111  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
112  using reference = T&;
113  using const_reference = const T&;
114  using index_type = IndexType;
115  using size_type = IndexType;
116  using gen_type = GenType;
118  using storage_type = typename std::aligned_storage_t<sizeof(T), alignof(T)>;
119  private:
120  void grow() { reserve(m_cap == 0 ? DEFAULT_CAP : m_cap << 1); }
121  void remove_impl(Key k, bool gen_bump);
122  Option<reference> at_impl(Key k) const;
123  reference _get_raw(index_type idx) const {
124  return reinterpret_cast<T*>(std::launder(&m_data[0]))[idx];
125  }
126 
127  Key* m_slots{};
128  storage_type* m_data{};
129  index_type* m_slot_idxes{};
130 
131  size_type m_size{};
132  size_type m_cap{};
133 
134  index_type m_free_head{};
135 
136  constexpr static size_type DEFAULT_CAP = 4;
137  public:
138  SlotMap() {}
139  ~SlotMap();
140 
141  size_type size() const { return m_size; }
142  size_type capacity() const { return m_cap; }
143  size_type cap() const { return m_cap; }
144  bool is_empty() const { return m_size == 0; }
145  bool empty() const { return is_empty(); }
146 
147  Option<reference> at(Key k) { return at_impl(k); }
148  Option<const_reference> at(Key k) const { return at_impl(k); }
149  Option<reference> operator[](Key k) { return at(k); }
150  Option<const_reference> operator[](Key k) const { return at(k); }
151  T& at_unsafe(Key k) { return at(k).unwrap(); }
152  const T& at_unsafe(Key k) const { return at(k).unwrap(); }
153 
154  gen_type get_curr_gen(index_type idx) const { return idx < m_cap ? m_slots[idx].gen : 0; }
155  gen_type get_curr_gen(Key k) const { return get_curr_gen(k.idx); }
156  Key get_curr_key(index_type idx) const { return {.idx = idx, .gen = get_curr_gen(idx)}; }
157  Key get_curr_key(Key k) const { return get_curr_key(k.idx); }
158  bool is_free(index_type key_idx) const;
159  bool is_free(Key k) const { return is_free(k.idx); }
160 
161  Key add(const T& elem);
162  Key push(const T& elem) { return add(elem); }
163  // This isn't really emplacing
164  template<typename... Args>
165  Key emplace(Args&&... args) { return add(T(std::forward<Args>(args)...)); }
169  bool insert(Key k, const T& elem);
170  template<typename... Args>
171  bool insert_emplace(Key k, Args&&... args);
172 
173  void reserve(size_type new_cap);
174 
175  void remove(Key k);
177  void deactivate(Key k);
178  void erase(Key k) { remove(k); }
179  void clear();
180 
181  iterator begin() { return &_get_raw(0); }
182  const_iterator begin() const { return cbegin(); }
183  iterator end() { return begin() + m_size; }
184  const_iterator end() const { return cend(); }
185  const_iterator cbegin() const {
186  return reinterpret_cast<const T*>(std::launder(&m_data[0]));
187  }
188  const_iterator cend() const { return cbegin() + m_size; }
189  reverse_iterator rbegin() { return reverse_iterator(end()); }
190  const_reverse_iterator rbegin() const { return crbegin(); }
191  reverse_iterator rend() { return reverse_iterator(begin()); }
192  const_reverse_iterator rend() const { return crend(); }
193  const_reverse_iterator crbegin() { return const_reverse_iterator(cend()); }
194  const_reverse_iterator crend() { return const_reverse_iterator(cbegin()); }
195  };
196  // These type names are hideous
197  template<typename T, typename IndexType, typename GenType>
199  std::destroy(m_data, m_data + m_size);
200  std::free(m_data);
201  std::destroy(m_slots, m_slots + m_cap);
202  std::free(m_slots);
203  std::destroy(m_slot_idxes, m_slot_idxes + m_cap);
204  std::free(m_slot_idxes);
205  m_size = m_cap = m_free_head = 0;
206  }
207  template<typename T, typename IndexType, typename GenType>
208  bool SlotMap<T, IndexType, GenType>::is_free(index_type key_idx) const {
209  if (m_size == m_cap) return false;
210  for (index_type idx = m_free_head;; idx = m_slots[idx].idx) {
211  if (idx == key_idx) return true;
212  if (idx == m_slots[idx].idx) break;
213  }
214  return false;
215  }
216  template<typename T, typename IndexType, typename GenType>
217  Option<T&> SlotMap<T, IndexType, GenType>::at_impl(SlotMap<T, IndexType, GenType>::Key k) const {
218  if (k.idx >= m_cap) return Option<T&>::none();
219  const Key& key = m_slots[k.idx];
220  if (key.gen != k.gen) return Option<T&>::none();
221  if (is_free(k.idx)) return Option<T&>::none();
222  return Option<T&>::some(_get_raw(key.idx));
223  }
224  template<typename T, typename IndexType, typename GenType>
225  void SlotMap<T, IndexType, GenType>::reserve(size_type new_cap) {
226  if (new_cap <= m_cap) return;
227 
228  storage_type* new_data;
229  Key* new_slots;
230  index_type* new_idxes;
231 
232  new_data = static_cast<storage_type*>(std::aligned_alloc(alignof(T),
233  sizeof(T) * new_cap));
234  new_slots = static_cast<Key*>(std::aligned_alloc(alignof(Key),
235  sizeof(Key) * new_cap));
236  new_idxes = static_cast<index_type*>(std::aligned_alloc(alignof(index_type),
237  sizeof(index_type) * new_cap));
238 
239  std::uninitialized_copy(m_data, m_data + m_size, new_data);
240  std::uninitialized_copy(m_slots, m_slots + m_cap, new_slots);
241  std::uninitialized_copy(m_slot_idxes, m_slot_idxes + m_size, new_idxes);
242 
243  std::destroy(m_data, m_data + m_size);
244  std::free(m_data);
245  std::destroy(m_slots, m_slots + m_cap);
246  std::free(m_slots);
247  std::destroy(m_slot_idxes, m_slot_idxes + m_size);
248  std::free(m_slot_idxes);
249 
250  m_data = new_data;
251  m_slots = new_slots;
252  m_slot_idxes = new_idxes;
253 
254  for (index_type idx = m_cap; idx < new_cap; idx++) {
255  m_slots[idx].gen = 0;
256  // end of free list points to itself
257  m_slots[idx].idx = std::min(idx+1, new_cap-1);
258  }
259  if (m_size == m_cap) {
260  m_free_head = m_cap;
261  } else for (index_type idx = m_free_head;; idx = m_slots[idx].idx) {
262  if (idx == m_slots[idx].idx) {
263  m_slots[idx].idx = m_cap;
264  break;
265  }
266  }
267  m_cap = new_cap;
268  }
269  template<typename T, typename IndexType, typename GenType>
270  template<typename... Args>
271  bool SlotMap<T, IndexType, GenType>::insert_emplace(Key k, Args&&... args) {
272  if (m_size == m_cap) return false;
273 
274  index_type prev_idx;
275  for (index_type idx = m_free_head;; idx = m_slots[idx].idx) {
276  if (idx == k.idx) break;
277  if (idx == m_slots[idx].idx) return false;
278  prev_idx = idx;
279  }
280  if (k.gen != m_slots[k.idx].gen) return false;
281 
282  if (m_size == m_cap) grow();
283  new(&m_data[m_size]) T(std::forward<Args>(args)...);
284 
285  if (k.idx == m_free_head) {
286  m_free_head = m_slots[k.idx].idx;
287  } else if (k.idx == m_slots[k.idx].idx) {
288  m_slots[prev_idx].idx = prev_idx;
289  } else {
290  m_slots[prev_idx].idx = m_slots[k.idx].idx;
291  }
292  m_slots[k.idx].idx = m_size;
293  m_slot_idxes[m_size++] = k.idx;
294  return true;
295  }
296  template<typename T, typename IndexType, typename GenType>
298  return insert_emplace(k, elem);
299  }
300  template<typename T, typename IndexType, typename GenType>
302  if (m_size == m_cap) grow();
303  new(&m_data[m_size]) T(elem);
304 
305  index_type key_idx = m_free_head;
306  m_free_head = m_slots[key_idx].idx;
307 
308  m_slots[key_idx].idx = m_size;
309  m_slot_idxes[m_size++] = key_idx;
310  return {.idx = key_idx, .gen = m_slots[key_idx].gen};
311  }
312  template<typename T, typename IndexType, typename GenType>
313  void SlotMap<T, IndexType, GenType>::remove_impl(SlotMap<T, IndexType, GenType>::Key k, bool gen_bump) {
314  if (k.idx >= m_cap) return;
315  Key& key = m_slots[k.idx];
316  if (key.gen != k.gen) return;
317  if (is_free(k.idx)) return;
318 
319  m_size--;
320  _get_raw(key.idx).~T();
321 
322  std::swap(m_data[key.idx], m_data[m_size]);
323 
324  m_slots[m_slot_idxes[m_size]].idx = key.idx;
325  m_slot_idxes[key.idx] = m_slot_idxes[m_size];
326 
327  key.idx = m_free_head;
328  if (gen_bump) key.gen++;
329  m_free_head = k.idx;
330  }
331  template<typename T, typename IndexType, typename GenType>
332  void SlotMap<T, IndexType, GenType>::remove(SlotMap<T, IndexType, GenType>::Key k) {
333  remove_impl(k, true);
334  }
335  template<typename T, typename IndexType, typename GenType>
337  remove_impl(k, false);
338  }
339  template<typename T, typename IndexType, typename GenType>
341  for (index_type i = 0; i < m_cap; i++) remove({.idx = i, .gen = m_slots[i].gen});
342  }
343  }
344 }
345 
346 namespace std {
347  template<typename IndexType, typename GenType>
348  struct hash<nta::utils::SlotMapKey<IndexType, GenType>> {
349  std::size_t operator()(const nta::utils::SlotMapKey<IndexType, GenType> key) const {
350  return nta::utils::hash_combine(std::hash<IndexType>()(key.idx),
351  std::hash<GenType>()(key.gen));
352  }
353  };
354 };
355 
356 #endif // NTA_SLOTMAP_H_INCLUDED
nta::utils::hash_combine
std::size_t hash_combine(std::size_t lhs, std::size_t rhs)
Stolen from Boost.
Definition: utils.cpp:26
nta::utils::IDFactory::free_id
void free_id(T id)
Definition: IDFactory.h:59
nta::utils::Formatter
Specialize this struct to use custom types with format.
Definition: format.h:23
nta::utils::IDFactory::is_free
bool is_free(T id) const
Returns whether or not an id is free.
Definition: IDFactory.h:63
nta::utils::Option::some
static Option some(const T &data)
Creates an Option holding some data.
Definition: Option.h:48
nta::utils::SlotMap::deactivate
void deactivate(Key k)
Same thing as remove, but odes not bump the generation.
Definition: SlotMap.h:336
nta::utils::IDFactory< GenIndex< IndexType, GenType > >::reset
void reset()
Resets this to a new IDFactory.
Definition: SlotMap.h:51
nta::utils::IDFactory::m_free
std::vector< T > m_free
IDs that were previously active but have since been freed.
Definition: IDFactory.h:17
nta::utils::IDFactory< GenIndex< IndexType, GenType > >::clear
void clear()
Resets this to a new IDFactory.
Definition: SlotMap.h:49
nta::utils::SlotMap
Definition: SlotMap.h:105
nta::utils::IDFactory< GenIndex< IndexType, GenType > >::get_last_id
IndexType get_last_id() const
Returns m_last_id.
Definition: SlotMap.h:69
nta::utils::IDFactory::m_last_id
T m_last_id
The smallest id that has never been assigned.
Definition: IDFactory.h:19
nta::utils::IDFactory
Class for generating unique (integral) IDs.
Definition: IDFactory.h:14
nta::utils::IDFactory< GenIndex< IndexType, GenType > >::is_in_use
bool is_in_use(value_type id) const
Returns whether or not the id is in use.
Definition: SlotMap.h:64
nta::utils::SlotMap::insert
bool insert(Key k, const T &elem)
Definition: SlotMap.h:297
nta::utils::Option
Definition: Option.h:17
nta::utils::format
std::string format(const std::string &fmt, Args &&... args)
Definition: format.h:88
nta::utils::Option::none
static Option none()
Creates a None variant Option.
Definition: Option.h:50
nta::utils::IDFactory< GenIndex< IndexType, GenType > >::get_count
std::size_t get_count() const
Returns the number of active ids.
Definition: SlotMap.h:67
nta
Definition: Animation2D.h:6
nta::utils::IDFactory< GenIndex< IndexType, GenType > >::m_last_id
IndexType m_last_id
The smallest id that has never been assigned.
Definition: SlotMap.h:45
nta::utils::IDFactory< GenIndex< IndexType, GenType > >::m_free
std::vector< value_type > m_free
IDs that were previously active but have since been freed.
Definition: SlotMap.h:43
nta::utils::IDFactory::gen_id
T gen_id()
Generates a new, unused ID.
Definition: IDFactory.h:49
nta::utils::IDFactory::clear
void clear()
Resets this to a new IDFactory.
Definition: IDFactory.h:23
nta::utils::IDFactory< GenIndex< IndexType, GenType > >::free
void free(value_type id)
calls free_id
Definition: SlotMap.h:60
nta::utils::IDFactory< GenIndex< IndexType, GenType > >::operator()
value_type operator()()
Calls (and returns) gen_id.
Definition: SlotMap.h:72
nta::utils::SlotMapKey
A generational index.
Definition: SlotMap.h:13