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 /// \brief A CoRoutine - representing a potentially asynchronous operation/function.
51 /// \tparam RTy The type of parameter that is returned by the CoRoutine.
52 template<typename RTy> class CoRoutine {
53 public:
54 class DiscordCoreAPI_Dll promise_type {
55 public:
56 template<typename RTy02> friend class CoRoutine;
57
58 void requestStop() {
59 this->areWeStoppedBool.store(true);
60 }
61
62 bool areWeStopped() {
63 return this->areWeStoppedBool.load();
64 }
65
66 void return_value(RTy&& at) {
67 this->result = std::move(at);
68 }
69
70 CoRoutine<RTy> get_return_object() {
71 return CoRoutine<RTy>{ std::coroutine_handle<CoRoutine<RTy>::promise_type>::from_promise(*this) };
72 }
73
74 std::suspend_never initial_suspend() {
75 return {};
76 }
77
78 std::suspend_always final_suspend() noexcept {
79 if (this->areWeDone) {
80 this->areWeDone->store(true);
81 }
82 return {};
83 }
84
85 void unhandled_exception() {
86 if (this->exceptionBuffer) {
87 this->exceptionBuffer->send(std::current_exception());
88 }
89 }
90
91 protected:
92 UnboundedMessageBlock<std::exception_ptr>* exceptionBuffer{ nullptr };
93 std::atomic_bool areWeStoppedBool{};
94 std::atomic_bool* areWeDone{ nullptr };
95 RTy result{};
96 };
97
98 CoRoutine<RTy>& operator=(CoRoutine<RTy>&& other) noexcept {
99 if (this != &other) {
100 this->coroutineHandle = other.coroutineHandle;
101 other.coroutineHandle = nullptr;
102 this->coroutineHandle.promise().exceptionBuffer = &this->exceptionBuffer;
103 this->coroutineHandle.promise().areWeDone = &this->areWeDone;
104 this->currentStatus.store(other.currentStatus.load());
105 other.currentStatus.store(CoRoutineStatus::Cancelled);
106 }
107 return *this;
108 };
109
110 CoRoutine(CoRoutine<RTy>&& other) noexcept {
111 *this = std::move(other);
112 }
113
114 CoRoutine<RTy>& operator=(const CoRoutine<RTy>& other) = delete;
115
116 CoRoutine(const CoRoutine<RTy>& other) = delete;
117
118 CoRoutine<RTy>& operator=(std::coroutine_handle<CoRoutine<RTy>::promise_type> coroutineHandleNew) {
119 this->coroutineHandle = coroutineHandleNew;
120 this->coroutineHandle.promise().exceptionBuffer = &this->exceptionBuffer;
121 this->coroutineHandle.promise().areWeDone = &this->areWeDone;
122 return *this;
123 }
124
125 explicit CoRoutine(std::coroutine_handle<CoRoutine<RTy>::promise_type> coroutineHandleNew) {
126 *this = coroutineHandleNew;
127 };
128
129 ~CoRoutine() {
130 if (this && this->coroutineHandle) {
131 this->coroutineHandle.promise().exceptionBuffer = nullptr;
132 this->coroutineHandle.promise().areWeDone = nullptr;
133 if (this->coroutineHandle.done()) {
134 this->coroutineHandle.destroy();
135 }
136 }
137 }
138
139 /// \brief Collects the status of the CoRoutine.
140 /// \returns CoRoutineStatus The status of the CoRoutine.
142 if (!this->coroutineHandle) {
143 this->currentStatus.store(CoRoutineStatus::Cancelled);
144 } else if (this->coroutineHandle && !this->coroutineHandle.done()) {
145 this->currentStatus.store(CoRoutineStatus::Running);
146 } else if (this->coroutineHandle && this->coroutineHandle.done()) {
147 this->currentStatus.store(CoRoutineStatus::Complete);
148 }
149 return this->currentStatus.load();
150 }
151
152 /// \brief Gets the resulting value of the CoRoutine.
153 /// \returns RTy The return value of the CoRoutine.
154 RTy get() {
155 if (this && this->coroutineHandle) {
156 while (!this->areWeDone.load()) {
157 std::this_thread::sleep_for(1ms);
158 }
159 this->currentStatus.store(CoRoutineStatus::Complete);
160 std::exception_ptr exceptionPtr{};
161 while (this->exceptionBuffer.tryReceive(exceptionPtr)) {
162 std::rethrow_exception(exceptionPtr);
163 std::this_thread::sleep_for(1ms);
164 }
165 this->result = std::move(this->coroutineHandle.promise().result);
166 return this->result;
167 } else {
168 throw CoRoutineError("CoRoutine::get(), You called get() on a CoRoutine that is "
169 "not in a valid state.");
170 }
171 return RTy{};
172 }
173
174 /// \brief Cancels the currently executing CoRoutine and returns the current result.
175 /// \returns RTy The return value of the CoRoutine.
176 RTy cancel() {
177 if (this && this->coroutineHandle) {
178 if (!this->coroutineHandle.done()) {
179 this->coroutineHandle.promise().requestStop();
180 while (!this->areWeDone.load()) {
181 std::this_thread::sleep_for(1ms);
182 }
183 }
184 std::exception_ptr exceptionPtr{};
185 this->currentStatus.store(CoRoutineStatus::Cancelled);
186 while (this->exceptionBuffer.tryReceive(exceptionPtr)) {
187 std::rethrow_exception(exceptionPtr);
188 std::this_thread::sleep_for(1ms);
189 }
190 this->result = std::move(this->coroutineHandle.promise().result);
191 return this->result;
192 }
193 return RTy{};
194 }
195
196 protected:
197 std::coroutine_handle<CoRoutine<RTy>::promise_type> coroutineHandle{ nullptr };
198 std::atomic<CoRoutineStatus> currentStatus{ CoRoutineStatus::Idle };
199 UnboundedMessageBlock<std::exception_ptr> exceptionBuffer{};
200 std::atomic_bool areWeDone{};
201 RTy result{};
202 };
203
204 /// \brief A CoRoutine - representing a potentially asynchronous operation/function.
205 /// \tparam void The type of parameter that is returned by the CoRoutine.
206 template<> class CoRoutine<void> {
207 public:
208 class DiscordCoreAPI_Dll promise_type {
209 public:
210 template<typename RTy> friend class CoRoutine;
211
212 void requestStop() {
213 this->areWeStoppedBool.store(true);
214 }
215
216 bool areWeStopped() {
217 return this->areWeStoppedBool.load();
218 }
219
220 void return_void() {
221 }
222
223 CoRoutine<void> get_return_object() {
224 return CoRoutine<void>{ std::coroutine_handle<CoRoutine<void>::promise_type>::from_promise(*this) };
225 }
226
227 std::suspend_never initial_suspend() {
228 return {};
229 }
230
231 std::suspend_always final_suspend() noexcept {
232 if (this->areWeDone) {
233 this->areWeDone->store(true);
234 }
235 return {};
236 }
237
238 void unhandled_exception() {
239 if (this->exceptionBuffer) {
240 this->exceptionBuffer->send(std::current_exception());
241 }
242 }
243
244 protected:
245 UnboundedMessageBlock<std::exception_ptr>* exceptionBuffer{ nullptr };
246 std::atomic_bool areWeStoppedBool{};
247 std::atomic_bool* areWeDone{ nullptr };
248 };
249
250 CoRoutine<void>& operator=(CoRoutine<void>&& other) noexcept {
251 if (this != &other) {
252 this->coroutineHandle = other.coroutineHandle;
253 other.coroutineHandle = nullptr;
254 this->coroutineHandle.promise().exceptionBuffer = &this->exceptionBuffer;
255 this->coroutineHandle.promise().areWeDone = &this->areWeDone;
256 this->currentStatus.store(other.currentStatus.load());
257 other.currentStatus.store(CoRoutineStatus::Cancelled);
258 }
259 return *this;
260 };
261
262 CoRoutine(CoRoutine<void>&& other) noexcept {
263 *this = std::move(other);
264 }
265
266 CoRoutine<void>& operator=(const CoRoutine<void>& other) = delete;
267
268 CoRoutine(const CoRoutine<void>& other) = delete;
269
270 CoRoutine<void>& operator=(std::coroutine_handle<CoRoutine<void>::promise_type> coroutineHandleNew) {
271 this->coroutineHandle = coroutineHandleNew;
272 this->coroutineHandle.promise().exceptionBuffer = &this->exceptionBuffer;
273 this->coroutineHandle.promise().areWeDone = &this->areWeDone;
274 return *this;
275 }
276
277 explicit CoRoutine(std::coroutine_handle<CoRoutine<void>::promise_type> coroutineHandleNew) {
278 *this = coroutineHandleNew;
279 };
280
281 ~CoRoutine() {
282 if (this && this->coroutineHandle) {
283 this->coroutineHandle.promise().exceptionBuffer = nullptr;
284 this->coroutineHandle.promise().areWeDone = nullptr;
285 if (this->coroutineHandle.done()) {
286 this->coroutineHandle.destroy();
287 }
288 }
289 }
290
291 /// \brief Collects the status of the CoRoutine.
292 /// \returns CoRoutineStatus The status of the CoRoutine.
294 if (!this->coroutineHandle) {
295 this->currentStatus.store(CoRoutineStatus::Cancelled);
296 } else if (this->coroutineHandle && !this->coroutineHandle.done()) {
297 this->currentStatus.store(CoRoutineStatus::Running);
298 } else if (this->coroutineHandle && this->coroutineHandle.done()) {
299 this->currentStatus.store(CoRoutineStatus::Complete);
300 }
301 return this->currentStatus.load();
302 }
303
304 /// \brief Gets the resulting value of the CoRoutine.
305 void get() {
306 if (this && this->coroutineHandle) {
307 while (!this->areWeDone.load()) {
308 std::this_thread::sleep_for(1ms);
309 }
310 this->currentStatus.store(CoRoutineStatus::Complete);
311 std::exception_ptr exceptionPtr{};
312 while (this->exceptionBuffer.tryReceive(exceptionPtr)) {
313 std::rethrow_exception(exceptionPtr);
314 std::this_thread::sleep_for(1ms);
315 }
316 } else {
317 throw CoRoutineError("CoRoutine::get(), You called get() on a CoRoutine that is "
318 "not in a valid state.");
319 }
320 }
321
322 /// \brief Cancels the currently executing CoRoutine and returns the current result.
323 void cancel() {
324 if (this && this->coroutineHandle) {
325 if (!this->coroutineHandle.done()) {
326 this->coroutineHandle.promise().requestStop();
327 while (!this->areWeDone.load()) {
328 std::this_thread::sleep_for(1ms);
329 }
330 }
331 this->currentStatus.store(CoRoutineStatus::Cancelled);
332 std::exception_ptr exceptionPtr{};
333 while (this->exceptionBuffer.tryReceive(exceptionPtr)) {
334 std::rethrow_exception(exceptionPtr);
335 std::this_thread::sleep_for(1ms);
336 }
337 }
338 }
339
340 protected:
341 std::coroutine_handle<CoRoutine<void>::promise_type> coroutineHandle{ nullptr };
342 std::atomic<CoRoutineStatus> currentStatus{ CoRoutineStatus::Idle };
343 UnboundedMessageBlock<std::exception_ptr> exceptionBuffer{};
344 std::atomic_bool areWeDone{};
345 };
346
347 class DiscordCoreAPI_Dll NewThreadAwaiterBase {
348 public:
349 static DiscordCoreInternal::CoRoutineThreadPool threadPool;
350 };
351
352 /// \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.
353 /// \tparam RTy The type of value returned by the containing CoRoutine.
354 template<typename RTy> class NewThreadAwaiter : public NewThreadAwaiterBase {
355 public:
356 bool await_ready() const noexcept {
357 return false;
358 }
359
360 void await_suspend(std::coroutine_handle<typename CoRoutine<RTy>::promise_type> coroHandleNew) noexcept {
361 NewThreadAwaiterBase::threadPool.submitTask(coroHandleNew);
362 this->coroHandle = coroHandleNew;
363 }
364
365 auto await_resume() noexcept {
366 return this->coroHandle;
367 }
368
369 protected:
370 std::coroutine_handle<typename CoRoutine<RTy>::promise_type> coroHandle{};
371 };
372
373 /**@}*/
374};// 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:52
CoRoutineStatus getStatus()
Collects the status of the CoRoutine.
Definition: CoRoutine.hpp:141
RTy get()
Gets the resulting value of the CoRoutine.
Definition: CoRoutine.hpp:154
RTy cancel()
Cancels the currently executing CoRoutine and returns the current result.
Definition: CoRoutine.hpp:176
void cancel()
Cancels the currently executing CoRoutine and returns the current result.
Definition: CoRoutine.hpp:323
CoRoutineStatus getStatus()
Collects the status of the CoRoutine.
Definition: CoRoutine.hpp:293
void get()
Gets the resulting value of the CoRoutine.
Definition: CoRoutine.hpp:305
An awaitable that can be used to launch the CoRoutine onto a new thread - as well as return the handl...
Definition: CoRoutine.hpp:354
A thread-safe messaging block for data-structures.
Definition: Utilities.hpp:1194
bool tryReceive(OTy &object)
Tries to receive an object of type OTy to be placed into a reference.
Definition: Utilities.hpp:1254