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