HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
polymorphic_optional.hpp
1// Copyright Take Vos 2021-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "assert.hpp"
8#include "concepts.hpp"
9#include "memory.hpp"
10#include "required.hpp"
11#include <array>
12#include <memory>
13#include <type_traits>
14#include <atomic>
15#include <concepts>
16#include <thread>
17
18hi_warning_push();
19// C26432: If you define or delete any default operation in the type '...', define or delete them all (c.21).
20// False positive. The copy/move assignment/constructors are templated
21hi_warning_ignore_msvc(26432);
22// C26495: Variable '...' is uninitialized. Always initialize a member variable (type.6).
23// For performance reasons polymorphic_optional::_buffer must remain uninitialized.
24hi_warning_ignore_msvc(26495);
25
26namespace hi::inline v1 {
27
37template<typename BaseType, std::size_t Size, std::size_t Alignment = alignof(BaseType)>
38class alignas(Alignment) polymorphic_optional {
39public:
40 using base_type = BaseType;
41 using pointer = base_type *;
42 using const_pointer = base_type const *;
43
45
48 static constexpr std::size_t capacity = Size - sizeof(std::atomic<pointer>);
49
52 static constexpr std::size_t alignment = Alignment;
53
55 {
56 reset();
57 }
58
59 constexpr polymorphic_optional() noexcept = default;
60 constexpr polymorphic_optional(std::nullopt_t) noexcept : polymorphic_optional() {}
61
62 template<std::derived_from<base_type> Other>
63 constexpr polymorphic_optional(std::unique_ptr<Other, std::default_delete<Other>>&& other) noexcept :
64 _pointer(other.release())
65 {
66 }
67
68 template<std::derived_from<base_type> Other>
69 polymorphic_optional& operator=(std::unique_ptr<Other, std::default_delete<Other>>&& other) noexcept
70 {
71 reset();
72 _pointer.store(other.release(), std::memory_order::release);
73 }
74
75 template<std::derived_from<base_type> Other>
76 polymorphic_optional(Other&& other) noexcept : _pointer(new Other(std::forward<Other>(*other)))
77 {
78 }
79
80 template<std::derived_from<base_type> Other>
81 polymorphic_optional(Other&& other) noexcept requires(sizeof(Other) <= capacity and alignof(Other) <= alignment) :
82 _pointer(new (_buffer.data()) Other(std::forward<Other>(*other)))
83 {
84 }
85
86 template<std::derived_from<base_type> Other>
87 polymorphic_optional& operator=(Other&& other) noexcept
88 {
89 reset();
90 auto *new_ptr = new Other(std::forward<Other>(other));
91 _pointer.store(new_ptr, std::memory_order::release);
92 return *this;
93 }
94
95 template<std::derived_from<base_type> Other>
96 polymorphic_optional& operator=(Other&& other) noexcept requires(sizeof(Other) <= capacity and alignof(Other) <= alignment)
97 {
98 reset();
99 auto *new_ptr = new (_buffer.data()) Other(std::forward<Other>(other));
100 _pointer.store(new_ptr, std::memory_order::release);
101 return *this;
102 }
103
104 [[nodiscard]] bool empty(std::memory_order memory_order = std::memory_order::seq_cst) const noexcept
105 {
106 return _pointer.load(memory_order) == nullptr;
107 }
108
109 operator bool() const noexcept
110 {
111 return not empty();
112 }
113
114 template<typename Value>
115 Value& value(std::memory_order memory_order = std::memory_order::seq_cst)
116 {
117 auto *ptr = _pointer.load(memory_order);
118 if (ptr == nullptr) {
120 }
121 return down_cast<Value&>(*ptr);
122 }
123
124 template<typename Value>
125 Value const& value(std::memory_order memory_order = std::memory_order::seq_cst) const
126 {
127 auto *ptr = _pointer.load(memory_order);
128 if (ptr == nullptr) {
130 }
131 return down_cast<Value const&>(*ptr);
132 }
133
134 base_type *operator->() noexcept
135 {
136 return _pointer.load();
137 }
138
139 base_type const *operator->() const noexcept
140 {
141 return _pointer.load();
142 }
143
144 base_type& operator*() noexcept
145 {
146 return *_pointer.load();
147 }
148
149 base_type const& operator*() const noexcept
150 {
151 return *_pointer.load();
152 }
153
156 hi_force_inline void reset() noexcept
157 {
158 if (auto *ptr = _pointer.exchange(nullptr, std::memory_order::acquire)) {
159 if (equal_ptr(ptr, this)) {
160 std::destroy_at(ptr);
161 } else {
162 delete ptr;
163 }
164 }
165 };
166
167 template<typename Value, typename... Args>
168 hi_force_inline Value& emplace(Args&&...args) noexcept
169 {
170 reset();
171
172 if constexpr (sizeof(Value) <= capacity and alignof(Value) <= alignment) {
173 // Overwrite the buffer with the new slot.
174 auto new_ptr = new (_buffer.data()) Value(std::forward<Args>(args)...);
175 hi_axiom(equal_ptr(new_ptr, this));
176
177 _pointer.store(new_ptr, std::memory_order::release);
178 return *new_ptr;
179
180 } else {
181 // We need a heap allocated pointer with a fully constructed object
182 // Lets do this ahead of time to let another thread have some time
183 // to release the ring-buffer-slot.
184 hilet new_ptr = new Value(std::forward<Args>(args)...);
185 hi_axiom(new_ptr != nullptr);
186
187 _pointer.store(new_ptr, std::memory_order::release);
188 return *new_ptr;
189 }
190 }
191
198 template<typename Func>
199 hi_force_inline auto invoke_and_reset(Func&& func) noexcept
200 {
201 using func_result = decltype(std::declval<Func>()(std::declval<base_type&>()));
202 using result_type = std::conditional_t<std::is_same_v<func_result, void>, bool, std::optional<func_result>>;
203
204 // Check if the emplace has finished.
205 if (auto ptr = _pointer.load(std::memory_order::acquire)) {
206 if constexpr (std::is_same_v<func_result, void>) {
207 if (equal_ptr(ptr, this)) {
208 std::forward<Func>(func)(*ptr);
209 std::destroy_at(ptr);
210
211 // Empty after destroying the value.
212 _pointer.store(nullptr, std::memory_order::release);
213 return true;
214
215 } else {
216 // Since the object is on the heap, we can empty this immediately.
217 _pointer.store(nullptr, std::memory_order::release);
218
219 std::forward<Func>(func)(*ptr);
220 delete ptr;
221 return true;
222 }
223
224 } else {
225 if (equal_ptr(ptr, this)) {
226 auto result = std::forward<Func>(func)(*ptr);
227 std::destroy_at(ptr);
228
229 // Empty after destroying the value.
230 _pointer.store(nullptr, std::memory_order::release);
231 return result_type{std::move(result)};
232
233 } else {
234 // Since the object is on the heap, we can empty this immediately.
235 _pointer.store(nullptr, std::memory_order::release);
236
237 auto result = std::forward<Func>(func)(*ptr);
238 delete ptr;
239 return result_type{std::move(result)};
240 }
241 }
242
243 } else {
244 return result_type{};
245 }
246 }
247
255 template<typename Value, typename Func, typename... Args>
256 hi_force_inline auto wait_emplace_and_invoke(Func&& func, Args&&...args) noexcept
257 {
258 using func_result = decltype(std::declval<Func>()(std::declval<Value&>()));
259
260 if constexpr (sizeof(Value) <= capacity and alignof(Value) <= alignment) {
261 // Wait until the _pointer is a nullptr.
262 // And acquire the buffer to start overwriting it.
263 // There are no other threads that will make this non-null afterwards.
264 while (_pointer.load(std::memory_order_acquire)) {
265 // If we get here, that would suck, but nothing to do about it.
266 [[unlikely]] contended();
267 }
268
269 // Overwrite the buffer with the new slot.
270 auto new_ptr = new (_buffer.data()) Value(std::forward<Args>(args)...);
271 hi_axiom(equal_ptr(new_ptr, this));
272
273 if constexpr (std::is_same_v<func_result, void>) {
274 // Call the function on the newly created message.
275 std::forward<Func>(func)(*new_ptr);
276
277 // Release the buffer for reading.
278 _pointer.store(new_ptr, std::memory_order::release);
279
280 } else {
281 // Call the function on the newly created message
282 auto tmp = std::forward<Func>(func)(*new_ptr);
283
284 // Release the buffer for reading.
285 _pointer.store(new_ptr, std::memory_order::release);
286 return tmp;
287 }
288
289 } else {
290 // We need a heap allocated pointer with a fully constructed object
291 // Lets do this ahead of time to let another thread have some time
292 // to release the ring-buffer-slot.
293 hilet new_ptr = new Value(std::forward<Args>(args)...);
294 hi_axiom(new_ptr != nullptr);
295
296 // Wait until the slot.pointer is a nullptr.
297 // We don't need to acquire since we wrote into a new heap location.
298 // There are no other threads that will make this non-null afterwards.
299 while (_pointer.load(std::memory_order::relaxed)) {
300 // If we get here, that would suck, but nothing to do about it.
301 [[unlikely]] contended();
302 }
303
304 if constexpr (std::is_same_v<func_result, void>) {
305 // Call the function on the newly created message
306 std::forward<Func>(func)(*new_ptr);
307
308 // Release the heap for reading.
309 _pointer.store(new_ptr, std::memory_order::release);
310
311 } else {
312 // Call the function on the newly created message
313 auto tmp = std::forward<Func>(func)(*new_ptr);
314
315 // Release the heap for reading.
316 _pointer.store(new_ptr, std::memory_order::release);
317 return tmp;
318 }
319 }
320 }
321
322private:
327
334 std::atomic<pointer> _pointer = nullptr;
335
336 hi_no_inline void contended() noexcept
337 {
338 using namespace std::chrono_literals;
339
340 // If we get here, that would suck, but nothing to do about it.
341 ++global_counter<"polymorphic_optional:contended">;
343 }
344};
345
346} // namespace hi::inline v1
347
348hi_warning_pop();
This file includes required definitions.
#define hilet
Invariant should be the default for variables.
Definition required.hpp:23
STL namespace.
Definition alignment.hpp:64
Polymorphic optional.
Definition polymorphic_optional.hpp:38
hi_force_inline void reset() noexcept
Destroys the contained value, otherwise has no effect.
Definition polymorphic_optional.hpp:156
hi_force_inline auto wait_emplace_and_invoke(Func &&func, Args &&...args) noexcept
Wait until the optional is empty, emplace a value, then invoke a function on it before committing.
Definition polymorphic_optional.hpp:256
hi_force_inline auto invoke_and_reset(Func &&func) noexcept
Invoke a function on the value if it exists then reset.
Definition polymorphic_optional.hpp:199
Definition concepts.hpp:36
T forward(T... args)
T move(T... args)
T sleep_for(T... args)