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 DCA_INLINE co_routine_error(const jsonifier::string_view& message, 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> DCA_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 DCA_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 DCA_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 DCA_INLINE void requestStop() {
92 areWeStoppedBool.store(true, std::memory_order_release);
93 }
94
95 DCA_INLINE bool stopRequested() {
96 return areWeStoppedBool.load(std::memory_order_acquire);
97 }
98
99 template<typename return_type_newer> DCA_INLINE void return_value(return_type_newer&& returnValue) {
100 if (resultBuffer) {
101 resultBuffer->setResult(std::forward<return_type_newer>(returnValue));
102 }
103 }
104
105 DCA_INLINE auto get_return_object() {
106 return co_routine<return_type, timeOut>{ std::coroutine_handle<promise_type>::from_promise(*this) };
107 }
108
109 DCA_INLINE std::suspend_never initial_suspend() {
110 while (!resultBuffer) {
111 std::this_thread::sleep_for(1ms);
112 }
113 return {};
114 }
115
116 DCA_INLINE std::suspend_always final_suspend() noexcept {
117 return {};
118 }
119
120 DCA_INLINE void unhandled_exception() {
121 if (exceptionBuffer) {
122 exceptionBuffer->setResult(std::current_exception());
123 }
124 }
125
126 DCA_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 DCA_INLINE co_routine() = default;
138
139 DCA_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 DCA_INLINE co_routine(co_routine<return_type, timeOut>&& other) noexcept {
152 *this = std::move(other);
153 }
154
155 DCA_INLINE co_routine& operator=(const co_routine<return_type, timeOut>& other) = delete;
156 DCA_INLINE co_routine(const co_routine<return_type, timeOut>& other) = delete;
157
158 DCA_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 DCA_INLINE explicit co_routine(std::coroutine_handle<promise_type> coroutineHandleNew) {
166 *this = coroutineHandleNew;
167 };
168
169 DCA_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 DCA_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.
244 DCA_INLINE return_type cancel() {
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 DCA_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 DCA_INLINE void requestStop() {
282 areWeStoppedBool.store(true, std::memory_order_release);
283 }
284
285 DCA_INLINE bool stopRequested() {
286 return areWeStoppedBool.load(std::memory_order_acquire);
287 }
288
289 DCA_INLINE void return_void() {
290 return;
291 };
292
293 DCA_INLINE auto get_return_object() {
294 return co_routine<return_type, timeOut>{ std::coroutine_handle<promise_type>::from_promise(*this) };
295 }
296
297 DCA_INLINE std::suspend_never initial_suspend() {
298 while (!resultBuffer) {
299 std::this_thread::sleep_for(1ms);
300 }
301 return {};
302 }
303
304 DCA_INLINE std::suspend_always final_suspend() noexcept {
305 if (resultBuffer) {
306 resultBuffer->store(true);
307 }
308 return {};
309 }
310
311 DCA_INLINE void unhandled_exception() {
312 if (exceptionBuffer) {
313 exceptionBuffer->setResult(std::current_exception());
314 }
315 }
316
317 DCA_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 DCA_INLINE co_routine() = default;
329
330 DCA_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 DCA_INLINE co_routine(co_routine<return_type, timeOut>&& other) noexcept {
343 *this = std::move(other);
344 }
345
346 DCA_INLINE co_routine& operator=(const co_routine<return_type, timeOut>& other) = delete;
347 DCA_INLINE co_routine(const co_routine<return_type, timeOut>& other) = delete;
348
349 DCA_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 DCA_INLINE explicit co_routine(std::coroutine_handle<promise_type> coroutineHandleNew) {
357 *this = coroutineHandleNew;
358 };
359
360 DCA_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 DCA_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 DCA_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 DCA_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 DCA_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 DCA_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 DCA_INLINE bool await_ready() const {
469 return false;
470 }
471
472 DCA_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 DCA_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};
A co_routine - representing a potentially asynchronous operation/function.
DCA_INLINE void cancel()
Cancels the currently executing co_routine and returns the current result.
DCA_INLINE void get()
Gets the resulting value of the co_routine.
return_type_new return_type
The return type of this co_routine.
Definition CoRoutine.hpp:85
DCA_INLINE return_type cancelAndWait()
Cancels the currently running co_routine, while blocking to wait for it to complete.
DCA_INLINE return_type get()
Gets the resulting value of the co_routine.
DCA_INLINE void cancelAndWait()
Cancels the currently running co_routine, while blocking to wait for it to complete.
DCA_INLINE return_type cancel()
Cancels the currently executing co_routine and returns the current result.
DCA_INLINE co_routine_status getStatus()
Collects the status of the co_routine.
DCA_INLINE co_routine_status getStatus()
Collects the status of the co_routine.
An awaitable that can be used to launch the co_routine onto a new thread - as well as return the hand...
DCA_INLINE unique_ptr< value_type, deleter > makeUnique(arg_types &&... args)
Helper function to create a unique_ptr for a non-array object.
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:841
DCA_INLINE dca_exception(jsonifier::string_view error, std::source_location location=std::source_location::current())
Constructor to create a dca_exception with an error message and optional source location.
Definition Base.hpp:845