KDBindings API Documentation  1.0.95
genindex_array.h
Go to the documentation of this file.
1 /*
2 This code has been adapted from MIT licensed code, originally by Jeremy burns and available at
3 https://gist.github.com/jaburns/ca72487198832f6203e831133ffdfff4.
4 The original license is provided below:
5 
6 Copyright 2021 Jeremy Burns
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
9 (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
10 publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
11 subject to the following conditions:
12 
13 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14 
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
16 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
17 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
18 IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 */
20 
21 #pragma once
22 
23 #include <functional>
24 #include <vector>
25 #include <cstdint>
26 #include <optional>
27 #include <cassert>
28 #include <limits>
29 #include <stdexcept>
30 #include <string>
31 
32 namespace KDBindings {
33 
34 namespace Private {
35 
36 struct GenerationalIndex {
37  uint32_t index = 0;
38  uint32_t generation = 0;
39 
40  bool operator==(const GenerationalIndex &rhs) const
41  {
42  return index == rhs.index && generation == rhs.generation;
43  }
44 };
45 
46 class GenerationalIndexAllocator
47 {
48  struct AllocatorEntry {
49  bool isLive = false;
50  uint32_t generation = 0;
51  };
52 
53  std::vector<AllocatorEntry> m_entries;
54  std::vector<uint32_t> m_freeIndices;
55 
56 public:
57  GenerationalIndex allocate()
58  {
59  if (m_freeIndices.size() > 0) {
60  uint32_t index = m_freeIndices.back();
61  m_freeIndices.pop_back();
62 
63  m_entries[index].generation += 1;
64  m_entries[index].isLive = true;
65 
66  return { index, m_entries[index].generation };
67  } else {
68  // check that we are still within the bounds of uint32_t
69  // (parentheses added to avoid Windows min/max macros)
70  if (m_entries.size() + 1 >= (std::numeric_limits<uint32_t>::max)()) {
71  throw std::length_error(std::string("Maximum number of values inside GenerationalIndexArray reached: ") + std::to_string(m_entries.size()));
72  }
73 
74  m_entries.push_back({ true, 0 });
75  return { static_cast<uint32_t>(m_entries.size()) - 1, 0 };
76  }
77  }
78 
79  bool deallocate(GenerationalIndex index)
80  {
81  if (isLive(index)) {
82  m_entries[index.index].isLive = false;
83  m_freeIndices.emplace_back(index.index);
84  return true;
85  }
86 
87  return false;
88  }
89 
90  bool isLive(GenerationalIndex index) const noexcept
91  {
92  return index.index < m_entries.size() &&
93  m_entries[index.index].generation == index.generation &&
94  m_entries[index.index].isLive;
95  }
96 };
97 
98 // A GenerationalIndexArray stores elements in contiguous memory just like an std::vector
99 // and also allows items to be retrieved in constant time through indexed access, but it keeps
100 // track of the "version"/generation of values at indices so that it can inform an accessor
101 // when the item at the index it is trying to access is no longer the item that it wants.
102 template<typename T>
103 class GenerationalIndexArray
104 {
105  struct Entry {
106  uint32_t generation;
107  T value;
108  };
109 
110  // TODO: m_entries never shrinks after an entry has been deleted, it might be
111  // a good idea to add a "trim" function at some point if this becomes an issue
112 
113  std::vector<std::optional<Entry>> m_entries;
114  GenerationalIndexAllocator m_allocator;
115 
116 public:
117  // Sets the value at a specific index inside the array
118  void set(const GenerationalIndex index, T &&value)
119  {
120  while (m_entries.size() <= index.index)
121  m_entries.emplace_back(std::nullopt);
122 
123 #ifndef NDEBUG
124  uint32_t previousGeneration = 0;
125 
126  const auto &previousEntry = m_entries[index.index];
127  if (previousEntry)
128  previousGeneration = previousEntry->generation;
129 
130  assert(index.generation >= previousGeneration);
131 #endif
132 
133  m_entries[index.index] = std::optional<Entry>{ { index.generation, std::move(value) } };
134  }
135 
136  // Insert a value at the first free index and get the index back
137  GenerationalIndex insert(T &&value)
138  {
139  const auto index = m_allocator.allocate();
140  set(index, std::move(value));
141  return index;
142  }
143 
144  // Erase the value at the specified index and free up the index again
145  void erase(GenerationalIndex index)
146  {
147  if (m_allocator.deallocate(index))
148  m_entries[index.index] = std::nullopt;
149  }
150 
151  // Get a pointer to the value at the specified index
152  T *get(GenerationalIndex index)
153  {
154  if (index.index >= m_entries.size())
155  return nullptr;
156 
157  auto &entry = m_entries[index.index];
158  if (entry && entry->generation == index.generation) {
159  return &entry->value;
160  }
161 
162  return nullptr;
163  }
164 
165  // Get a const pointer to the value at the specified index
166  const T *get(GenerationalIndex index) const noexcept
167  {
168  return const_cast<const T *>(const_cast<GenerationalIndexArray *>(this)->get(index));
169  }
170 
171  // Erase all the values in the array and thus free up all indices too
172  void clear()
173  {
174  const auto numEntries = entriesSize();
175 
176  for (auto i = decltype(numEntries){ 0 }; i < numEntries; ++i) {
177  const auto index = indexAtEntry(i);
178 
179  if (index != std::nullopt)
180  erase(*index);
181  }
182  }
183 
184  // The number entries currently in the array, not all necessarily correspond to valid indices,
185  // use "indexAtEntry" to translate from an entry index to a optional GenerationalIndex
186  uint32_t entriesSize() const noexcept
187  {
188  // this cast is safe because the allocator checks that we never exceed the capacity of uint32_t
189  return static_cast<uint32_t>(m_entries.size());
190  }
191 
192  // Convert an entry index into a GenerationalIndex, if possible otherwise returns nullopt
193  std::optional<GenerationalIndex> indexAtEntry(uint32_t entryIndex) const
194  {
195  if (entryIndex >= entriesSize())
196  return std::nullopt;
197 
198  const auto &entry = m_entries[entryIndex];
199  if (!entry)
200  return std::nullopt;
201 
202  GenerationalIndex index = { entryIndex, entry->generation };
203 
204  if (m_allocator.isLive(index))
205  return index;
206 
207  return std::nullopt;
208  }
209 };
210 
211 } // namespace Private
212 
213 } // namespace KDBindings
The main namespace of the KDBindings library.
Definition: binding.h:21

© Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
KDBindings
Reactive programming & data binding in C++
https://github.com/KDAB/KDBindings/
Generated by doxygen 1.9.1