HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
scoped_task.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 "required.hpp"
8#include "notifier.hpp"
9#include "counters.hpp"
10#include <coroutine>
11#include <type_traits>
12#include <memory>
13#include <exception>
14
15namespace hi::inline v1 {
16
26template<typename T = void>
28public:
29 using value_type = T;
30
38 using return_value_type = std::variant<std::monostate, std::exception_ptr, value_type>;
41 using notifier_type = notifier<void(value_type)>;
42
43 struct promise_type {
44 notifier_type _notifier;
45 return_value_ptr_type _value_ptr;
46
47 void return_value(std::convertible_to<value_type> auto &&value) noexcept
48 {
49 *_value_ptr = return_value_type{std::in_place_index<2>, hi_forward(value)};
50 }
51
52 void unhandled_exception() noexcept
53 {
54 *_value_ptr = return_value_type{std::in_place_index<1>, std::current_exception()};
55 }
56
57 std::suspend_never final_suspend() noexcept
58 {
59 switch (_value_ptr->index()) {
60 case 1:
61 // We need to trigger the notifier on an exception too.
62 if constexpr (std::is_default_constructible_v<value_type>) {
63 _notifier(value_type{});
64 return {};
65 }
66 hi_no_default();
67 case 2:
68 // Trigger the notifier with the co_return value.
69 _notifier(std::get<2>(*_value_ptr));
70 return {};
71 default: hi_no_default();
72 }
73 }
74
75 scoped_task get_return_object() noexcept
76 {
77 _value_ptr = std::make_shared<return_value_type>();
78 return scoped_task{handle_type::from_promise(*this), _value_ptr};
79 }
80
83 std::suspend_never initial_suspend() noexcept
84 {
85 return {};
86 }
87 };
88
89 using handle_type = std::coroutine_handle<promise_type>;
90
91 scoped_task(handle_type coroutine, const_return_value_ptr_type value_ptr) noexcept :
92 _coroutine(coroutine), _value_ptr(std::move(value_ptr))
93 {
94 }
95
96 ~scoped_task()
97 {
98 if (_value_ptr and not completed()) {
99 hi_axiom(_coroutine);
100 _coroutine.destroy();
101 }
102 }
103
104 scoped_task() = default;
105
106 // scoped_task can not be copied because it tracks if the co-routine must be destroyed by the
107 // shared_ptr to the value shared between scoped_task and the promise.
108 scoped_task(scoped_task const &) = delete;
109 scoped_task &operator=(scoped_task const &) = delete;
110
111 scoped_task(scoped_task &&other) noexcept
112 {
113 _coroutine = std::exchange(other._coroutine, {});
114 _value_ptr = std::exchange(other._value_ptr, {});
115 }
116
117 scoped_task &operator=(scoped_task &&other) noexcept
118 {
119 _coroutine = std::exchange(other._coroutine, {});
120 _value_ptr = std::exchange(other._value_ptr, {});
121 return *this;
122 }
123
126 [[nodiscard]] bool completed() const noexcept
127 {
128 return _value_ptr->index() != 0;
129 }
130
133 explicit operator bool() const noexcept
134 {
135 return completed();
136 }
137
143 [[nodiscard]] value_type const &value() const
144 {
145 switch (_value_ptr->index()) {
146 case 1: std::rethrow_exception(std::get<1>(*_value_ptr));
147 case 2: return std::get<2>(*_value_ptr);
148 default: hi_no_default();
149 }
150 }
151
157 [[nodiscard]] value_type const &operator*() const
158 {
159 return value();
160 }
161
167 notifier_type::token_type subscribe(std::invocable<value_type> auto &&callback) noexcept
168 {
169 return _coroutine.promise()._notifier.subscribe(hi_forward(callback));
170 }
171
172private:
173 // Optional value type
174 handle_type _coroutine;
175 const_return_value_ptr_type _value_ptr;
176};
177
181template<>
182class scoped_task<void> {
183public:
184 using value_type = void;
185
193 using return_value_type = std::variant<std::monostate, std::exception_ptr, std::monostate>;
196 using notifier_type = notifier<void()>;
197
198 struct promise_type {
199 notifier_type _notifier;
200 return_value_ptr_type _value_ptr;
201
202 void return_void() noexcept
203 {
204 *_value_ptr = return_value_type{std::in_place_index<2>};
205 }
206
207 void unhandled_exception() noexcept
208 {
209 *_value_ptr = return_value_type{std::in_place_index<1>, std::current_exception()};
210 }
211
212 std::suspend_never final_suspend() noexcept
213 {
214 switch (_value_ptr->index()) {
215 case 1:
216 // Trigger the notifier on exception.
217 _notifier();
218 return {};
219 case 2:
220 // Trigger the notifier with the co_return value.
221 _notifier();
222 return {};
223 default: hi_no_default();
224 }
225 }
226 scoped_task get_return_object() noexcept
227 {
228 _value_ptr = std::make_shared<return_value_type>();
229 return scoped_task{handle_type::from_promise(*this), _value_ptr};
230 }
231
232 std::suspend_never initial_suspend() noexcept
233 {
234 return {};
235 }
236 };
237
238 using handle_type = std::coroutine_handle<promise_type>;
239
240 scoped_task(handle_type coroutine, const_return_value_ptr_type value_ptr) noexcept :
241 _coroutine(coroutine), _value_ptr(std::move(value_ptr))
242 {
243 }
244
246 {
247 if (_value_ptr and not completed()) {
248 hi_axiom(_coroutine);
249 _coroutine.destroy();
250 }
251 }
252
253 scoped_task() = default;
254 scoped_task(scoped_task const &) = delete;
255 scoped_task &operator=(scoped_task const &) = delete;
256
257 scoped_task(scoped_task &&other) noexcept
258 {
259 _coroutine = std::exchange(other._coroutine, {});
260 _value_ptr = std::exchange(other._value_ptr, {});
261 }
262
263 scoped_task &operator=(scoped_task &&other) noexcept
264 {
265 _coroutine = std::exchange(other._coroutine, {});
266 _value_ptr = std::exchange(other._value_ptr, {});
267 return *this;
268 }
269
273 [[nodiscard]] bool completed() const noexcept
274 {
275 return _value_ptr->index() != 0;
276 }
277
281 explicit operator bool() const noexcept
282 {
283 return completed();
284 }
285
292 void value() const
293 {
294 switch (_value_ptr->index()) {
295 case 1: std::rethrow_exception(std::get<1>(*_value_ptr));
296 case 2: return;
297 default: hi_no_default();
298 }
299 }
300
304 notifier_type::token_type subscribe(std::invocable<> auto &&callback) noexcept
305 {
306 return _coroutine.promise()._notifier.subscribe(hi_forward(callback));
307 }
308
309private:
310 // Optional value type
311 handle_type _coroutine;
312 const_return_value_ptr_type _value_ptr;
313};
314
315} // namespace hi::inline v1
This file includes required definitions.
#define hi_forward(x)
Forward a value, based on the decltype of the value.
Definition required.hpp:29
A scoped_task.
Definition scoped_task.hpp:27
bool completed() const noexcept
Check if the co-routine has completed.
Definition scoped_task.hpp:126
value_type const & operator*() const
Get the return value returned from co_return.
Definition scoped_task.hpp:157
std::variant< std::monostate, std::exception_ptr, value_type > return_value_type
The return value type.
Definition scoped_task.hpp:38
value_type const & value() const
Get the return value returned from co_return.
Definition scoped_task.hpp:143
notifier_type::token_type subscribe(std::invocable< value_type > auto &&callback) noexcept
Subscribe a callback for when the co-routine is completed.
Definition scoped_task.hpp:167
Definition scoped_task.hpp:43
std::suspend_never initial_suspend() noexcept
Before we enter the coroutine, allow the caller to set the callback.
Definition scoped_task.hpp:83
bool completed() const noexcept
Definition scoped_task.hpp:273
std::variant< std::monostate, std::exception_ptr, std::monostate > return_value_type
The return value type.
Definition scoped_task.hpp:193
void value() const
Get the return value returned from co_return.
Definition scoped_task.hpp:292
notifier_type::token_type subscribe(std::invocable<> auto &&callback) noexcept
Definition scoped_task.hpp:304
T current_exception(T... args)
T move(T... args)
T rethrow_exception(T... args)