libdonut  2.3.2
Application framework for cross-platform game development in C++20
LinearBuffer.hpp
Go to the documentation of this file.
1 #ifndef DONUT_LINEAR_BUFFER_HPP
2 #define DONUT_LINEAR_BUFFER_HPP
3 
5 
6 #include <algorithm> // std::max
7 #include <array> // std::array
8 #include <cassert> // assert
9 #include <cstddef> // std::size_t, std::byte
10 #include <cstdint> // std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t
11 #include <cstring> // std::memcpy
12 #include <functional> // std::invoke
13 #include <memory> // std::unique_ptr, std::make_unique, std::align
14 #include <new> // std::launder
15 #include <span> // std::span
16 #include <type_traits> // std::is_..._v, std::remove_..._t, std::false_type, std::true_type, std::integral_constant, std::common_type_t
17 #include <utility> // std::forward, std::declval, std::...index_sequence, std::in_place_index...
18 #include <vector> // std::vector
19 
20 namespace donut {
21 
22 template <typename... Ts>
23 class LinearBuffer;
24 
25 namespace detail {
26 
27 template <typename T, std::size_t Index, typename... Ts>
28 struct LinearBufferIndexImpl;
29 
30 template <typename T, std::size_t Index, typename First, typename... Rest>
31 struct LinearBufferIndexImpl<T, Index, First, Rest...> : LinearBufferIndexImpl<T, Index + 1, Rest...> {};
32 
33 template <typename T, std::size_t Index, typename... Rest>
34 struct LinearBufferIndexImpl<T, Index, T, Rest...> : std::integral_constant<std::size_t, Index> {};
35 
36 template <typename T>
37 struct LinearBufferMinElementSize : std::integral_constant<std::size_t, sizeof(T)> {};
38 
39 template <typename T>
40 struct LinearBufferMinElementSize<T[]> : std::integral_constant<std::size_t, sizeof(std::size_t)> {};
41 
42 template <typename T>
43 struct LinearBufferVisitorParameterType {
44  using type = const T;
45 };
46 
47 template <typename T>
48 struct LinearBufferVisitorParameterType<T[]> {
49  using type = std::span<const T>;
50 };
51 
52 } // namespace detail
53 
55 template <typename T, typename B>
56 struct linear_buffer_has_alternative;
57 
58 template <typename T, typename First, typename... Rest>
59 struct linear_buffer_has_alternative<T, LinearBuffer<First, Rest...>> : linear_buffer_has_alternative<T, LinearBuffer<Rest...>> {};
60 
61 template <typename T>
62 struct linear_buffer_has_alternative<T, LinearBuffer<>> : std::false_type {};
63 
64 template <typename T, typename... Rest>
65 struct linear_buffer_has_alternative<T, LinearBuffer<T, Rest...>> : std::true_type {};
67 
68 template <typename T, typename B>
69 inline constexpr bool linear_buffer_has_alternative_v = linear_buffer_has_alternative<T, B>::value;
70 
72 template <typename T, typename B>
73 struct linear_buffer_index;
74 
75 template <typename T, typename... Ts>
76 struct linear_buffer_index<T, LinearBuffer<Ts...>> : detail::LinearBufferIndexImpl<T, 0, Ts...> {};
78 
79 template <typename T, typename B>
80 inline constexpr std::size_t linear_buffer_index_v = linear_buffer_index<T, B>::value;
81 
83 template <std::size_t Index, typename B>
84 struct linear_buffer_alternative;
85 
86 template <std::size_t Index, typename First, typename... Rest>
87 struct linear_buffer_alternative<Index, LinearBuffer<First, Rest...>> : linear_buffer_alternative<Index - 1, LinearBuffer<Rest...>> {};
88 
89 template <typename T, typename... Rest>
90 struct linear_buffer_alternative<0, LinearBuffer<T, Rest...>> {
91  using type = T;
92 };
94 
95 template <std::size_t Index, typename B>
96 using linear_buffer_alternative_t = typename linear_buffer_alternative<Index, B>::type;
97 
99 template <typename B>
100 struct linear_buffer_size;
101 
102 template <typename... Ts>
103 struct linear_buffer_size<LinearBuffer<Ts...>> : std::integral_constant<std::size_t, sizeof...(Ts)> {};
105 
106 template <typename B>
107 inline constexpr std::size_t linear_buffer_size_v = linear_buffer_size<B>::value;
108 
109 template <typename... Ts>
111 public:
112  static_assert((std::is_trivially_copyable_v<std::remove_extent_t<Ts>> && ...), "LinearBuffer requires all element types to be trivially copyable.");
113 
114  // clang-format off
115  using index_type =
116  std::conditional_t<sizeof...(Ts) < 255ull, std::uint8_t,
117  std::conditional_t<sizeof...(Ts) < 65535ull, std::uint16_t,
118  std::conditional_t<sizeof...(Ts) < 4294967295ull, std::uint32_t,
119  std::uint64_t>>>;
120  // clang-format on
121 
122  static constexpr index_type npos = sizeof...(Ts);
123 
124  explicit LinearBuffer(LinearMemoryResource* memoryResource, std::size_t nextChunkSize = 64) noexcept
125  : memoryResource(memoryResource)
126  , nextChunkSize(std::max(nextChunkSize, MIN_CHUNK_SIZE)) {
127  assert(memoryResource);
128  }
129 
130  template <typename T>
131  void push_back(const T& value) requires(!std::is_unbounded_array_v<T> && linear_buffer_has_alternative_v<T, LinearBuffer>) {
132  constexpr std::size_t HEADER_SIZE = sizeof(index_type);
133  constexpr std::size_t requiredSize = HEADER_SIZE + sizeof(T) + sizeof(index_type) + sizeof(std::byte*);
134  const std::size_t remainingMemorySize = static_cast<std::size_t>(remainingMemoryEnd - remainingMemoryBegin);
135  if (remainingMemorySize < requiredSize) {
136  [[unlikely]];
137  const std::size_t newChunkSize = std::max(requiredSize, nextChunkSize);
138  if (head) {
139  assert(remainingMemorySize >= sizeof(index_type) + sizeof(std::byte*));
140  std::byte* const oldChunkTail = remainingMemoryBegin;
141  std::byte* const newChunk = allocateChunk(newChunkSize);
142  static_assert(sizeof(npos) == sizeof(index_type));
143  std::memcpy(oldChunkTail, &npos, sizeof(index_type));
144  std::memcpy(oldChunkTail + sizeof(index_type), &newChunk, sizeof(std::byte*));
145  } else {
146  head = allocateChunk(newChunkSize);
147  }
148  }
149  constexpr index_type index = linear_buffer_index_v<T, LinearBuffer>;
151  std::memcpy(remainingMemoryBegin + HEADER_SIZE, &value, sizeof(T));
152  remainingMemoryBegin += HEADER_SIZE + sizeof(T);
153  }
154 
155  template <typename T, typename... Args>
156  void emplace_back(Args&&... args) requires(!std::is_unbounded_array_v<T> && linear_buffer_has_alternative_v<T, LinearBuffer> && std::is_constructible_v<T, Args...>) {
157  return push_back<T>(T{std::forward<Args>(args)...});
158  }
159 
160  template <typename T>
161  std::span<const T> append(std::span<const T> values) requires(linear_buffer_has_alternative_v<T[], LinearBuffer>) {
162  constexpr std::size_t HEADER_SIZE = sizeof(index_type) + sizeof(std::size_t);
163  const std::size_t remainingMemorySize = static_cast<std::size_t>(remainingMemoryEnd - remainingMemoryBegin);
164  void* alignedPointer = remainingMemoryBegin + HEADER_SIZE;
165  const std::size_t minRequiredSize = HEADER_SIZE + sizeof(index_type) + sizeof(std::byte*);
167  if (remainingMemorySize < minRequiredSize || (!values.empty() && !std::align(alignof(T), values.size_bytes(), alignedPointer, space))) {
168  [[unlikely]];
169  const std::size_t requiredSize = HEADER_SIZE + alignof(T) - 1 + values.size_bytes() + sizeof(index_type) + sizeof(std::byte*);
170  const std::size_t newChunkSize = std::max(requiredSize, nextChunkSize);
171  if (head) {
172  assert(remainingMemorySize >= sizeof(index_type) + sizeof(std::byte*));
173  std::byte* const oldChunkTail = remainingMemoryBegin;
174  std::byte* const newChunk = allocateChunk(newChunkSize);
175  static_assert(sizeof(npos) == sizeof(index_type));
176  std::memcpy(oldChunkTail, &npos, sizeof(index_type));
177  std::memcpy(oldChunkTail + sizeof(index_type), &newChunk, sizeof(std::byte*));
178  } else {
179  head = allocateChunk(newChunkSize);
180  }
181  alignedPointer = remainingMemoryBegin + HEADER_SIZE;
182  space = static_cast<std::size_t>(remainingMemoryEnd - remainingMemoryBegin) - minRequiredSize;
183  [[maybe_unused]] void* const aligned = std::align(alignof(T), values.size_bytes(), alignedPointer, space);
184  assert(aligned);
185  }
186  constexpr index_type index = linear_buffer_index_v<T[], LinearBuffer>;
187  const std::size_t count = values.size();
189  std::memcpy(remainingMemoryBegin + sizeof(index_type), &count, sizeof(std::size_t));
190  std::memcpy(alignedPointer, values.data(), values.size_bytes());
191  remainingMemoryBegin = static_cast<std::byte*>(alignedPointer) + values.size_bytes();
192  return std::span<const T>{reinterpret_cast<const T*>(alignedPointer), count};
193  }
194 
195  template <typename Visitor>
196  auto visit(Visitor&& visitor) const {
197  using R = std::common_type_t<decltype(std::invoke(std::forward<Visitor>(visitor), std::declval<typename detail::LinearBufferVisitorParameterType<Ts>::type>()))...>;
198  const std::byte* const end = remainingMemoryBegin;
199  for (const std::byte* pointer = head; pointer != end;) {
201  std::memcpy(&index, pointer, sizeof(index_type));
202  pointer += sizeof(index_type);
203  const auto apply = [&]<std::size_t Index>(std::in_place_index_t<Index>) -> void {
205  if constexpr (std::is_unbounded_array_v<RawType>) {
206  using T = std::remove_extent_t<RawType>;
207  std::size_t count;
208  std::memcpy(&count, pointer, sizeof(std::size_t));
209  pointer += sizeof(std::size_t);
210  if constexpr (alignof(T) > 1) {
211  if (count > 0) {
212  void* alignedPointer = const_cast<void*>(static_cast<const void*>(pointer));
213  std::size_t remainingMemorySize = end - pointer;
214  [[maybe_unused]] void* const aligned = std::align(alignof(T), count * sizeof(T), alignedPointer, remainingMemorySize);
215  assert(aligned);
216  pointer = static_cast<const std::byte*>(alignedPointer);
217  }
218  }
219  const std::span<const T> values{reinterpret_cast<const T*>(pointer), count};
220  if constexpr (std::is_void_v<R>) {
221  std::invoke(std::forward<Visitor>(visitor), values);
222  pointer += count * sizeof(T);
223  } else {
224  if (std::invoke(std::forward<Visitor>(visitor), values)) {
225  pointer += count * sizeof(T);
226  } else {
227  pointer = end;
228  }
229  }
230  } else {
231  using T = RawType;
232  alignas(T) std::array<std::byte, sizeof(T)> storage;
233  std::memcpy(storage.data(), pointer, sizeof(T));
234  const T& value = *std::launder(reinterpret_cast<const T*>(storage.data()));
235  if constexpr (std::is_void_v<R>) {
236  std::invoke(std::forward<Visitor>(visitor), value);
237  pointer += sizeof(T);
238  } else {
239  if (std::invoke(std::forward<Visitor>(visitor), value)) {
240  pointer += sizeof(T);
241  } else {
242  pointer = end;
243  }
244  }
245  }
246  };
247  [&]<std::size_t... Indices>(std::index_sequence<Indices...>) -> void {
248  if (!(((index == Indices) ? (apply(std::in_place_index<Indices>), true) : false) || ...)) {
249  [[unlikely]];
250  std::memcpy(&pointer, pointer, sizeof(std::byte*));
251  }
252  }(std::make_index_sequence<sizeof...(Ts)>{});
253  }
254  if constexpr (!std::is_void_v<R>) {
255  return true;
256  }
257  }
258 
259 private:
260  static constexpr std::size_t MIN_CHUNK_SIZE = std::max({(sizeof(index_type) + detail::LinearBufferMinElementSize<Ts>::value + sizeof(index_type) + sizeof(std::byte*))...});
261 
262  [[nodiscard]] std::byte* allocateChunk(std::size_t newChunkSize) {
263  assert(memoryResource);
264  std::byte* const newChunk = static_cast<std::byte*>(memoryResource->allocate(newChunkSize, 1));
265  remainingMemoryBegin = newChunk;
266  remainingMemoryEnd = newChunk + newChunkSize;
267  nextChunkSize += nextChunkSize / 2;
268  return newChunk;
269  }
270 
271  LinearMemoryResource* memoryResource;
272  std::byte* head = nullptr;
273  std::byte* remainingMemoryBegin = nullptr;
274  std::byte* remainingMemoryEnd = nullptr;
275  std::size_t nextChunkSize;
276 };
277 
278 } // namespace donut
279 
280 #endif
Definition: LinearBuffer.hpp:110
void emplace_back(Args &&... args) requires(!std
Definition: LinearBuffer.hpp:156
auto visit(Visitor &&visitor) const
Definition: LinearBuffer.hpp:196
const std::size_t minRequiredSize
Definition: LinearBuffer.hpp:165
LinearBuffer(LinearMemoryResource *memoryResource, std::size_t nextChunkSize=64) noexcept
Definition: LinearBuffer.hpp:124
remainingMemoryBegin
Definition: LinearBuffer.hpp:191
std::memcpy(remainingMemoryBegin, &index, sizeof(index_type))
void * alignedPointer
Definition: LinearBuffer.hpp:164
void push_back(const T &value) requires(!std
Definition: LinearBuffer.hpp:131
const std::size_t count
Definition: LinearBuffer.hpp:187
std::size_t space
Definition: LinearBuffer.hpp:166
std::conditional_t< sizeof...(Ts)< 255ull, std::uint8_t, std::conditional_t< sizeof...(Ts)< 65535ull, std::uint16_t, std::conditional_t< sizeof...(Ts)< 4294967295ull, std::uint32_t, std::uint64_t > >> index_type
Definition: LinearBuffer.hpp:119
static constexpr index_type npos
Definition: LinearBuffer.hpp:122
std::span< const T > append(std::span< const T > values) requires(linear_buffer_has_alternative_v< T[]
const std::size_t remainingMemorySize
Definition: LinearBuffer.hpp:163
constexpr index_type index
Definition: LinearBuffer.hpp:186
Definition: LinearAllocator.hpp:15
void * allocate(std::size_t size, std::size_t alignment)
Definition: LinearAllocator.hpp:25
Definition: Application.hpp:9
constexpr bool linear_buffer_has_alternative_v
Definition: LinearBuffer.hpp:69
constexpr std::size_t linear_buffer_size_v
Definition: LinearBuffer.hpp:107
typename linear_buffer_alternative< Index, B >::type linear_buffer_alternative_t
Definition: LinearBuffer.hpp:96
constexpr std::size_t linear_buffer_index_v
Definition: LinearBuffer.hpp:80