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