DiscordCoreAPI
A Discord bot library written in C++, with custom asynchronous coroutines.
Loading...
Searching...
No Matches
CoRoutine.hpp
Go to the documentation of this file.
1/*
2 DiscordCoreAPI, A bot library for Discord, written in C++, and featuring explicit multithreading through the usage of custom, asynchronous C++ CoRoutines.
3
4 Copyright 2021, 2022 Chris M. (RealTimeChris)
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
19 USA
20*/
21/// CoRoutine.hpp - Header for the coroutine class.
22/// Oct 23, 2021
23/// https://discordcoreapi.com
24/// \file CoRoutine.hpp
25
26#pragma once
27
30
31namespace DiscordCoreAPI {
32 /**
33 * \addtogroup utilities
34 * @{
35 */
36
37 /// \brief The current status of the associated CoRoutine.
38 enum class CoRoutineStatus {
39 Idle = 0,///< Idle.
40 Running = 1,///< Running.
41 Complete = 2,///< Complete.
42 Cancelled = 3///< Cancelled.
43 };
44
45 /// \brief An error type for CoRoutines.
46 struct DiscordCoreAPI_Dll CoRoutineError : public DCAException {
47 explicit CoRoutineError(const std::string& message);
48 };
49
50 class DiscordCoreAPI_Dll CoRoutineBase {
51 public:
52 static DiscordCoreInternal::CoRoutineThreadPool threadPool;
53
54 virtual ~CoRoutineBase() noexcept = default;
55 };
56
57 /// \brief A CoRoutine - representing a potentially asynchronous operation/function.
58 /// \tparam RTy The type of parameter that is returned by the CoRoutine.
59 template<typename RTy> class CoRoutine : public CoRoutineBase {
60 public:
61 class DiscordCoreAPI_Dll promise_type {
62 public:
63 template<typename RTy02> friend class CoRoutine;
64
65 void requestStop() {
66 this->areWeStoppedBool.store(true);
67 }
68
69 bool areWeStopped() {
70 return this->areWeStoppedBool.load();
71 }
72
73 void return_value(RTy&& at) {
74 this->result = std::move(at);
75 }
76
77 CoRoutine<RTy> get_return_object() {
78 return CoRoutine<RTy>{ std::coroutine_handle<CoRoutine<RTy>::promise_type>::from_promise(*this) };
79 }
80
81 std::suspend_never initial_suspend() {
82 return {};
83 }
84
85 std::suspend_always final_suspend() noexcept {
86 if (this->areWeDone) {
87 this->areWeDone->store(true);
88 }
89 return {};
90 }
91
92 void unhandled_exception() {
93 if (this->exceptionBuffer) {
94 this->exceptionBuffer->send(std::current_exception());
95 }
96 }
97
98 protected:
99 UnboundedMessageBlock<std::exception_ptr>* exceptionBuffer{ nullptr };
100 std::atomic_bool areWeStoppedBool{};
101 std::atomic_bool* areWeDone{ nullptr };
102 RTy result{};
103 };
104
105 CoRoutine<RTy>& operator=(CoRoutine<RTy>&& other) noexcept {
106 if (this != &other) {
107 this->coroutineHandle = other.coroutineHandle;
108 other.coroutineHandle = nullptr;
109 this->coroutineHandle.promise().exceptionBuffer = &this->exceptionBuffer;
110 this->coroutineHandle.promise().areWeDone = &this->areWeDone;
111 this->currentStatus.store(other.currentStatus.load());
112 other.currentStatus.store(CoRoutineStatus::Cancelled);
113 }
114 return *this;
115 };
116
117 CoRoutine(CoRoutine<RTy>&& other) noexcept {
118 *this = std::move(other);
119 };
120
121 CoRoutine<RTy>& operator=(const CoRoutine<RTy>& other) = delete;
122
123 CoRoutine(const CoRoutine<RTy>& other) = delete;
124
125 CoRoutine<RTy>& operator=(std::coroutine_handle<CoRoutine<RTy>::promise_type> coroutineHandleNew) {
126 this->coroutineHandle = coroutineHandleNew;
127 this->coroutineHandle.promise().exceptionBuffer = &this->exceptionBuffer;
128 this->coroutineHandle.promise().areWeDone = &this->areWeDone;
129 return *this;
130 }
131
132 explicit CoRoutine(std::coroutine_handle<CoRoutine<RTy>::promise_type> coroutineHandleNew) {
133 *this = coroutineHandleNew;
134 };
135
136 ~CoRoutine() {
137 if (this && this->coroutineHandle) {
138 this->coroutineHandle.promise().exceptionBuffer = nullptr;
139 this->coroutineHandle.promise().areWeDone = nullptr;
140 if (this->coroutineHandle.done()) {
141 this->coroutineHandle.destroy();
142 }
143 }
144 }
145
146 /// \brief Collects the status of the CoRoutine.
147 /// \returns CoRoutineStatus The status of the CoRoutine.
149 if (!this->coroutineHandle) {
150 this->currentStatus.store(CoRoutineStatus::Cancelled);
151 } else if (this->coroutineHandle && !this->coroutineHandle.done()) {
152 this->currentStatus.store(CoRoutineStatus::Running);
153 } else if (this->coroutineHandle && this->coroutineHandle.done()) {
154 this->currentStatus.store(CoRoutineStatus::Complete);
155 }
156 return this->currentStatus.load();
157 }
158
159 /// \brief Gets the resulting value of the CoRoutine.
160 /// \returns RTy The return value of the CoRoutine.
161 RTy get() {
162 if (this && this->coroutineHandle) {
163 while (!this->areWeDone.load()) {
164 std::this_thread::sleep_for(1ms);
165 }
166 this->currentStatus.store(CoRoutineStatus::Complete);
167 std::exception_ptr exceptionPtr{};
168 while (this->exceptionBuffer.tryReceive(exceptionPtr)) {
169 std::rethrow_exception(exceptionPtr);
170 std::this_thread::sleep_for(1ms);
171 }
172 this->result = std::move(this->coroutineHandle.promise().result);
173 return this->result;
174 } else {
175 throw CoRoutineError("CoRoutine::get(), You called get() on a CoRoutine that is "
176 "not in a valid state.");
177 }
178 return RTy{};
179 }
180
181 /// \brief Cancels the currently executing CoRoutine and returns the current result.
182 /// \returns RTy The return value of the CoRoutine.
183 RTy cancel() {
184 if (this && this->coroutineHandle) {
185 if (!this->coroutineHandle.done()) {
186 this->coroutineHandle.promise().requestStop();
187 while (!this->areWeDone.load()) {
188 std::this_thread::sleep_for(1ms);
189 }
190 }
191 std::exception_ptr exceptionPtr{};
192 this->currentStatus.store(CoRoutineStatus::Cancelled);
193 while (this->exceptionBuffer.tryReceive(exceptionPtr)) {
194 std::rethrow_exception(exceptionPtr);
195 std::this_thread::sleep_for(1ms);
196 }
197 this->result = std::move(this->coroutineHandle.promise().result);
198 return this->result;
199 }
200 return RTy{};
201 }
202
203 protected:
204 std::coroutine_handle<CoRoutine<RTy>::promise_type> coroutineHandle{ nullptr };
205 std::atomic<CoRoutineStatus> currentStatus{ CoRoutineStatus::Idle };
206 UnboundedMessageBlock<std::exception_ptr> exceptionBuffer{};
207 std::atomic_bool areWeDone{};
208 RTy result{};
209 };
210
211 /// \brief A CoRoutine - representing a potentially asynchronous operation/function.
212 /// \tparam void The type of parameter that is returned by the CoRoutine.
213 template<> class CoRoutine<void> : public CoRoutineBase {
214 public:
215 class DiscordCoreAPI_Dll promise_type {
216 public:
217 template<typename RTy> friend class CoRoutine;
218
219 void requestStop() {
220 this->areWeStoppedBool.store(true);
221 }
222
223 bool areWeStopped() {
224 return this->areWeStoppedBool.load();
225 }
226
227 void return_void() {
228 }
229
230 CoRoutine<void> get_return_object() {
231 return CoRoutine<void>{ std::coroutine_handle<CoRoutine<void>::promise_type>::from_promise(*this) };
232 }
233
234 std::suspend_never initial_suspend() {
235 return {};
236 }
237
238 std::suspend_always final_suspend() noexcept {
239 if (this->areWeDone) {
240 this->areWeDone->store(true);
241 }
242 return {};
243 }
244
245 void unhandled_exception() {
246 if (this->exceptionBuffer) {
247 this->exceptionBuffer->send(std::current_exception());
248 }
249 }
250
251 protected:
252 UnboundedMessageBlock<std::exception_ptr>* exceptionBuffer{ nullptr };
253 std::atomic_bool areWeStoppedBool{};
254 std::atomic_bool* areWeDone{ nullptr };
255 };
256
257 CoRoutine<void>& operator=(CoRoutine<void>&& other) noexcept {
258 if (this != &other) {
259 this->coroutineHandle = other.coroutineHandle;
260 other.coroutineHandle = nullptr;
261 this->coroutineHandle.promise().exceptionBuffer = &this->exceptionBuffer;
262 this->coroutineHandle.promise().areWeDone = &this->areWeDone;
263 this->currentStatus.store(other.currentStatus.load());
264 other.currentStatus.store(CoRoutineStatus::Cancelled);
265 }
266 return *this;
267 };
268
269 CoRoutine(CoRoutine<void>&& other) noexcept {
270 *this = std::move(other);
271 };
272
273 CoRoutine<void>& operator=(const CoRoutine<void>& other) = delete;
274
275 CoRoutine(const CoRoutine<void>& other) = delete;
276
277 CoRoutine<void>& operator=(std::coroutine_handle<CoRoutine<void>::promise_type> coroutineHandleNew) {
278 this->coroutineHandle = coroutineHandleNew;
279 this->coroutineHandle.promise().exceptionBuffer = &this->exceptionBuffer;
280 this->coroutineHandle.promise().areWeDone = &this->areWeDone;
281 return *this;
282 }
283
284 explicit CoRoutine(std::coroutine_handle<CoRoutine<void>::promise_type> coroutineHandleNew) {
285 *this = coroutineHandleNew;
286 };
287
288 ~CoRoutine() {
289 if (this && this->coroutineHandle) {
290 this->coroutineHandle.promise().exceptionBuffer = nullptr;
291 this->coroutineHandle.promise().areWeDone = nullptr;
292 if (this->coroutineHandle.done()) {
293 this->coroutineHandle.destroy();
294 }
295 }
296 }
297
298 /// \brief Collects the status of the CoRoutine.
299 /// \returns CoRoutineStatus The status of the CoRoutine.
301 if (!this->coroutineHandle) {
302 this->currentStatus.store(CoRoutineStatus::Cancelled);
303 } else if (this->coroutineHandle && !this->coroutineHandle.done()) {
304 this->currentStatus.store(CoRoutineStatus::Running);
305 } else if (this->coroutineHandle && this->coroutineHandle.done()) {
306 this->currentStatus.store(CoRoutineStatus::Complete);
307 }
308 return this->currentStatus.load();
309 }
310
311 /// \brief Gets the resulting value of the CoRoutine.
312 void get() {
313 if (this && this->coroutineHandle) {
314 while (!this->areWeDone.load()) {
315 std::this_thread::sleep_for(1ms);
316 }
317 this->currentStatus.store(CoRoutineStatus::Complete);
318 std::exception_ptr exceptionPtr{};
319 while (this->exceptionBuffer.tryReceive(exceptionPtr)) {
320 std::rethrow_exception(exceptionPtr);
321 std::this_thread::sleep_for(1ms);
322 }
323 } else {
324 throw CoRoutineError("CoRoutine::get(), You called get() on a CoRoutine that is "
325 "not in a valid state.");
326 }
327 }
328
329 /// \brief Cancels the currently executing CoRoutine and returns the current result.
330 void cancel() {
331 if (this && this->coroutineHandle) {
332 if (!this->coroutineHandle.done()) {
333 this->coroutineHandle.promise().requestStop();
334 while (!this->areWeDone.load()) {
335 std::this_thread::sleep_for(1ms);
336 }
337 }
338 this->currentStatus.store(CoRoutineStatus::Cancelled);
339 std::exception_ptr exceptionPtr{};
340 while (this->exceptionBuffer.tryReceive(exceptionPtr)) {
341 std::rethrow_exception(exceptionPtr);
342 std::this_thread::sleep_for(1ms);
343 }
344 }
345 }
346
347 protected:
348 std::coroutine_handle<CoRoutine<void>::promise_type> coroutineHandle{ nullptr };
349 std::atomic<CoRoutineStatus> currentStatus{ CoRoutineStatus::Idle };
350 UnboundedMessageBlock<std::exception_ptr> exceptionBuffer{};
351 std::atomic_bool areWeDone{};
352 };
353
354 /// \brief An awaitable that can be used to launch the CoRoutine onto a new thread - as well as return the handle for stoppping its execution.
355 /// \tparam RTy The type of value returned by the containing CoRoutine.
356 template<typename RTy> class NewThreadAwaiter {
357 public:
358 bool await_ready() const noexcept {
359 return false;
360 }
361
362 void await_suspend(std::coroutine_handle<typename CoRoutine<RTy>::promise_type> coroHandleNew) noexcept {
363 CoRoutine<RTy>::threadPool.submitTask(coroHandleNew);
364 this->coroHandle = coroHandleNew;
365 }
366
367 auto await_resume() noexcept {
368 return this->coroHandle;
369 }
370
371 protected:
372 std::coroutine_handle<typename CoRoutine<RTy>::promise_type> coroHandle{};
373 };
374
375 /**@}*/
376};// namespace DiscordCoreAPI
CoRoutineStatus
The current status of the associated CoRoutine.
Definition: CoRoutine.hpp:38
The main namespace for this library.
An error type for CoRoutines.
Definition: CoRoutine.hpp:46
A CoRoutine - representing a potentially asynchronous operation/function.
Definition: CoRoutine.hpp:59
CoRoutineStatus getStatus()
Collects the status of the CoRoutine.
Definition: CoRoutine.hpp:148
RTy get()
Gets the resulting value of the CoRoutine.
Definition: CoRoutine.hpp:161
RTy cancel()
Cancels the currently executing CoRoutine and returns the current result.
Definition: CoRoutine.hpp:183
void cancel()
Cancels the currently executing CoRoutine and returns the current result.
Definition: CoRoutine.hpp:330
CoRoutineStatus getStatus()
Collects the status of the CoRoutine.
Definition: CoRoutine.hpp:300
void get()
Gets the resulting value of the CoRoutine.
Definition: CoRoutine.hpp:312
An awaitable that can be used to launch the CoRoutine onto a new thread - as well as return the handl...
Definition: CoRoutine.hpp:356
A thread-safe messaging block for data-structures.
Definition: Utilities.hpp:1645