CeresEngine 0.2.0
A game development framework
Loading...
Searching...
No Matches
Generator.hpp
Go to the documentation of this file.
1//
2// CeresEngine - A game development framework
3//
4// Created by Rogiel Sulzbach.
5// Copyright (c) 2018-2022 Rogiel Sulzbach. All rights reserved.
6//
7
8#pragma once
9
10#if !defined(__cpp_coroutines) && !defined(__cpp_impl_coroutine)
11#error "Generator: Unsupported platform!"
12#endif
13
15
18
19#include <iterator>
20#include <mutex>
21#include <optional>
22
23namespace CeresEngine {
24
50 template<typename T> class Generator final {
51 class Promise;
52
53 public: // Coroutine interface
54 class Iterator;
55
58
59 private:
62
64 using suspend_always = CeresEngine::suspend_always;
65
67 using suspend_never = CeresEngine::suspend_never;
68
69 // The coroutine handle
71
72 public:
76
78 Generator(const Generator& other) = delete;
79
81 Generator& operator=(const Generator& other) = delete;
82
85 Generator(Generator&& other) noexcept : mCoroutine(other.mCoroutine) { other.mCoroutine = nullptr; }
86
90 Generator& operator=(Generator&& other) noexcept {
91 std::swap(mCoroutine, other.mCoroutine);
92 return *this;
93 }
94
97 if(mCoroutine) {
98 mCoroutine.destroy();
99 }
100 }
101
102 private:
106
107 public:
110 if(mCoroutine) {
111 CE_LIKELY {
112 mCoroutine.resume();
113 if(mCoroutine.done()) {
115 mCoroutine.promise().rethrow();
116 return Iterator{nullptr};
117 }
118 }
119 }
120 }
121 return Iterator{mCoroutine};
122 }
123
126 [[nodiscard]] Iterator end() noexcept { return Iterator{nullptr}; }
127
128 private:
131 public:
133 using Value = std::remove_reference_t<T>;
134
136 using Reference = std::conditional_t<std::is_reference_v<T>, T, T&>;
137
139 using Pointer = Value*;
140
141 private:
143 Pointer mValue = nullptr;
144
147 std::exception_ptr mException = nullptr;
148
149 public:
151 Promise() = default;
152
153 public: // Coroutine implementation
157 Generator get_return_object() noexcept { return Generator<T>{CoroutineHandle::from_promise(*this)}; }
158
161
163 [[nodiscard]] constexpr suspend_always final_suspend() const noexcept { return {}; }
164
170 suspend_always yield_value(std::remove_reference_t<T>& aValue) noexcept {
171 mValue = std::addressof(aValue);
172 return {};
173 }
174
180 suspend_always yield_value(const std::remove_reference_t<T>& aValue) noexcept {
181 mValue = std::addressof(const_cast<std::remove_reference_t<T>&>(aValue));
182 return {};
183 }
184
185 // /// Yields a new value to the generator.
186 // /// \param aValue The value to be yielded to the generator
187 // /// \return `suspend_always`
188 // suspend_always yield_value(std::remove_reference_t<T>&& aValue) noexcept {
189 // mValue = std::addressof(aValue);
190 // return {};
191 // }
192
197 mException = std::current_exception(); // NOLINT
198 }
199
203
209 template<typename U> suspend_never await_transform(U&& value) = delete;
210
212 [[nodiscard]] Reference getValue() const noexcept { return static_cast<Reference>(*mValue); }
213
216 void rethrow() {
217 if(mException) {
218 CE_UNLIKELY { std::rethrow_exception(mException); }
219 }
220 }
221 };
222
223 public:
227 private:
231
232 public:
236
241
242 public: // Iterator concept
243 using iterator_category = std::input_iterator_tag;
244 using difference_type = std::ptrdiff_t;
245 using value_type = typename Promise::Value;
246 using reference = typename Promise::Reference;
247 using pointer = typename Promise::Pointer;
248
255 [[nodiscard]] bool operator==(const Iterator& other) const noexcept { return mCoroutine.address() == other.mCoroutine.address(); }
256
261 [[nodiscard]] bool operator!=(const Iterator& other) const noexcept { return !(*this == other); }
262
267 mCoroutine.resume();
268 if(mCoroutine.done()) {
270 mCoroutine.promise().rethrow();
271 mCoroutine = nullptr;
272 }
273 }
274 return *this;
275 }
276
280 CE_FORCE_INLINE void operator++(int) { (void)operator++(); }
281
285 [[nodiscard]] reference operator*() const noexcept { return mCoroutine.promise().getValue(); }
286
290 [[nodiscard]] pointer operator->() const noexcept { return std::addressof(operator*()); }
291 };
292 };
293
319 template<typename T, typename Lockable = Mutex> class AsyncGenerator final {
320 class Promise; // Resumable Promise Requirement
321 class Iterator;
322
323 public: // Coroutine interface
326
327 private:
328 using Reference = T&;
329 using Pointer = T*;
330
333
336
338 using suspend_always = CeresEngine::suspend_always;
339
341 using suspend_never = CeresEngine::suspend_never;
342
343 private:
344 CoroutineHandle mCoroutine; // resumable handle
345
346 public:
350
353
356
359 AsyncGenerator(AsyncGenerator&& other) noexcept : mCoroutine(other.mCoroutine) { other.mCoroutine = nullptr; }
360
365 std::swap(mCoroutine, other.mCoroutine);
366 return *this;
367 }
368
370 // delete the coroutine frame
371 // coro.done() won't be checked since the usecase might want it
372 // the library only guarantees there is no leak
373 if(mCoroutine) {
374 mCoroutine.destroy();
375 }
376 }
377
378 private:
381 explicit AsyncGenerator(Promise* promise) noexcept : mCoroutine{CoroutineHandle::from_promise(*promise)} {}
382
383 public:
386 if(mCoroutine) {
387 CE_LIKELY {
388 mCoroutine.resume();
389 if(mCoroutine.done()) {
391 mCoroutine.promise().rethrow();
392 return Iterator{nullptr};
393 }
394 }
395 }
396 }
397 return Iterator{mCoroutine};
398 }
399
400 [[nodiscard]] Iterator end() noexcept { return Iterator{nullptr}; }
401
402 private:
404 friend class Iterator;
405 friend class AsyncGenerator;
406
407 public:
409 std::optional<Pointer> current = nullptr;
410
413 std::exception_ptr exception = nullptr;
414
417
421 //
422 // public:
423 // /// Allocates memory for a new instance of `Promise`.
424 // template<typename RawAllocator>
425 // static void* operator new(size_t size, RawAllocator& allocator) noexcept {
426 // return AllocatorTraits<RawAllocator>::allocate_node(allocator, size, alignof(std::max_align_t));
427 // }
428 //
429 // /// Deletes memory for an instance of `Promise`.
430 // template<typename RawAllocator>
431 // static void operator delete(void* p, std::size_t sz, RawAllocator& allocator) noexcept {
432 // AllocatorTraits<RawAllocator>::deallocate_node(allocator, p, sz, alignof(std::max_align_t));
433 // }
434
435 public:
437
439 constexpr suspend_always initial_suspend() const noexcept { return {}; }
440
442 constexpr suspend_always final_suspend() const noexcept { return {}; }
443
449 Promise& yield_value(std::remove_reference_t<T>& value) noexcept {
450 current = std::addressof(value);
451 return *this;
452 }
453
457 Promise& yield_value(std::remove_reference_t<T>&& value) noexcept {
458 current = std::addressof(value);
459 return *this;
460 }
461
466 exception = std::current_exception(); // NOLINT
467 current.reset();
468
469 // case finished:
470 // Activate the iterator anyway.
471 // It's not a good pattern but this point is the appropriate
472 // for the iterator to notice and stop the loop
473 return await_resume();
474 }
475
477 current.reset();
478
479 // case finished:
480 // Activate the iterator anyway.
481 // It's not a good pattern but this point is the appropriate
482 // for the iterator to notice and stop the loop
483 return await_resume();
484 }
485
487 // Never suspend if there is a waiting iterator.
488 // It will be resumed in `await_resume` function.
489 //
490 // !!! Using relaxed order here needs more verification !!!
491 //
492 return task.address() != nullptr;
493 }
494
495 void await_suspend(const AsyncCoroutineHandle handle) noexcept {
497 // iterator will reactivate this
498 task = handle;
499 }
500
503 {
505 coroutine = task;
506 task = nullptr;
507 }
508
509 // Resume if and only if there is a waiting work
510 //
511 if(coroutine) {
512 coroutine.resume();
513 }
514 }
515
518
521 void rethrow() {
522 if(exception) {
523 CE_UNLIKELY { std::rethrow_exception(exception); }
524 }
525 }
526 };
527
531 private:
532 Promise* mPromise = nullptr;
533
534 public:
537 Iterator() noexcept : mPromise(nullptr) {}
538
540 explicit Iterator(std::nullptr_t) noexcept : mPromise(nullptr) {}
541
545 explicit Iterator(const AsyncCoroutineHandle handle) noexcept : mPromise(nullptr) {
546 auto& p = CoroutineHandle::from_address(handle.address()).promise();
547 mPromise = std::addressof(p);
548 }
549
550 public: // Iterator concept
551 using iterator_category = std::input_iterator_tag;
552 using difference_type = std::ptrdiff_t;
553 using value_type = T;
554 using reference = T const&;
555 using pointer = T const*;
556
563 [[nodiscard]] bool operator==(const Iterator& rhs) const noexcept { return this->mPromise == rhs.mPromise; }
564
569 [[nodiscard]] bool operator!=(const Iterator& rhs) const noexcept { return !(*this == rhs); }
570
572 Iterator operator++(int) = delete; // NOLINT
573
578 // reset and advance
579 mPromise->current = nullptr;
580
581 // iterator resumes promise if it is suspended
583 return await_resume();
584 }
585
590
595
600
605
607 // case finished:
608 // Must advance because there is nothing to resume this
609 // iterator
610 if(mPromise == nullptr) {
611 return true;
612 }
613
614 // case yield:
615 // Continue the loop
616 return mPromise->current != nullptr;
617 }
618
620 // case empty:
621 // Promise suspended for some reason. Wait for it to yield
622 // Expect promise to resume this iterator appropriately
623 //
625 mPromise->task = rh;
626 }
627
629 if(mPromise) {
630 mPromise->rethrow();
631
632 // case finished:
633 // There is nothing we can do in this iterator.
634 // We have meet the end condition of `for-co_await`
635 if(mPromise->current == std::nullopt) {
636 mPromise = nullptr;
637 }
638 }
639 return *this;
640 }
641 };
642 };
643
644} // namespace CeresEngine
#define CE_FORCE_INLINE
Definition Macros.hpp:367
#define CE_LIKELY
Definition Macros.hpp:396
#define CE_UNLIKELY
Definition Macros.hpp:397
An iterator type that allows the caller to iterate over the results of a generator.
Definition Generator.hpp:530
Iterator & await_resume()
Definition Generator.hpp:628
bool operator!=(const Iterator &rhs) const noexcept
Check if two iterator are not equal.
Definition Generator.hpp:569
reference operator*() noexcept
Dereferences the iterator and returns a reference to the last yielded value.
Definition Generator.hpp:589
reference operator*() const noexcept
Dereferences the iterator and returns a reference to the last yielded value.
Definition Generator.hpp:594
Iterator & operator++()
Resumes the generator coroutine and runs until the next value is yielded by it.
Definition Generator.hpp:577
T const & reference
Definition Generator.hpp:554
Iterator(std::nullptr_t) noexcept
Initialize an past-the-end iterator.
Definition Generator.hpp:540
pointer operator->() noexcept
Dereferences the iterator and returns a pointer to the last yielded value.
Definition Generator.hpp:599
Iterator(const AsyncCoroutineHandle handle) noexcept
Creates a new Iterator object with an attached coroutine handle.
Definition Generator.hpp:545
T value_type
Definition Generator.hpp:553
bool operator==(const Iterator &rhs) const noexcept
Check if two iterator are equal.
Definition Generator.hpp:563
Promise * mPromise
Definition Generator.hpp:532
bool await_ready() const noexcept
Definition Generator.hpp:606
Iterator operator++(int)=delete
Post increment is not supported.
void await_suspend(AsyncCoroutineHandle rh) noexcept
Definition Generator.hpp:619
std::input_iterator_tag iterator_category
Definition Generator.hpp:551
T const * pointer
Definition Generator.hpp:555
pointer operator->() const noexcept
Dereferences the iterator and returns a pointer to the last yielded value.
Definition Generator.hpp:604
Iterator() noexcept
Iterator needs to be default-constructible to satisfy the Range concept.
Definition Generator.hpp:537
std::ptrdiff_t difference_type
Definition Generator.hpp:552
Definition Generator.hpp:403
AsyncGenerator get_return_object() noexcept
Definition Generator.hpp:436
AsyncCoroutineHandle task
The task currently being awaited.
Definition Generator.hpp:416
void rethrow()
If an exception was thrown in the coroutine, rethrows it.
Definition Generator.hpp:521
void return_void() noexcept
Definition Generator.hpp:476
bool await_ready() const noexcept
Definition Generator.hpp:486
Reference value() const noexcept
Definition Generator.hpp:517
Lockable mutex
A lockable object that sychronizes access to the promise when executing asynchronous code.
Definition Generator.hpp:420
Promise & yield_value(std::remove_reference_t< T > &value) noexcept
Yields a new value to the generator.
Definition Generator.hpp:449
void unhandled_exception() noexcept
A method called internally by the compiler when an exception is thrown in the coroutine.
Definition Generator.hpp:465
Promise & yield_value(std::remove_reference_t< T > &&value) noexcept
Yields a new value to the generator.
Definition Generator.hpp:457
void await_resume() noexcept
Definition Generator.hpp:501
constexpr suspend_always initial_suspend() const noexcept
Definition Generator.hpp:439
std::exception_ptr exception
The exception throw by the coroutine.
Definition Generator.hpp:413
std::optional< Pointer > current
The value returned by the last continuation of the coroutine.
Definition Generator.hpp:409
constexpr suspend_always final_suspend() const noexcept
Definition Generator.hpp:442
void await_suspend(const AsyncCoroutineHandle handle) noexcept
Definition Generator.hpp:495
A generator represents a coroutine type that produces a sequence of values of type T,...
Definition Generator.hpp:319
T * Pointer
Definition Generator.hpp:329
Iterator end() noexcept
Definition Generator.hpp:400
CoroutineHandle mCoroutine
Definition Generator.hpp:344
AsyncGenerator(AsyncGenerator &&other) noexcept
Creates a new generator by moving the contents of another.
Definition Generator.hpp:359
~AsyncGenerator() noexcept
Definition Generator.hpp:369
AsyncGenerator(Promise *promise) noexcept
Creates a new AsyncGenerator from an existing coroutine handle.
Definition Generator.hpp:381
AsyncGenerator & operator=(AsyncGenerator &&other) noexcept
Assigns the generator by moving the contents of another.
Definition Generator.hpp:364
AsyncGenerator & operator=(const AsyncGenerator &)=delete
Disables copy assignment operator.
AsyncGenerator(const AsyncGenerator &)=delete
Disables copy constructor.
CeresEngine::suspend_always suspend_always
An alias to std::suspend_always.
Definition Generator.hpp:338
CeresEngine::suspend_never suspend_never
An alias to std::suspend_never.
Definition Generator.hpp:341
AsyncGenerator() noexcept
Creates a new empty generator.
Definition Generator.hpp:349
coroutine_handle< Promise > CoroutineHandle
The coroutine handle type.
Definition Generator.hpp:332
T & Reference
Definition Generator.hpp:328
coroutine_handle<> AsyncCoroutineHandle
The coroutine handle for an asynchronous task.
Definition Generator.hpp:335
Iterator begin()
Definition Generator.hpp:385
An iterator type that allows the caller to iterate over the results of a generator.
Definition Generator.hpp:226
bool operator==(const Iterator &other) const noexcept
Check if two iterator are equal.
Definition Generator.hpp:255
reference operator*() const noexcept
Dereferences the iterator and returns a reference to the last yielded value.
Definition Generator.hpp:285
Iterator & operator++()
Resumes the generator coroutine and runs until the next value is yielded by it.
Definition Generator.hpp:266
bool operator!=(const Iterator &other) const noexcept
Check if two iterator are not equal.
Definition Generator.hpp:261
Iterator(CoroutineHandle coroutine) noexcept
Creates a new Iterator object with an attached coroutine handle.
Definition Generator.hpp:240
Iterator() noexcept
Iterator needs to be default-constructible to satisfy the Range concept.
Definition Generator.hpp:235
std::ptrdiff_t difference_type
Definition Generator.hpp:244
std::input_iterator_tag iterator_category
Definition Generator.hpp:243
CoroutineHandle mCoroutine
The coroutine handle instance.
Definition Generator.hpp:230
typename Promise::Pointer pointer
Definition Generator.hpp:247
typename Promise::Value value_type
Definition Generator.hpp:245
typename Promise::Reference reference
Definition Generator.hpp:246
pointer operator->() const noexcept
Dereferences the iterator and returns a pointer to the last yielded value.
Definition Generator.hpp:290
void operator++(int)
Resumes the generator coroutine and runs until the next value is yielded by it.
Definition Generator.hpp:280
The promise type used to implement to coroutine.
Definition Generator.hpp:130
void rethrow()
If an exception was thrown in the coroutine, rethrows it.
Definition Generator.hpp:216
void return_void() noexcept
A method called internally by the compiler when the coroutine successfully finishes.
Definition Generator.hpp:202
constexpr suspend_always initial_suspend() const noexcept
Definition Generator.hpp:160
Promise()=default
Creates a new promise object.
std::remove_reference_t< T > Value
The value type returned by the generator.
Definition Generator.hpp:133
std::conditional_t< std::is_reference_v< T >, T, T & > Reference
A reference of Value.
Definition Generator.hpp:136
void unhandled_exception() noexcept
A method called internally by the compiler when an exception is thrown in the coroutine.
Definition Generator.hpp:196
Pointer mValue
The value returned by the last continuation of the coroutine.
Definition Generator.hpp:143
Value * Pointer
A pointer of Value.
Definition Generator.hpp:139
suspend_always yield_value(const std::remove_reference_t< T > &aValue) noexcept
Yields a new value to the generator.
Definition Generator.hpp:180
suspend_always yield_value(std::remove_reference_t< T > &aValue) noexcept
Yields a new value to the generator.
Definition Generator.hpp:170
suspend_never await_transform(U &&value)=delete
Don't allow any use of 'co_await' inside the generator coroutine.
constexpr suspend_always final_suspend() const noexcept
Definition Generator.hpp:163
std::exception_ptr mException
The exception throw by the coroutine.
Definition Generator.hpp:147
Generator get_return_object() noexcept
Creates a new Generator object.
Definition Generator.hpp:157
Reference getValue() const noexcept
Definition Generator.hpp:212
A generator represents a coroutine type that produces a sequence of values of type T,...
Definition Generator.hpp:50
~Generator()
Destroys the generator and it's corresponding coroutine.
Definition Generator.hpp:96
Iterator begin()
Definition Generator.hpp:109
Generator(CoroutineHandle coroutine) noexcept
Creates a new Generator from an existing coroutine handle.
Definition Generator.hpp:105
Generator() noexcept
Creates a new empty generator.
Definition Generator.hpp:75
CeresEngine::suspend_never suspend_never
An alias to std::suspend_never.
Definition Generator.hpp:67
Iterator end() noexcept
Definition Generator.hpp:126
Generator(Generator &&other) noexcept
Creates a new generator by moving the contents of another.
Definition Generator.hpp:85
Generator & operator=(Generator &&other) noexcept
Assigns the generator by moving the contents of another.
Definition Generator.hpp:90
CeresEngine::suspend_always suspend_always
An alias to std::suspend_always.
Definition Generator.hpp:64
Generator(const Generator &other)=delete
Disables copy constructor.
Generator & operator=(const Generator &other)=delete
Disables copy assignment operator.
coroutine_handle< Promise > CoroutineHandle
The coroutine handle type.
Definition Generator.hpp:61
CoroutineHandle mCoroutine
Definition Generator.hpp:70
The class UniqueLock is a general-purpose mutex ownership wrapper allowing deferred locking,...
Definition Threading.hpp:270
Definition Application.hpp:19
decltype(auto) lock(Func &&func, Ts &... objects)
Definition Threading.hpp:667
constexpr size_t hash(const T &v)
Generates a hash for the provided type.
Definition Hash.hpp:25