1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 2023, The LegacyClonk Team and contributors
5 *
6 * Distributed under the terms of the ISC license; see accompanying file
7 * "COPYING" for details.
8 *
9 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
10 * See accompanying file "TRADEMARK" for details.
11 *
12 * To redistribute this file separately, substitute the full license texts
13 * for the above references.
14 */
15
16#pragma once
17
18#include "C4Coroutine.h"
19#include "C4ThreadPool.h"
20#include "StdSync.h"
21
22#ifdef _WIN32
23#include "C4WinRT.h"
24
25#include <chrono>
26#else
27#include <array>
28#include <cstring>
29#include <limits>
30#include <optional>
31#include <ranges>
32#include <stdexcept>
33#include <vector>
34#endif
35
36namespace C4Awaiter
37{
38 namespace Awaiter
39 {
40 struct ResumeInMainThread
41 {
42 bool await_ready() const noexcept;
43 void await_suspend(const std::coroutine_handle<> handle) const noexcept;
44 constexpr void await_resume() const noexcept {}
45 };
46
47 struct ResumeInGlobalThreadPool
48 {
49 constexpr bool await_ready() const noexcept { return false; }
50
51 void await_suspend(const std::coroutine_handle<> handle) const noexcept
52 {
53 C4ThreadPool::Global->SubmitCallback(callback: handle);
54 }
55
56 constexpr void await_resume() const noexcept {}
57 };
58
59#ifndef _WIN32
60 template<bool Single>
61 class ResumeOnSignals : public C4Task::CancellableAwaiter<ResumeOnSignals<Single>>
62 {
63 private:
64 enum class State
65 {
66 Idle,
67 Pending,
68 Cancelling,
69 Cancelled
70 };
71
72 using PollFdContainer = std::conditional_t<Single, std::array<pollfd, 2>, std::vector<pollfd>>;
73
74 public:
75 template<std::ranges::range T>
76 ResumeOnSignals(T &&range, const std::uint32_t timeout) requires (!Single) : fds{std::ranges::begin(range), std::ranges::end(range)}, timeout{timeout}
77 {
78 fds.push_back(pollfd{.fd = cancellationEvent.GetFD(), .events = POLLIN});
79 }
80
81 ResumeOnSignals(const pollfd fd, const std::uint32_t timeout) requires Single
82 : fds{{fd, pollfd{.fd = cancellationEvent.GetFD(), .events = POLLIN}}},
83 timeout{timeout}
84 {
85 }
86
87 public:
88 bool await_ready() noexcept
89 {
90 return StdSync::Poll(std::span{fds.data(), fds.size() - 1}, 0) > 0;
91 }
92
93 template<typename T>
94 void await_suspend(const std::coroutine_handle<T> handle)
95 {
96 const std::size_t size{fds.size()};
97
98 C4Task::CancellableAwaiter<ResumeOnSignals<Single>>::SetCancellablePromise(handle);
99
100 State expected{State::Idle};
101 if (!state.compare_exchange_strong(expected, State::Pending, std::memory_order_acq_rel))
102 {
103 handle.resume();
104 }
105
106 C4ThreadPool::Global->SubmitCallback([handle, size, this]
107 {
108 const struct Cleanup
109 {
110 ~Cleanup()
111 {
112 handle.resume();
113 }
114
115 std::coroutine_handle<> handle;
116 } cleanup{handle};
117
118 if (StdSync::Poll(fds, timeout) == -1)
119 {
120 exception = std::make_exception_ptr(ex: std::runtime_error{std::string{"poll failed: "} + std::strerror(errno)});
121 }
122 });
123 }
124
125 std::vector<pollfd> await_resume() requires (!Single)
126 {
127 CheckCancellationAndException();
128
129 fds.resize(fds.size() - 1);
130
131 std::erase_if(fds, [](const auto &fd) { return fd.revents == 0; });
132
133 return std::move(fds);
134 }
135
136 pollfd await_resume() requires Single
137 {
138 CheckCancellationAndException();
139
140 return fds[0];
141 }
142
143 void SetupCancellation(C4Task::CancellablePromise *const promise)
144 {
145 promise->SetCancellationCallback(callback: [](void *const argument)
146 {
147 auto &awaiter = *reinterpret_cast<ResumeOnSignals *>(argument);
148 State expected{State::Pending};
149 if (awaiter.state.compare_exchange_strong(expected, State::Cancelling, std::memory_order_acq_rel))
150 {
151 awaiter.cancellationEvent.Set();
152 awaiter.state.store(State::Cancelled, std::memory_order_release);
153 }
154 }, argument: this);
155 }
156
157 private:
158 void CheckCancellationAndException()
159 {
160 State expected{State::Pending};
161
162 while (!state.compare_exchange_weak(expected, State::Idle, std::memory_order_acq_rel))
163 {
164 if (expected == State::Cancelling)
165 {
166 expected = State::Cancelled;
167 }
168 }
169
170 if (expected == State::Cancelled)
171 {
172 throw C4Task::CancelledException{};
173 }
174
175 if (exception)
176 {
177 std::rethrow_exception(exception);
178 }
179 }
180
181 private:
182 CStdEvent cancellationEvent;
183 PollFdContainer fds;
184 std::uint32_t timeout;
185 std::atomic<State> state{State::Idle};
186 std::exception_ptr exception;
187 };
188#endif
189 }
190
191 [[nodiscard]] inline constexpr Awaiter::ResumeInMainThread ResumeInMainThread() noexcept
192 {
193 return {};
194 }
195
196 [[nodiscard]] inline constexpr Awaiter::ResumeInGlobalThreadPool ResumeInGlobalThreadPool() noexcept
197 {
198 return {};
199 }
200
201#ifdef _WIN32
202 [[nodiscard]] inline auto ResumeOnSignal(const HANDLE handle, const std::uint32_t timeout = StdSync::Infinite)
203 {
204 if (timeout == StdSync::Infinite)
205 {
206 return winrt::resume_on_signal(handle);
207 }
208 else
209 {
210 return winrt::resume_on_signal(handle, std::chrono::duration_cast<winrt::Windows::Foundation::TimeSpan>(std::chrono::milliseconds{timeout}));
211 }
212 }
213#else
214 [[nodiscard]] inline Awaiter::ResumeOnSignals<true> ResumeOnSignal(const pollfd fd, const std::uint32_t timeout = StdSync::Infinite) noexcept
215 {
216 return {fd, timeout};
217 }
218
219 template<std::ranges::range T>
220 [[nodiscard]] inline Awaiter::ResumeOnSignals<false> ResumeOnSignals(T &&fds, const std::uint32_t timeout = StdSync::Infinite) noexcept
221 {
222 return {std::forward<T>(fds), timeout};
223 }
224#endif
225}
226