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 <array>
11#include <memory>
12#include <type_traits>
13#include <atomic>
14#include <concepts>
15#include <thread>
16
17namespace hi::inline v1 {
18
28template<typename BaseType, std::size_t Size, std::size_t Alignment = alignof(BaseType)>
29class alignas(Alignment) polymorphic_optional {
30public:
31 using base_type = BaseType;
32 using pointer = base_type *;
33 using const_pointer = base_type const *;
34
36
39 static constexpr std::size_t capacity = Size - sizeof(std::atomic<pointer>);
40
43 static constexpr std::size_t alignment = Alignment;
44
46 {
47 reset();
48 }
49
50 constexpr polymorphic_optional() noexcept : _pointer() {}
51 constexpr polymorphic_optional(std::nullopt_t) noexcept : polymorphic_optional() {}
52
53 template<std::derived_from<base_type> Other>
54 constexpr polymorphic_optional(std::unique_ptr<Other, std::default_delete<Other>>&& other) noexcept :
55 _pointer(other.release())
56 {
57 }
58
59 template<std::derived_from<base_type> Other>
60 polymorphic_optional& operator=(std::unique_ptr<Other, std::default_delete<Other>>&& other) noexcept
61 {
62 reset();
63 _pointer.store(other.release(), std::memory_order::release);
64 }
65
66 template<std::derived_from<base_type> Other>
67 polymorphic_optional(Other&& other) noexcept : _pointer(new Other(std::forward<Other>(*other)))
68 {
69 }
70
71 template<std::derived_from<base_type> Other>
72 polymorphic_optional(Other&& other) noexcept requires(sizeof(Other) <= capacity and alignof(Other) <= alignment) :
73 _pointer(new (_buffer.data()) Other(std::forward<Other>(*other)))
74 {
75 }
76
77 template<std::derived_from<base_type> Other>
78 polymorphic_optional& operator=(Other&& other) noexcept
79 {
80 reset();
81 auto *new_ptr = new Other(std::forward<Other>(other));
82 _pointer.store(new_ptr, std::memory_order::release);
83 return *this;
84 }
85
86 template<std::derived_from<base_type> Other>
87 polymorphic_optional& operator=(Other&& other) noexcept requires(sizeof(Other) <= capacity and alignof(Other) <= alignment)
88 {
89 reset();
90 auto *new_ptr = new (_buffer.data()) Other(std::forward<Other>(other));
91 _pointer.store(new_ptr, std::memory_order::release);
92 return *this;
93 }
94
95 [[nodiscard]] bool empty(std::memory_order memory_order = std::memory_order::seq_cst) const noexcept
96 {
97 return _pointer.load(memory_order) == nullptr;
98 }
99
100 operator bool() const noexcept
101 {
102 return not empty();
103 }
104
105 template<typename Value>
106 Value& value(std::memory_order memory_order = std::memory_order::seq_cst)
107 {
108 auto *ptr = _pointer.load(memory_order);
109 if (ptr == nullptr) {
111 }
112 return down_cast<Value&>(*ptr);
113 }
114
115 template<typename Value>
116 Value const& value(std::memory_order memory_order = std::memory_order::seq_cst) const
117 {
118 auto *ptr = _pointer.load(memory_order);
119 if (ptr == nullptr) {
121 }
122 return down_cast<Value const&>(*ptr);
123 }
124
125 base_type *operator->() noexcept
126 {
127 return _pointer.load();
128 }
129
130 base_type const *operator->() const noexcept
131 {
132 return _pointer.load();
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
147 hi_force_inline void reset() noexcept
148 {
149 if (auto *ptr = _pointer.exchange(nullptr, std::memory_order::acquire)) {
150 if (equal_ptr(ptr, this)) {
151 std::destroy_at(ptr);
152 } else {
153 delete ptr;
154 }
155 }
156 };
157
158 template<typename Value, typename... Args>
159 hi_force_inline Value& emplace(Args&&...args) noexcept
160 {
161 reset();
162
163 if constexpr (sizeof(Value) <= capacity and alignof(Value) <= alignment) {
164 // Overwrite the buffer with the new slot.
165 auto new_ptr = new (_buffer.data()) Value(std::forward<Args>(args)...);
166 hi_axiom(equal_ptr(new_ptr, this));
167
168 _pointer.store(new_ptr, std::memory_order::release);
169 return *new_ptr;
170
171 } else {
172 // We need a heap allocated pointer with a fully constructed object
173 // Lets do this ahead of time to let another thread have some time
174 // to release the ring-buffer-slot.
175 hilet new_ptr = new Value(std::forward<Args>(args)...);
176 hi_axiom(new_ptr != nullptr);
177
178 _pointer.store(new_ptr, std::memory_order::release);
179 return *new_ptr;
180 }
181 }
182
189 template<typename Func>
190 hi_force_inline auto invoke_and_reset(Func&& func) noexcept
191 {
192 using func_result = decltype(std::declval<Func>()(std::declval<base_type&>()));
193 using result_type = std::conditional_t<std::is_same_v<func_result, void>, bool, std::optional<func_result>>;
194
195 // Check if the emplace has finished.
196 if (auto ptr = _pointer.load(std::memory_order::acquire)) {
197 if constexpr (std::is_same_v<func_result, void>) {
198 if (equal_ptr(ptr, this)) {
199 std::forward<Func>(func)(*ptr);
200 std::destroy_at(ptr);
201
202 // Empty after destroying the value.
203 _pointer.store(nullptr, std::memory_order::release);
204 return true;
205
206 } else {
207 // Since the object is on the heap, we can empty this immediately.
208 _pointer.store(nullptr, std::memory_order::release);
209
210 std::forward<Func>(func)(*ptr);
211 delete ptr;
212 return true;
213 }
214
215 } else {
216 if (equal_ptr(ptr, this)) {
217 auto result = std::forward<Func>(func)(*ptr);
218 std::destroy_at(ptr);
219
220 // Empty after destroying the value.
221 _pointer.store(nullptr, std::memory_order::release);
222 return result_type{std::move(result)};
223
224 } else {
225 // Since the object is on the heap, we can empty this immediately.
226 _pointer.store(nullptr, std::memory_order::release);
227
228 auto result = std::forward<Func>(func)(*ptr);
229 delete ptr;
230 return result_type{std::move(result)};
231 }
232 }
233
234 } else {
235 return result_type{};
236 }
237 }
238
246 template<typename Value, typename Func, typename... Args>
247 hi_force_inline auto wait_emplace_and_invoke(Func&& func, Args&&...args) noexcept
248 {
249 using func_result = decltype(std::declval<Func>()(std::declval<Value&>()));
250
251 if constexpr (sizeof(Value) <= capacity and alignof(Value) <= alignment) {
252 // Wait until the _pointer is a nullptr.
253 // And acquire the buffer to start overwriting it.
254 // There are no other threads that will make this non-null afterwards.
255 while (_pointer.load(std::memory_order_acquire)) {
256 // If we get here, that would suck, but nothing to do about it.
257 [[unlikely]] contended();
258 }
259
260 // Overwrite the buffer with the new slot.
261 auto new_ptr = new (_buffer.data()) Value(std::forward<Args>(args)...);
262 hi_axiom(equal_ptr(new_ptr, this));
263
264 if constexpr (std::is_same_v<func_result, void>) {
265 // Call the function on the newly created message.
266 std::forward<Func>(func)(*new_ptr);
267
268 // Release the buffer for reading.
269 _pointer.store(new_ptr, std::memory_order::release);
270
271 } else {
272 // Call the function on the newly created message
273 auto tmp = std::forward<Func>(func)(*new_ptr);
274
275 // Release the buffer for reading.
276 _pointer.store(new_ptr, std::memory_order::release);
277 return tmp;
278 }
279
280 } else {
281 // We need a heap allocated pointer with a fully constructed object
282 // Lets do this ahead of time to let another thread have some time
283 // to release the ring-buffer-slot.
284 hilet new_ptr = new Value(std::forward<Args>(args)...);
285 hi_axiom(new_ptr != nullptr);
286
287 // Wait until the slot.pointer is a nullptr.
288 // We don't need to acquire since we wrote into a new heap location.
289 // There are no other threads that will make this non-null afterwards.
290 while (_pointer.load(std::memory_order::relaxed)) {
291 // If we get here, that would suck, but nothing to do about it.
292 [[unlikely]] contended();
293 }
294
295 if constexpr (std::is_same_v<func_result, void>) {
296 // Call the function on the newly created message
297 std::forward<Func>(func)(*new_ptr);
298
299 // Release the heap for reading.
300 _pointer.store(new_ptr, std::memory_order::release);
301
302 } else {
303 // Call the function on the newly created message
304 auto tmp = std::forward<Func>(func)(*new_ptr);
305
306 // Release the heap for reading.
307 _pointer.store(new_ptr, std::memory_order::release);
308 return tmp;
309 }
310 }
311 }
312
313private:
318
325 std::atomic<pointer> _pointer;
326
327 hi_no_inline void contended() noexcept
328 {
329 using namespace std::chrono_literals;
330
331 // If we get here, that would suck, but nothing to do about it.
332 ++global_counter<"polymorphic_optional:contended">;
334 }
335};
336
337} // namespace hi::inline v1
#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:29
hi_force_inline void reset() noexcept
Destroys the contained value, otherwise has no effect.
Definition polymorphic_optional.hpp:147
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:247
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:190
Definition concepts.hpp:35
T forward(T... args)
T move(T... args)
T sleep_for(T... args)