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