HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
polymorphic_optional.hpp
1// Copyright Take Vos 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 "../utility/utility.hpp"
8#include "../macros.hpp"
9#include <array>
10#include <memory>
11#include <type_traits>
12#include <atomic>
13#include <concepts>
14#include <thread>
15
16
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// C26403: Reset or explicitly delete an owner<T> pointer 'new_ptr'
26// We can't use std::allocator because we can't hold a size and be compatible with unique_ptr at the same time.
27hi_warning_ignore_msvc(26403);
28
29namespace hi::inline v1 {
30
40template<typename BaseType, std::size_t Size, std::size_t Alignment = alignof(BaseType)>
41class alignas(Alignment) polymorphic_optional {
42public:
43 using base_type = BaseType;
44 using pointer = base_type *;
45 using const_pointer = base_type const *;
46
48
51 constexpr static std::size_t capacity = Size - sizeof(std::atomic<pointer>);
52
55 constexpr static std::size_t alignment = Alignment;
56
58 {
59 reset();
60 }
61
62 constexpr polymorphic_optional() noexcept = default;
63 constexpr polymorphic_optional(std::nullopt_t) noexcept : polymorphic_optional() {}
64
65 template<std::derived_from<base_type> Other>
66 constexpr polymorphic_optional(std::unique_ptr<Other, std::default_delete<Other>>&& other) noexcept :
67 _pointer(other.release())
68 {
69 }
70
71 template<std::derived_from<base_type> Other>
72 polymorphic_optional& operator=(std::unique_ptr<Other, std::default_delete<Other>>&& other) noexcept
73 {
74 reset();
75 _pointer.store(other.release(), std::memory_order::release);
76 }
77
78 template<std::derived_from<base_type> Other>
79 polymorphic_optional(Other&& other) noexcept : _pointer(new Other(std::forward<Other>(*other)))
80 {
81 }
82
83 template<std::derived_from<base_type> Other>
84 polymorphic_optional(Other&& other) noexcept requires(sizeof(Other) <= capacity and alignof(Other) <= alignment) :
85 _pointer(new (_buffer.data()) Other(std::forward<Other>(*other)))
86 {
87 }
88
89 template<std::derived_from<base_type> Other>
90 polymorphic_optional& operator=(Other&& other) noexcept
91 {
92 reset();
93 auto *new_ptr = new Other(std::forward<Other>(other));
94 _pointer.store(new_ptr, std::memory_order::release);
95 return *this;
96 }
97
98 template<std::derived_from<base_type> Other>
99 polymorphic_optional& operator=(Other&& other) noexcept requires(sizeof(Other) <= capacity and alignof(Other) <= alignment)
100 {
101 reset();
102 auto *new_ptr = new (_buffer.data()) Other(std::forward<Other>(other));
103 _pointer.store(new_ptr, std::memory_order::release);
104 return *this;
105 }
106
107 [[nodiscard]] bool empty(std::memory_order memory_order = std::memory_order::seq_cst) const noexcept
108 {
109 return _pointer.load(memory_order) == nullptr;
110 }
111
112 operator bool() const noexcept
113 {
114 return not empty();
115 }
116
117 template<typename Value>
118 Value& value(std::memory_order memory_order = std::memory_order::seq_cst)
119 {
120 auto *ptr = _pointer.load(memory_order);
121 if (ptr == nullptr) {
123 }
124 return down_cast<Value&>(*ptr);
125 }
126
127 template<typename Value>
128 Value const& value(std::memory_order memory_order = std::memory_order::seq_cst) const
129 {
130 auto *ptr = _pointer.load(memory_order);
131 if (ptr == nullptr) {
133 }
134 return down_cast<Value const&>(*ptr);
135 }
136
137 base_type *operator->() noexcept
138 {
139 return _pointer.load();
140 }
141
142 base_type const *operator->() const noexcept
143 {
144 return _pointer.load();
145 }
146
147 base_type& operator*() noexcept
148 {
149 return *_pointer.load();
150 }
151
152 base_type const& operator*() const noexcept
153 {
154 return *_pointer.load();
155 }
156
159 hi_force_inline void reset() noexcept
160 {
161 if (auto *ptr = _pointer.exchange(nullptr, std::memory_order::acquire)) {
162 if (equal_ptr(ptr, this)) {
163 std::destroy_at(ptr);
164 } else {
165 delete ptr;
166 }
167 }
168 };
169
170 template<typename Value, typename... Args>
171 hi_force_inline Value& emplace(Args&&...args) noexcept
172 {
173 reset();
174
175 if constexpr (sizeof(Value) <= capacity and alignof(Value) <= alignment) {
176 // Overwrite the buffer with the new slot.
177 auto new_ptr = new (_buffer.data()) Value(std::forward<Args>(args)...);
178 hi_axiom(equal_ptr(new_ptr, this));
179
180 _pointer.store(new_ptr, std::memory_order::release);
181 return *new_ptr;
182
183 } else {
184 // We need a heap allocated pointer with a fully constructed object
185 // Lets do this ahead of time to let another thread have some time
186 // to release the ring-buffer-slot.
187 hilet new_ptr = new Value(std::forward<Args>(args)...);
188 hi_assert_not_null(new_ptr);
189
190 _pointer.store(new_ptr, std::memory_order::release);
191 return *new_ptr;
192 }
193 }
194
201 template<typename Func>
202 hi_force_inline auto invoke_and_reset(Func&& func) noexcept
203 {
204 using func_result = decltype(std::declval<Func>()(std::declval<base_type&>()));
205 using result_type = std::conditional_t<std::is_same_v<func_result, void>, bool, std::optional<func_result>>;
206
207 // Check if the emplace has finished.
208 if (auto ptr = _pointer.load(std::memory_order::acquire)) {
209 if constexpr (std::is_same_v<func_result, void>) {
210 if (equal_ptr(ptr, this)) {
211 std::forward<Func>(func)(*ptr);
212 std::destroy_at(ptr);
213
214 // Empty after destroying the value.
215 _pointer.store(nullptr, std::memory_order::release);
216 return true;
217
218 } else {
219 // Since the object is on the heap, we can empty this immediately.
220 _pointer.store(nullptr, std::memory_order::release);
221
222 std::forward<Func>(func)(*ptr);
223 delete ptr;
224 return true;
225 }
226
227 } else {
228 if (equal_ptr(ptr, this)) {
229 auto result = std::forward<Func>(func)(*ptr);
230 std::destroy_at(ptr);
231
232 // Empty after destroying the value.
233 _pointer.store(nullptr, std::memory_order::release);
234 return result_type{std::move(result)};
235
236 } else {
237 // Since the object is on the heap, we can empty this immediately.
238 _pointer.store(nullptr, std::memory_order::release);
239
240 auto result = std::forward<Func>(func)(*ptr);
241 delete ptr;
242 return result_type{std::move(result)};
243 }
244 }
245
246 } else {
247 return result_type{};
248 }
249 }
250
258 template<typename Value, typename Func, typename... Args>
259 hi_force_inline auto wait_emplace_and_invoke(Func&& func, Args&&...args) noexcept
260 {
261 using func_result = decltype(std::declval<Func>()(std::declval<Value&>()));
262
263 if constexpr (sizeof(Value) <= capacity and alignof(Value) <= alignment) {
264 // Wait until the _pointer is a nullptr.
265 // And acquire the buffer to start overwriting it.
266 // There are no other threads that will make this non-null afterwards.
267 while (_pointer.load(std::memory_order_acquire)) {
268 // If we get here, that would suck, but nothing to do about it.
269 [[unlikely]] contended();
270 }
271
272 // Overwrite the buffer with the new slot.
273 hilet new_ptr = new (_buffer.data()) Value(std::forward<Args>(args)...);
274 hi_assume(new_ptr != nullptr);
275 hi_axiom(equal_ptr(new_ptr, this));
276
277 if constexpr (std::is_same_v<func_result, void>) {
278 // Call the function on the newly created message.
279 std::forward<Func>(func)(*new_ptr);
280
281 // Release the buffer for reading.
282 _pointer.store(new_ptr, std::memory_order::release);
283
284 } else {
285 // Call the function on the newly created message
286 auto tmp = std::forward<Func>(func)(*new_ptr);
287
288 // Release the buffer for reading.
289 _pointer.store(new_ptr, std::memory_order::release);
290 return tmp;
291 }
292
293 } else {
294 // We need a heap allocated pointer with a fully constructed object
295 // Lets do this ahead of time to let another thread have some time
296 // to release the ring-buffer-slot.
297 hilet new_ptr = new Value(std::forward<Args>(args)...);
298 hi_assert_not_null(new_ptr);
299
300 // Wait until the slot.pointer is a nullptr.
301 // We don't need to acquire since we wrote into a new heap location.
302 // There are no other threads that will make this non-null afterwards.
303 while (_pointer.load(std::memory_order::relaxed)) {
304 // If we get here, that would suck, but nothing to do about it.
305 [[unlikely]] contended();
306 }
307
308 if constexpr (std::is_same_v<func_result, void>) {
309 // Call the function on the newly created message
310 std::forward<Func>(func)(*new_ptr);
311
312 // Release the heap for reading.
313 _pointer.store(new_ptr, std::memory_order::release);
314
315 } else {
316 // Call the function on the newly created message
317 auto tmp = std::forward<Func>(func)(*new_ptr);
318
319 // Release the heap for reading.
320 _pointer.store(new_ptr, std::memory_order::release);
321 return tmp;
322 }
323 }
324 }
325
326private:
331
338 std::atomic<pointer> _pointer = nullptr;
339
340 hi_no_inline void contended() noexcept
341 {
342 using namespace std::chrono_literals;
343
344 // If we get here, that would suck, but nothing to do about it.
345 //++global_counter<"polymorphic_optional:contended">;
347 }
348};
349
350} // namespace hi::inline v1
351
352hi_warning_pop();
@ other
The gui_event does not have associated data.
STL namespace.
DOXYGEN BUG.
Definition algorithm.hpp:16
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
Polymorphic optional.
Definition polymorphic_optional.hpp:41
hi_force_inline void reset() noexcept
Destroys the contained value, otherwise has no effect.
Definition polymorphic_optional.hpp:159
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:259
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:202
T forward(T... args)
T move(T... args)
T sleep_for(T... args)