| 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 | |
| 36 | namespace 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 | |