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