Anklang-0.3.0.dev1148+g62a6709a anklang-0.3.0.dev1148+g62a6709a
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
cotask.hh
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#pragma once
3
4#include <ase/cxxaux.hh>
5#include <coroutine>
6#include <exception>
7#include <functional>
8#include <optional>
9#include <type_traits>
10
11namespace Ase {
12
14template<typename T>
15concept IsAwaitable = requires (T &t, std::coroutine_handle<> h)
16{
17 { t.await_ready() } -> std::convertible_to<bool>;
18 t.await_resume();
19 t.await_suspend (h);
20};
21
24 // not-awaitable: bool await_ready () { return true; }
25 // not-awaitable: void await_suspend (std::coroutine_handle<> h) {}
26 // not-awaitable: void await_resume () {}
27 struct promise_type {
28 constexpr DetachedTask get_return_object() { return DetachedTask {}; }
29 constexpr auto initial_suspend() { return std::suspend_never{}; } // immediate start
30 constexpr auto final_suspend() noexcept { return std::suspend_never{}; } // auto .destroy()
31 constexpr void return_void() {}
32 constexpr void unhandled_exception() { ase_rethrow (std::current_exception()); } // std::terminate(); }
33 };
34};
35
37struct CoTaskAux {
38 template<typename promise_type>
39 struct FinalAwaiter {
40 // Always suspend, so CoTask<>::frame_ stays valid
41 constexpr bool await_ready() const noexcept { return false; }
45 {
46 auto &promise = h.promise(); // `h` is the CoTask<> handle, currently in co_return
47 if (promise.continuation_) // we have an awaiting caller
48 return promise.continuation_; // jump to awaiting caller
49 if (promise.exception_) // excption without continuation
50 ase_rethrow (promise.exception_); // must not be forgotten
51 return std::noop_coroutine(); // Detached root, suspend is a no-op
52 }
53 constexpr void await_resume() noexcept { ASE_ASSERT_UNREACHED(); }
54 };
55 struct promise_base {
56 std::exception_ptr exception_ = nullptr; // Exception thrown from this coroutine
57 void unhandled_exception() noexcept { exception_ = std::current_exception(); }
58 // Always create + return CoTask, before starting/resuming execution
59 constexpr std::suspend_always initial_suspend() noexcept { return {}; }
60#if 0 // universal co_await
61 template<typename T>
62 struct ConstantAwaiter {
63 T value_;
64 constexpr bool await_ready() const noexcept { return true; }
65 void await_suspend (std::coroutine_handle<>) const noexcept {}
66 T await_resume() noexcept { return std::move (value_); }
67 };
68 template<typename T> decltype(auto) // Accepts anything: Tasks, Awaiters, Integers, Strings…
69 await_transform (T &&value)
70 {
71 if constexpr (IsAwaitable<T>)
72 // raw Awaiter that has await_ready/suspend/resume
73 return std::forward<T> (value);
74 else if constexpr (requires { value.operator co_await(); })
75 // T has a member `operator co_await`
76 return std::forward<T> (value).operator co_await();
77 else if constexpr (requires { operator co_await (std::forward<T> (value)); })
78 // T has a global `operator co_await`
79 return operator co_await (std::forward<T> (value));
80 else
81 // T is a plain value like int, string
82 return ConstantAwaiter<std::remove_cvref_t<T>> { std::forward<T>(value) };
83 }
84#endif
85 };
86};
87
92template<typename Result>
93struct CoTask {
94 static_assert (!std::is_reference_v<Result>, "The Result in CoTask<Result> must be movable or copyable");
96 std::coroutine_handle<> continuation_ = nullptr; // Waiter frame co_await-ing this CoTask
97 CoTask get_return_object() noexcept { return CoTask { std::coroutine_handle<promise_type>::from_promise (*this) }; }
98 CoTaskAux::FinalAwaiter<promise_type> final_suspend() noexcept { return {}; }
100 void
101 return_value (Result &&value) noexcept (std::is_nothrow_move_constructible_v<Result>)
102 {
103 result_.emplace (std::move (value)); // move-only
104 }
105 void
106 return_value (const Result &value) noexcept (std::is_nothrow_copy_constructible_v<Result>)
107 {
108 result_.emplace (value); // copyable
109 }
110 };
111 // -- Awaitable Protocol --
112 constexpr bool await_ready () const noexcept { return !frame_ || frame_.done(); }
113 Result
114 await_resume()
115 {
116 if (frame_.promise().exception_) ase_rethrow (frame_.promise().exception_);
117 return std::move (*frame_.promise().result_);
118 }
120 await_suspend (std::coroutine_handle<> waiter) noexcept
121 {
122 // assert SINGLE caller for this coroutine
123 ASE_ASSERT_RETURN (frame_.promise().continuation_ == nullptr, std::noop_coroutine());
124 frame_.promise().continuation_ = waiter; // next task to wake up when we co_return
125 return frame_.done() ? waiter : frame_; // handle to resume immediately (symmetric transfer)
126 }
127 // -- Frame Handling ---
128 explicit CoTask (std::coroutine_handle<promise_type> h) : frame_ (h) {}
129 ~CoTask ()
130 {
132 ASE_ASSERT_RETURN (frame_.done()); // assert unless we start implementing cancelation
133 frame_.destroy();
134 }
135 /*ctor*/ CoTask (CoTask &&o) noexcept : frame_ (o.frame_) { o.frame_ = nullptr; }
136 CoTask& operator= (CoTask &&o) noexcept { std::swap (frame_, o.frame_); return *this; }
137 /*ctor*/ CoTask (const CoTask&) = delete; // no-copy
138 CoTask& operator= (const CoTask&) = delete; // no-copy
139protected:
141};
142
143// TODO: should we rethrow instead of storing if continuation_ == nullptr? i.e. we're a root task
144
146template<>
147struct CoTask<void> {
148 struct promise_type : CoTaskAux::promise_base {
149 std::coroutine_handle<> continuation_ = nullptr; // Waiter frame co_await-ing this CoTask
150 CoTask get_return_object() noexcept { return CoTask { std::coroutine_handle<promise_type>::from_promise (*this) }; }
151 CoTaskAux::FinalAwaiter<promise_type> final_suspend() noexcept { return {}; }
152 constexpr void return_void() noexcept {} // Handle 'co_return;'
153 };
154 // -- Awaitable Protocol --
155 constexpr bool await_ready () const noexcept { return !frame_ || frame_.done(); }
156 void
157 await_resume()
158 {
159 if (frame_.promise().exception_) ase_rethrow (frame_.promise().exception_);
160 }
162 await_suspend (std::coroutine_handle<> waiter) noexcept
163 {
164 // assert SINGLE caller for this coroutine
165 ASE_ASSERT_RETURN (frame_.promise().continuation_ == nullptr, std::noop_coroutine());
166 frame_.promise().continuation_ = waiter; // next task to wake up when we co_return
167 return frame_.done() ? waiter : frame_; // handle to resume immediately (symmetric transfer)
168 }
169 // -- Frame Handling ---
170 explicit CoTask (std::coroutine_handle<promise_type> h) : frame_ (h) {}
171 /*dtor*/~CoTask () { ASE_RETURN_UNLESS (frame_); ASE_ASSERT_RETURN (frame_.done()); frame_.destroy(); }
172 /*ctor*/ CoTask (CoTask &&o) noexcept : frame_ (o.frame_) { o.frame_ = nullptr; }
173 CoTask& operator= (CoTask &&o) noexcept { std::swap (frame_, o.frame_); return *this; }
174 /*ctor*/ CoTask (const CoTask&) = delete; // no-copy
175 CoTask& operator= (const CoTask&) = delete; // no-copy
176protected:
178};
180
181} // Ase
Concept: Is T an awaitable?
Definition cotask.hh:15
T current_exception(T... args)
#define ASE_ASSERT_UNREACHED(...)
Abort and issue an assertion error.
Definition cxxaux.hh:87
#define ASE_ASSERT_RETURN(expr,...)
Return from the current function if expr evaluates to false and issue an assertion warning.
Definition cxxaux.hh:81
#define ASE_RETURN_UNLESS(cond,...)
Return silently if cond does not evaluate to true, with return value ...
Definition cxxaux.hh:78
T destroy(T... args)
T from_promise(T... args)
The Anklang C++ API namespace.
Definition api.hh:8
void ase_rethrow(std::exception_ptr exception)
Helper to trace rethrown exceptions.
Definition cxxaux.cc:87
Helper for CoTask<>
Definition cotask.hh:37
Start a coroutine in fire-and-forget mode.
Definition cotask.hh:23
T noop_coroutine(T... args)
T promise(T... args)
std::coroutine_handle await_suspend(std::coroutine_handle< promise_type > h) noexcept
Resume the parent frame that is co_await-ing this CoTask<>
Definition cotask.hh:44
Like CoTask<Result> without return type.
Definition cotask.hh:147
std::coroutine_handle< promise_type > frame_
Handle for this task.
Definition cotask.hh:177
General purpose coroutine task.
Definition cotask.hh:93
std::coroutine_handle< promise_type > frame_
Handle for this task.
Definition cotask.hh:140
T swap(T... args)