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 MIT License
3
4 DiscordCoreAPI, A bot library for Discord, written in C++, and featuring explicit multithreading through the usage of custom, asynchronous C++ CoRoutines.
5
6 Copyright 2022, 2023 Chris M. (RealTimeChris)
7
8 Permission is hereby granted, free of charge, to any person obtaining a copy
9 of this software and associated documentation files (the "Software"), to deal
10 in the Software without restriction, including without limitation the rights
11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the Software is
13 furnished to do so, subject to the following conditions:
14
15 The above copyright notice and this permission notice shall be included in all
16 copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 SOFTWARE.
25*/
26/// CoRoutine.hpp - Header for the coroutine class.
27/// Oct 23, 2021
28/// https://discordcoreapi.com
29/// \file CoRoutine.hpp
30#pragma once
31
35
36namespace discord_core_api {
37
38 /**
39 * \addtogroup utilities
40 * @{
41 */
42
43 /// @brief The current status of the associated co_routine.
44 enum class co_routine_status {
45 idle = 0,///< Idle.
46 running = 1,///< Running.
47 complete = 2,///< complete.
48 cancelled = 3///< cancelled.
49 };
50
51 /// @brief An error type for co_routines.
53 inline co_routine_error(jsonifier::string_view message, const std::source_location& location = std::source_location::current()) : dca_exception{ message, location } {};
54 };
55
56 template<typename value_type> class result_holder {
57 public:
58 template<typename value_type_new> inline void setResult(value_type_new&& newResult) {
59 result = makeUnique<value_type>(std::forward<value_type_new>(newResult));
60 sentYet.store(true, std::memory_order_release);
61 }
62
63 inline value_type getResult() {
64 if (sentYet.load(std::memory_order_acquire) && result) {
65 sentYet.store(false, std::memory_order_release);
66 return std::move(*result);
67 } else {
68 return {};
69 }
70 }
71
72 inline bool checkForResult() {
73 return sentYet.load(std::memory_order_acquire);
74 }
75
76 protected:
77 unique_ptr<value_type> result{};
78 std::atomic_bool sentYet{};
79 };
80
81 /// @brief A co_routine - representing a potentially asynchronous operation/function.
82 /// \tparam return_type the type of parameter that is returned by the co_routine.
83 template<typename return_type_new, bool timeOut> class co_routine {
84 public:
85 using return_type = return_type_new;///< The return type of this co_routine.
86
87 class promise_type {
88 public:
89 template<typename return_type02, bool timeOut02> friend class co_routine;
90
91 inline void requestStop() {
92 areWeStoppedBool.store(true, std::memory_order_release);
93 }
94
95 inline bool stopRequested() {
96 return areWeStoppedBool.load(std::memory_order_acquire);
97 }
98
99 template<typename return_type_newer> inline void return_value(return_type_newer&& returnValue) {
100 if (resultBuffer) {
101 resultBuffer->setResult(std::forward<return_type_newer>(returnValue));
102 }
103 }
104
105 inline auto get_return_object() {
106 return co_routine<return_type, timeOut>{ std::coroutine_handle<promise_type>::from_promise(*this) };
107 }
108
109 inline std::suspend_never initial_suspend() {
110 while (!resultBuffer) {
111 std::this_thread::sleep_for(1ms);
112 }
113 return {};
114 }
115
116 inline std::suspend_always final_suspend() noexcept {
117 return {};
118 }
119
120 inline void unhandled_exception() {
121 if (exceptionBuffer) {
122 exceptionBuffer->setResult(std::current_exception());
123 }
124 }
125
126 inline ~promise_type() {
127 exceptionBuffer = nullptr;
128 resultBuffer = nullptr;
129 }
130
131 protected:
132 result_holder<std::exception_ptr>* exceptionBuffer{};
133 result_holder<return_type>* resultBuffer{};
134 std::atomic_bool areWeStoppedBool{};
135 };
136
137 inline co_routine() = default;
138
139 inline co_routine& operator=(co_routine<return_type, timeOut>&& other) noexcept {
140 if (this != &other) {
141 coroutineHandle = other.coroutineHandle;
142 other.coroutineHandle = nullptr;
143 coroutineHandle.promise().exceptionBuffer = &exceptionBuffer;
144 coroutineHandle.promise().resultBuffer = &resultBuffer;
145 currentStatus.store(other.currentStatus.load(std::memory_order_acquire), std::memory_order_release);
146 other.currentStatus.store(co_routine_status::cancelled, std::memory_order_release);
147 }
148 return *this;
149 }
150
151 inline co_routine(co_routine<return_type, timeOut>&& other) noexcept {
152 *this = std::move(other);
153 }
154
155 inline co_routine& operator=(const co_routine<return_type, timeOut>& other) = delete;
156 inline co_routine(const co_routine<return_type, timeOut>& other) = delete;
157
158 inline co_routine& operator=(std::coroutine_handle<promise_type> coroutineHandleNew) {
159 coroutineHandle = coroutineHandleNew;
160 coroutineHandle.promise().exceptionBuffer = &exceptionBuffer;
161 coroutineHandle.promise().resultBuffer = &resultBuffer;
162 return *this;
163 }
164
165 inline explicit co_routine(std::coroutine_handle<promise_type> coroutineHandleNew) {
166 *this = coroutineHandleNew;
167 };
168
169 inline ~co_routine() {
170 if (coroutineHandle) {
171 coroutineHandle.promise().exceptionBuffer = nullptr;
172 coroutineHandle.promise().resultBuffer = nullptr;
173 }
174 }
175
176 /// @brief Collects the status of the co_routine.
177 /// @return co_routine_status the status of the co_routine.
179 if (!coroutineHandle) {
180 currentStatus.store(co_routine_status::cancelled, std::memory_order_release);
181 } else if (coroutineHandle && !coroutineHandle.done()) {
182 currentStatus.store(co_routine_status::running, std::memory_order_release);
183 } else if (coroutineHandle && coroutineHandle.done()) {
184 currentStatus.store(co_routine_status::complete, std::memory_order_release);
185 }
186 return currentStatus.load(std::memory_order_acquire);
187 }
188
189 /// @brief Gets the resulting value of the co_routine.
190 /// @return the final value resulting from the co_routine's execution.
191 inline return_type get() {
192 if (coroutineHandle) {
193 if (!coroutineHandle.done()) {
194 stop_watch<milliseconds> stopWatch{ 15000 };
195 stopWatch.reset();
196 while (!resultBuffer.checkForResult()) {
197 checkForExceptions();
198 if constexpr (timeOut) {
199 if (stopWatch.hasTimeElapsed()) {
200 return resultBuffer.getResult();
201 }
202 }
203 std::this_thread::sleep_for(1ms);
204 }
205 }
206 checkForExceptions();
207 currentStatus.store(co_routine_status::complete, std::memory_order_release);
208 return resultBuffer.getResult();
209 } else {
210 throw co_routine_error{ "co_routine::get(), you called get() on a co_routine that is "
211 "not in a valid state." };
212 }
213 }
214
215 /// @brief Cancels the currently running co_routine, while blocking to wait for it to complete.
216 /// @return the final value resulting from the co_routine's execution.
218 if (coroutineHandle) {
219 if (!coroutineHandle.done()) {
220 coroutineHandle.promise().requestStop();
221 stop_watch<milliseconds> stopWatch{ 15000 };
222 stopWatch.reset();
223 while (!resultBuffer.checkForResult()) {
224 checkForExceptions();
225 if constexpr (timeOut) {
226 if (stopWatch.hasTimeElapsed()) {
227 return resultBuffer.getResult();
228 }
229 }
230 std::this_thread::sleep_for(1ms);
231 }
232 }
233 checkForExceptions();
234 currentStatus.store(co_routine_status::cancelled, std::memory_order_release);
235 return resultBuffer.getResult();
236 } else {
237 throw co_routine_error{ "co_routine::cancelAndWait(), you called get() on a co_routine that is "
238 "not in a valid state." };
239 }
240 }
241
242 /// @brief Cancels the currently executing co_routine and returns the current result.
243 /// @return the final value resulting from the co_routine's execution.
245 if (coroutineHandle) {
246 if (!coroutineHandle.done()) {
247 coroutineHandle.promise().requestStop();
248 }
249 checkForExceptions();
250 currentStatus.store(co_routine_status::cancelled, std::memory_order_release);
251 return resultBuffer.getResult();
252 } else {
253 throw co_routine_error{ "co_routine::cancel(), you called cancel() on a co_routine that is "
254 "not in a valid state." };
255 }
256 }
257
258 protected:
259 std::atomic<co_routine_status> currentStatus{ co_routine_status::idle };
260 std::coroutine_handle<promise_type> coroutineHandle{};
261 result_holder<std::exception_ptr> exceptionBuffer{};
262 result_holder<return_type> resultBuffer{};
263
264 inline void checkForExceptions() {
265 if (exceptionBuffer.checkForResult()) {
266 std::rethrow_exception(exceptionBuffer.getResult());
267 }
268 }
269 };
270
271 /// @brief A co_routine - representing a potentially asynchronous operation/function.
272 /// \tparam void the type of parameter that is returned by the co_routine.
273 template<jsonifier::concepts::void_t return_type_new, bool timeOut> class co_routine<return_type_new, timeOut> {
274 public:
275 using return_type = return_type_new;///< The return type of this co_routine.
276
277 class promise_type {
278 public:
279 template<typename return_type02, bool timeOut02> friend class co_routine;
280
281 inline void requestStop() {
282 areWeStoppedBool.store(true, std::memory_order_release);
283 }
284
285 inline bool stopRequested() {
286 return areWeStoppedBool.load(std::memory_order_acquire);
287 }
288
289 inline void return_void() {
290 return;
291 };
292
293 inline auto get_return_object() {
294 return co_routine<return_type, timeOut>{ std::coroutine_handle<promise_type>::from_promise(*this) };
295 }
296
297 inline std::suspend_never initial_suspend() {
298 while (!resultBuffer) {
299 std::this_thread::sleep_for(1ms);
300 }
301 return {};
302 }
303
304 inline std::suspend_always final_suspend() noexcept {
305 if (resultBuffer) {
306 resultBuffer->store(true);
307 }
308 return {};
309 }
310
311 inline void unhandled_exception() {
312 if (exceptionBuffer) {
313 exceptionBuffer->setResult(std::current_exception());
314 }
315 }
316
317 inline ~promise_type() {
318 exceptionBuffer = nullptr;
319 resultBuffer = nullptr;
320 }
321
322 protected:
323 result_holder<std::exception_ptr>* exceptionBuffer{};
324 std::atomic_bool areWeStoppedBool{};
325 std::atomic_bool* resultBuffer{};
326 };
327
328 inline co_routine() = default;
329
330 inline co_routine& operator=(co_routine<return_type, timeOut>&& other) noexcept {
331 if (this != &other) {
332 coroutineHandle = other.coroutineHandle;
333 other.coroutineHandle = nullptr;
334 coroutineHandle.promise().exceptionBuffer = &exceptionBuffer;
335 coroutineHandle.promise().resultBuffer = &resultBuffer;
336 currentStatus.store(other.currentStatus.load(std::memory_order_acquire), std::memory_order_release);
337 other.currentStatus.store(co_routine_status::cancelled, std::memory_order_release);
338 }
339 return *this;
340 }
341
342 inline co_routine(co_routine<return_type, timeOut>&& other) noexcept {
343 *this = std::move(other);
344 }
345
346 inline co_routine& operator=(const co_routine<return_type, timeOut>& other) = delete;
347 inline co_routine(const co_routine<return_type, timeOut>& other) = delete;
348
349 inline co_routine& operator=(std::coroutine_handle<promise_type> coroutineHandleNew) {
350 coroutineHandle = coroutineHandleNew;
351 coroutineHandle.promise().exceptionBuffer = &exceptionBuffer;
352 coroutineHandle.promise().resultBuffer = &resultBuffer;
353 return *this;
354 }
355
356 inline explicit co_routine(std::coroutine_handle<promise_type> coroutineHandleNew) {
357 *this = coroutineHandleNew;
358 };
359
360 inline ~co_routine() {
361 if (coroutineHandle) {
362 coroutineHandle.promise().exceptionBuffer = nullptr;
363 coroutineHandle.promise().resultBuffer = nullptr;
364 }
365 }
366
367 /// @brief Collects the status of the co_routine.
368 /// @return co_routine_status the status of the co_routine.
370 if (!coroutineHandle) {
371 currentStatus.store(co_routine_status::cancelled, std::memory_order_release);
372 } else if (coroutineHandle && !coroutineHandle.done()) {
373 currentStatus.store(co_routine_status::running, std::memory_order_release);
374 } else if (coroutineHandle && coroutineHandle.done()) {
375 currentStatus.store(co_routine_status::complete, std::memory_order_release);
376 }
377 return currentStatus.load(std::memory_order_acquire);
378 }
379
380 /// @brief Gets the resulting value of the co_routine.
381 inline void get() {
382 if (coroutineHandle) {
383 if (!coroutineHandle.done()) {
384 stop_watch<milliseconds> stopWatch{ 15000 };
385 stopWatch.reset();
386 while (!resultBuffer.load()) {
387 checkForExceptions();
388 if constexpr (timeOut) {
389 if (stopWatch.hasTimeElapsed()) {
390 return;
391 }
392 }
393 std::this_thread::sleep_for(1ms);
394 }
395 }
396 checkForExceptions();
397 currentStatus.store(co_routine_status::complete, std::memory_order_release);
398 return;
399 } else {
400 throw co_routine_error{ "co_routine::get(), you called get() on a co_routine that is "
401 "not in a valid state." };
402 }
403 }
404
405 /// @brief Cancels the currently running co_routine, while blocking to wait for it to complete.
406 inline void cancelAndWait() {
407 if (coroutineHandle) {
408 if (!coroutineHandle.done()) {
409 coroutineHandle.promise().requestStop();
410 stop_watch<milliseconds> stopWatch{ 15000 };
411 stopWatch.reset();
412 while (!resultBuffer.load()) {
413 checkForExceptions();
414 if constexpr (timeOut) {
415 if (stopWatch.hasTimeElapsed()) {
416 return;
417 }
418 }
419 std::this_thread::sleep_for(1ms);
420 }
421 }
422 checkForExceptions();
423 currentStatus.store(co_routine_status::cancelled, std::memory_order_release);
424 return;
425 } else {
426 throw co_routine_error{ "co_routine::cancelAndWait(), you called get() on a co_routine that is "
427 "not in a valid state." };
428 }
429 }
430
431 /// @brief Cancels the currently executing co_routine and returns the current result.
432 inline void cancel() {
433 if (coroutineHandle) {
434 if (!coroutineHandle.done()) {
435 coroutineHandle.promise().requestStop();
436 }
437 checkForExceptions();
438 currentStatus.store(co_routine_status::cancelled, std::memory_order_release);
439 return;
440 } else {
441 throw co_routine_error{ "co_routine::cancel(), you called cancel() on a co_routine that is "
442 "not in a valid state." };
443 }
444 }
445
446 protected:
447 std::atomic<co_routine_status> currentStatus{ co_routine_status::idle };
448 std::coroutine_handle<promise_type> coroutineHandle{};
449 result_holder<std::exception_ptr> exceptionBuffer{};
450 std::atomic_bool resultBuffer{};
451
452 inline void checkForExceptions() {
453 if (exceptionBuffer.checkForResult()) {
454 std::rethrow_exception(exceptionBuffer.getResult());
455 }
456 }
457 };
458
459 class new_thread_awaiter_base {
460 public:
461 inline static discord_core_internal::co_routine_thread_pool threadPool{};
462 };
463
464 /// @brief An awaitable that can be used to launch the co_routine onto a new thread - as well as return the handle for stoppping its execution.
465 /// \tparam return_type the type of value returned by the containing co_routine.
466 template<typename return_type, bool timeOut> class new_thread_awaiter : public new_thread_awaiter_base {
467 public:
468 inline bool await_ready() const {
469 return false;
470 }
471
472 inline void await_suspend(std::coroutine_handle<typename co_routine<return_type, timeOut>::promise_type> coroHandleNew) {
473 new_thread_awaiter_base::threadPool.submitTask(coroHandleNew);
474 coroHandle = coroHandleNew;
475 }
476
477 inline auto await_resume() {
478 return coroHandle;
479 }
480
481 protected:
482 std::coroutine_handle<typename co_routine<return_type, timeOut>::promise_type> coroHandle{};
483 };
484
485 /**@}*/
486};
void cancel()
Cancels the currently executing co_routine and returns the current result.
Definition: CoRoutine.hpp:432
co_routine_status getStatus()
Collects the status of the co_routine.
Definition: CoRoutine.hpp:369
return_type_new return_type
The return type of this co_routine.
Definition: CoRoutine.hpp:275
void cancelAndWait()
Cancels the currently running co_routine, while blocking to wait for it to complete.
Definition: CoRoutine.hpp:406
void get()
Gets the resulting value of the co_routine.
Definition: CoRoutine.hpp:381
A co_routine - representing a potentially asynchronous operation/function.
Definition: CoRoutine.hpp:83
return_type cancel()
Cancels the currently executing co_routine and returns the current result.
Definition: CoRoutine.hpp:244
co_routine_status getStatus()
Collects the status of the co_routine.
Definition: CoRoutine.hpp:178
return_type cancelAndWait()
Cancels the currently running co_routine, while blocking to wait for it to complete.
Definition: CoRoutine.hpp:217
return_type_new return_type
The return type of this co_routine.
Definition: CoRoutine.hpp:85
return_type get()
Gets the resulting value of the co_routine.
Definition: CoRoutine.hpp:191
An awaitable that can be used to launch the co_routine onto a new thread - as well as return the hand...
Definition: CoRoutine.hpp:466
co_routine_status
The current status of the associated co_routine.
Definition: CoRoutine.hpp:44
The main namespace for the forward-facing interfaces.
An error type for co_routines.
Definition: CoRoutine.hpp:52
An exception class derived from std::runtime_error for dca-related exceptions.
Definition: Base.hpp:820