6#include <core/async/Awaitable.hpp>
7#include <core/async/Cancellation.hpp>
8#include <core/async/ExecutorContext.hpp>
9#include <core/async/IExecutor.hpp>
10#include <core/async/ParkedWork.hpp>
11#include <core/async/StopToken.hpp>
12#include <core/async/Task.hpp>
20#include "completion.hpp"
23#include "executor.hpp"
36namespace morph::async {
42class ExecutorResumer final :
public ::core::async::IExecutor,
public std::enable_shared_from_this<ExecutorResumer> {
45 explicit ExecutorResumer(::morph::exec::IExecutor& executor MORPH_LIFETIMEBOUND) : _executor{executor} {}
47 using ::core::async::IExecutor::submit;
51 void submit(std::coroutine_handle<> handle)
override {
52 _executor.post([self = shared_from_this(), handle] {
53 std::shared_ptr<void>
const keep =
self;
54 ::core::async::ExecutorScope
const scope{*
self, &keep,
nullptr};
62 void submit(::core::async::ParkedWork work)
override { submit(work.resume); }
65 ::morph::exec::IExecutor& _executor;
71 ::core::async::IExecutor* target;
75 [[nodiscard]]
constexpr bool await_ready() const noexcept {
return false; }
76 void await_suspend(std::coroutine_handle<> awaiting)
const { target->submit(awaiting); }
77 void await_resume() const noexcept {}
87 [[nodiscard]] SpawnedTask get_return_object() const noexcept {
return {}; }
88 [[nodiscard]] std::suspend_never initial_suspend() const noexcept {
return {}; }
89 [[nodiscard]] std::suspend_never final_suspend() const noexcept {
return {}; }
90 void return_void() const noexcept {}
93 [[noreturn]]
void unhandled_exception() const noexcept { std::terminate(); }
102inline SpawnedTask runSpawned(std::shared_ptr<ExecutorResumer> resumer, ::core::async::Task<void> task) {
103 co_await HopTo{resumer.get()};
105 co_await std::move(task);
106 }
catch (
const std::exception& exc) {
107 ::morph::log::logError(
"[spawn] task threw: {}", exc.what());
109 ::morph::log::logError(
"[spawn] task threw unknown exception");
126 detail::runSpawned(std::make_shared<detail::ExecutorResumer>(executor), std::move(task));
140 enum class Outcome : std::uint8_t { Pending, Fired, Cancelled };
146 void operator()()
const noexcept { shared->onStop(); }
149 struct Shared : std::enable_shared_from_this<Shared> {
150 std::atomic<Outcome> outcome{Outcome::Pending};
151 std::atomic<bool> armed{
false};
152 std::atomic<bool> claimed{
false};
153 std::atomic<detail::TimeoutScheduler::Handle> timer{0};
154 std::coroutine_handle<> continuation;
155 ::core::async::ResumeTarget context;
156 detail::TimeoutScheduler* scheduler =
nullptr;
157 std::optional<::core::async::StopCallback<OnStop>> stopCallback;
161 bool decide(Outcome outcome_)
noexcept {
162 auto expected = Outcome::Pending;
163 return outcome.compare_exchange_strong(expected, outcome_, std::memory_order_seq_cst);
166 void resume()
const {
168 context.submit(::core::async::ParkedWork{.resume = continuation});
170 continuation.resume();
174 void onStop()
noexcept {
175 if (!decide(Outcome::Cancelled)) {
181 auto const keep = shared_from_this();
182 scheduler->cancel(timer.load());
183 if (armed.load(std::memory_order_seq_cst) && !claimed.exchange(
true, std::memory_order_acq_rel)) {
196 DelayAwaiter(detail::TimeoutScheduler& scheduler MORPH_LIFETIMEBOUND, std::chrono::milliseconds duration)
197 : _shared{std::make_shared<Shared>()}, _duration{duration} {
198 _shared->scheduler = &scheduler;
203 _shared->stopCallback.reset();
204 if (_shared->decide(Outcome::Cancelled)) {
205 _shared->scheduler->cancel(_shared->timer.load());
217 [[nodiscard]]
constexpr bool await_ready() const noexcept {
return false; }
223 template <
typename Promise>
227 auto shared = _shared;
228 auto const duration = _duration;
229 shared->continuation = awaiting;
230 shared->context = ::core::async::ResumeTarget::current();
231 if constexpr (::core::async::HasStopToken<Promise>) {
232 ::core::async::StopToken
const token = awaiting.promise().stopToken();
233 if (token.stop_requested()) {
234 shared->outcome.store(Outcome::Cancelled);
237 if (token.stop_possible()) {
238 shared->stopCallback.emplace(token, OnStop{shared.get()});
241 shared->armed.store(
true, std::memory_order_seq_cst);
242 if (shared->outcome.load(std::memory_order_seq_cst) == Outcome::Cancelled) {
243 return shared->claimed.exchange(
true, std::memory_order_acq_rel);
245 auto const handle = shared->scheduler->schedule(duration, [shared] {
246 if (shared->decide(Outcome::Fired)) {
250 shared->timer.store(handle);
252 if (shared->outcome.load() == Outcome::Cancelled) {
253 shared->scheduler->cancel(handle);
260 _shared->stopCallback.reset();
261 if (_shared->outcome.load() == Outcome::Cancelled) {
262 throw ::core::async::OperationCancelled{};
267 std::shared_ptr<Shared> _shared;
268 std::chrono::milliseconds _duration;
279 std::chrono::milliseconds duration) {
constexpr Self self
The dialog or drawer a command list runs in.
The awaiter delay() returns: suspends until a TimeoutScheduler entry fires, or until a stop withdraws...
bool await_suspend(std::coroutine_handle< Promise > awaiting)
~DelayAwaiter()
Withdraws the wait if the frame is destroyed while suspended.
DelayAwaiter(detail::TimeoutScheduler &scheduler, std::chrono::milliseconds duration)
constexpr bool await_ready() const noexcept
The awaiter behind Completion<T>::operator co_await.
DelayAwaiter delay(detail::TimeoutScheduler &scheduler, std::chrono::milliseconds duration)
Suspends the awaiting coroutine for at least duration.
void spawn(::morph::exec::IExecutor &executor, ::core::async::Task< void > task)
Starts task detached, with every resumption – its first step included – posted to executor.
Abstract executor interface.
What drives a model's Task handler: the driver coroutine that starts and finishes it,...