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#include <optional>
16#include <chrono>
17
18hi_export_module(hikogui.container.polymorphic_optional);
19
20hi_warning_push();
21// C26432: If you define or delete any default operation in the type '...', define or delete them all (c.21).
22// False positive. The copy/move assignment/constructors are templated
23hi_warning_ignore_msvc(26432);
24// C26495: Variable '...' is uninitialized. Always initialize a member variable (type.6).
25// For performance reasons polymorphic_optional::_buffer must remain uninitialized.
26hi_warning_ignore_msvc(26495);
27// C26403: Reset or explicitly delete an owner<T> pointer 'new_ptr'
28// We can't use std::allocator because we can't hold a size and be compatible with unique_ptr at the same time.
29hi_warning_ignore_msvc(26403);
30
31hi_export namespace hi::inline v1 {
32
42template<typename BaseType, std::size_t Size, std::size_t Alignment = alignof(BaseType)>
43class alignas(Alignment) polymorphic_optional {
44public:
45 using base_type = BaseType;
46 using pointer = base_type *;
47 using const_pointer = base_type const *;
48
50
53 constexpr static std::size_t capacity = Size - sizeof(std::atomic<pointer>);
54
57 constexpr static std::size_t alignment = Alignment;
58
60 {
61 reset();
62 }
63
64 constexpr polymorphic_optional() noexcept = default;
65 constexpr polymorphic_optional(std::nullopt_t) noexcept : polymorphic_optional() {}
66
67 template<std::derived_from<base_type> Other>
68 constexpr polymorphic_optional(std::unique_ptr<Other, std::default_delete<Other>>&& other) noexcept :
69 _pointer(other.release())
70 {
71 }
72
73 template<std::derived_from<base_type> Other>
74 polymorphic_optional& operator=(std::unique_ptr<Other, std::default_delete<Other>>&& other) noexcept
75 {
76 reset();
77 _pointer.store(other.release(), std::memory_order::release);
78 }
79
80 template<std::derived_from<base_type> Other>
81 polymorphic_optional(Other&& other) noexcept : _pointer(new Other(std::forward<Other>(*other)))
82 {
83 }
84
85 template<std::derived_from<base_type> Other>
86 polymorphic_optional(Other&& other) noexcept requires(sizeof(Other) <= capacity and alignof(Other) <= alignment) :
87 _pointer(new (_buffer.data()) Other(std::forward<Other>(*other)))
88 {
89 }
90
91 template<std::derived_from<base_type> Other>
92 polymorphic_optional& operator=(Other&& other) noexcept
93 {
94 reset();
95 auto *new_ptr = new Other(std::forward<Other>(other));
96 _pointer.store(new_ptr, std::memory_order::release);
97 return *this;
98 }
99
100 template<std::derived_from<base_type> Other>
101 polymorphic_optional& operator=(Other&& other) noexcept requires(sizeof(Other) <= capacity and alignof(Other) <= alignment)
102 {
103 reset();
104 auto *new_ptr = new (_buffer.data()) Other(std::forward<Other>(other));
105 _pointer.store(new_ptr, std::memory_order::release);
106 return *this;
107 }
108
109 [[nodiscard]] bool empty(std::memory_order memory_order = std::memory_order::seq_cst) const noexcept
110 {
111 return _pointer.load(memory_order) == nullptr;
112 }
113
114 operator bool() const noexcept
115 {
116 return not empty();
117 }
118
119 template<typename Value>
120 Value& value(std::memory_order memory_order = std::memory_order::seq_cst)
121 {
122 auto *ptr = _pointer.load(memory_order);
123 if (ptr == nullptr) {
125 }
126 return down_cast<Value&>(*ptr);
127 }
128
129 template<typename Value>
130 Value const& value(std::memory_order memory_order = std::memory_order::seq_cst) const
131 {
132 auto *ptr = _pointer.load(memory_order);
133 if (ptr == nullptr) {
135 }
136 return down_cast<Value const&>(*ptr);
137 }
138
139 base_type *operator->() noexcept
140 {
141 return _pointer.load();
142 }
143
144 base_type const *operator->() const noexcept
145 {
146 return _pointer.load();
147 }
148
149 base_type& operator*() noexcept
150 {
151 return *_pointer.load();
152 }
153
154 base_type const& operator*() const noexcept
155 {
156 return *_pointer.load();
157 }
158
161 hi_force_inline void reset() noexcept
162 {
163 if (auto *ptr = _pointer.exchange(nullptr, std::memory_order::acquire)) {
164 if (equal_ptr(ptr, this)) {
165 std::destroy_at(ptr);
166 } else {
167 delete ptr;
168 }
169 }
170 };
171
172 template<typename Value, typename... Args>
173 hi_force_inline Value& emplace(Args&&...args) noexcept
174 {
175 reset();
176
177 if constexpr (sizeof(Value) <= capacity and alignof(Value) <= alignment) {
178 // Overwrite the buffer with the new slot.
179 auto new_ptr = new (_buffer.data()) Value(std::forward<Args>(args)...);
180 hi_axiom(equal_ptr(new_ptr, this));
181
182 _pointer.store(new_ptr, std::memory_order::release);
183 return *new_ptr;
184
185 } else {
186 // We need a heap allocated pointer with a fully constructed object
187 // Lets do this ahead of time to let another thread have some time
188 // to release the ring-buffer-slot.
189 auto const new_ptr = new Value(std::forward<Args>(args)...);
190 hi_assert_not_null(new_ptr);
191
192 _pointer.store(new_ptr, std::memory_order::release);
193 return *new_ptr;
194 }
195 }
196
203 template<typename Func>
204 hi_force_inline auto invoke_and_reset(Func&& func) noexcept
205 {
206 using func_result = decltype(std::declval<Func>()(std::declval<base_type&>()));
207 using result_type = std::conditional_t<std::is_same_v<func_result, void>, bool, std::optional<func_result>>;
208
209 // Check if the emplace has finished.
210 if (auto ptr = _pointer.load(std::memory_order::acquire)) {
211 if constexpr (std::is_same_v<func_result, void>) {
212 if (equal_ptr(ptr, this)) {
213 std::forward<Func>(func)(*ptr);
214 std::destroy_at(ptr);
215
216 // Empty after destroying the value.
217 _pointer.store(nullptr, std::memory_order::release);
218 return true;
219
220 } else {
221 // Since the object is on the heap, we can empty this immediately.
222 _pointer.store(nullptr, std::memory_order::release);
223
224 std::forward<Func>(func)(*ptr);
225 delete ptr;
226 return true;
227 }
228
229 } else {
230 if (equal_ptr(ptr, this)) {
231 auto result = std::forward<Func>(func)(*ptr);
232 std::destroy_at(ptr);
233
234 // Empty after destroying the value.
235 _pointer.store(nullptr, std::memory_order::release);
236 return result_type{std::move(result)};
237
238 } else {
239 // Since the object is on the heap, we can empty this immediately.
240 _pointer.store(nullptr, std::memory_order::release);
241
242 auto result = std::forward<Func>(func)(*ptr);
243 delete ptr;
244 return result_type{std::move(result)};
245 }
246 }
247
248 } else {
249 return result_type{};
250 }
251 }
252
260 template<typename Value, typename Func, typename... Args>
261 hi_force_inline auto wait_emplace_and_invoke(Func&& func, Args&&...args) noexcept
262 {
263 using func_result = decltype(std::declval<Func>()(std::declval<Value&>()));
264
265 if constexpr (sizeof(Value) <= capacity and alignof(Value) <= alignment) {
266 // Wait until the _pointer is a nullptr.
267 // And acquire the buffer to start overwriting it.
268 // There are no other threads that will make this non-null afterwards.
269 if (_pointer.load(std::memory_order_acquire) != nullptr) [[unlikely]] {
270 // If we get here, that would suck, but nothing to do about it.
271 contended_read();
272 }
273
274 // Overwrite the buffer with the new slot.
275 auto const new_ptr = new (_buffer.data()) Value(std::forward<Args>(args)...);
276 hi_assume(new_ptr != nullptr);
277 hi_axiom(equal_ptr(new_ptr, this));
278
279 if constexpr (std::is_same_v<func_result, void>) {
280 // Call the function on the newly created message.
281 std::forward<Func>(func)(*new_ptr);
282
283 // Release the buffer for reading.
284 _pointer.store(new_ptr, std::memory_order::release);
285
286 } else {
287 // Call the function on the newly created message
288 auto tmp = std::forward<Func>(func)(*new_ptr);
289
290 // Release the buffer for reading.
291 _pointer.store(new_ptr, std::memory_order::release);
292 return tmp;
293 }
294
295 } else {
296 // We need a heap allocated pointer with a fully constructed object
297 // Lets do this ahead of time to let another thread have some time
298 // to release the ring-buffer-slot.
299 auto const new_ptr = new Value(std::forward<Args>(args)...);
300 hi_assert_not_null(new_ptr);
301
302 // Wait until the slot.pointer is a nullptr.
303 // We don't need to acquire since we wrote into a new heap location.
304 // There are no other threads that will make this non-null afterwards.
305 if (_pointer.load(std::memory_order::relaxed) != nullptr) [[unlikely]] {
306 // If we get here, that would suck, but nothing to do about it.
307 contended_read();
308 }
309
310 if constexpr (std::is_same_v<func_result, void>) {
311 // Call the function on the newly created message
312 std::forward<Func>(func)(*new_ptr);
313
314 // Release the heap for reading.
315 _pointer.store(new_ptr, std::memory_order::release);
316
317 } else {
318 // Call the function on the newly created message
319 auto tmp = std::forward<Func>(func)(*new_ptr);
320
321 // Release the heap for reading.
322 _pointer.store(new_ptr, std::memory_order::release);
323 return tmp;
324 }
325 }
326 }
327
328private:
333
340 std::atomic<pointer> _pointer = nullptr;
341
342 hi_no_inline void contended_read() noexcept
343 {
344 using namespace std::chrono_literals;
345
346 do {
347 // If we get here, that would suck, but nothing to do about it.
348 //++global_counter<"polymorphic_optional:contended">;
350 } while (_pointer.load(std::memory_order::relaxed) != nullptr);
351 }
352};
353
354} // namespace hi::inline v1
355
356hi_warning_pop();
@ other
The gui_event does not have associated data.
STL namespace.
constexpr matrix2 operator*(matrix2 const &lhs, matrix2 const &rhs) noexcept
Matrix/Matrix multiplication.
Definition transform.hpp:69
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
Polymorphic optional.
Definition polymorphic_optional.hpp:43
hi_force_inline void reset() noexcept
Destroys the contained value, otherwise has no effect.
Definition polymorphic_optional.hpp:161
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:261
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:204
T forward(T... args)
T load(T... args)
T move(T... args)
T sleep_for(T... args)