CeresEngine 0.2.0
A game development framework
Loading...
Searching...
No Matches
AsyncEvent.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
12
15
16namespace CeresEngine {
17
28 public:
29 class Operation;
30
31 public:
39
49
52
61 void set() noexcept;
62
70
71 public:
73 public:
74 explicit Operation(const AsyncManualResetEvent& event) noexcept;
75
77 bool await_suspend(CoroutineHandle<> awaiter) noexcept;
78 void await_resume() const noexcept {}
79
80 private:
82
86 };
87
88 private:
89 friend class Operation;
90
91 // This variable has 3 states:
92 // - this - The state is 'set'.
93 // - nullptr - The state is 'not set' with no waiters.
94 // - other - The state is 'not set'.
95 // Points to an 'Operation' that is
96 // the head of a linked-list of waiters.
98 };
99
107 public:
108 class Operation;
109
110 public:
114
125
133 void set() noexcept;
134
139
140 public:
160
161 private:
162 friend class Operation;
163
164 void resume_waiters(UInt64 initialState) const noexcept;
165
166 // Bits 0-31 - Set count
167 // Bits 32-63 - Waiter count
171 };
172
174 public:
176
177 void set() noexcept {
178 void* oldValue = mState.exchange(this, std::memory_order_release);
179 if(oldValue != nullptr && oldValue != this) {
180 // There was a waiting coroutine that we now need to resume.
181 const auto handle = *static_cast<CoroutineHandle<>*>(oldValue);
182
183 // We also need to transition the state back to 'not set' before
184 // resuming the coroutine. This operation needs to be 'acquire'
185 // so that it synchronises with other calls to .set() that execute
186 // concurrently with this call and execute the above mState.exchange(this)
187 // operation with 'release' semantics.
188 // This needs to be an exchange() instead of a store() so that it can have
189 // 'acquire' semantics.
190 (void)mState.exchange(nullptr, std::memory_order_acquire);
191
192 // Finally, resume the waiting coroutine.
193 handle.resume();
194 }
195 }
196
197 [[nodiscard]] auto operator co_await() const noexcept {
198 class Awaiter {
199 public:
200 explicit Awaiter(const SingleConsumerAsyncAutoResetEvent& event) noexcept : mEvent(event) {}
201
202 [[nodiscard]] bool await_ready() const noexcept { return false; }
203 [[nodiscard]] bool await_suspend(const CoroutineHandle<> awaitingCoroutine) noexcept {
204 mAwaitingCoroutine = awaitingCoroutine;
205
206 void* oldValue = nullptr;
207 if(!mEvent.mState.compare_exchange_strong(oldValue, &mAwaitingCoroutine, std::memory_order_release, std::memory_order_relaxed)) {
208 // This will only fail if the event was already 'set'
209 // In which case we can just reset back to 'not set'
210 // Need to use exchange() rather than store() here so we can make this
211 // operation an 'acquire' operation so that we get visibility of all
212 // writes prior to all preceding calls to .set().
213 CE_ASSERT(oldValue == &mEvent);
214 (void)mEvent.mState.exchange(nullptr, std::memory_order_acquire);
215 return false;
216 }
217
218 return true;
219 }
220
221 void await_resume() noexcept {}
222
223 private:
225 CoroutineHandle<> mAwaitingCoroutine;
226 };
227
228 return Awaiter{*this};
229 }
230
231 private:
232 // nullptr - not set, no waiter
233 // this - set
234 // other - not set, pointer is address of a coroutine_handle<> to resume.
236 };
237
251 public:
259
261 [[nodiscard]] bool is_set() const noexcept { return mState.load(std::memory_order_acquire) == state::set; }
262
268 void set() {
269 const state oldState = mState.exchange(state::set, std::memory_order_acq_rel);
271 mAwaiter.resume();
272 }
273 }
274
279 mState.compare_exchange_strong(oldState, state::not_set, std::memory_order_relaxed);
280 }
281
289 [[nodiscard]] auto operator co_await() noexcept {
290 class Awaiter {
291 public:
292 explicit Awaiter(SingleConsumerEvent& event) : mEvent(event) {}
293
294 [[nodiscard]] bool await_ready() const noexcept { return mEvent.is_set(); }
295 [[nodiscard]] bool await_suspend(const CoroutineHandle<> awaiter) {
296 mEvent.mAwaiter = awaiter;
297
298 state oldState = state::not_set;
299 return mEvent.mState.compare_exchange_strong(oldState, state::not_set_consumer_waiting, std::memory_order_release, std::memory_order_acquire);
300 }
301
302 void await_resume() noexcept {}
303
304 private:
305 SingleConsumerEvent& mEvent;
306 };
307
308 return Awaiter{*this};
309 }
310
311 private:
312 enum class state {
313 not_set,
315 set
316 };
317
318 // TODO: Merge these two fields into a single Atomic<std::uintptr_t>
319 // by encoding 'not_set' as 0 (nullptr), 'set' as 1 and
320 // 'not_set_consumer_waiting' as a coroutine handle pointer.
323 };
324
325} // namespace CeresEngine
#define CE_ASSERT(...)
Definition Macros.hpp:323
Definition AsyncEvent.hpp:141
bool await_suspend(CoroutineHandle<> awaiter) noexcept
void await_resume() const noexcept
Definition AsyncEvent.hpp:150
CoroutineHandle mAwaiter
Definition AsyncEvent.hpp:157
Atomic< UInt32 > mRefCount
Definition AsyncEvent.hpp:158
const AsyncAutoResetEvent * mEvent
Definition AsyncEvent.hpp:155
Operation * mNext
Definition AsyncEvent.hpp:156
An async auto-reset event is a coroutine synchronisation abstraction that allows one or more coroutin...
Definition AsyncEvent.hpp:106
Operation * mWaiters
Definition AsyncEvent.hpp:170
AsyncAutoResetEvent(bool initiallySet=false) noexcept
Initialise the event to either 'set' or 'not set' state.
void resume_waiters(UInt64 initialState) const noexcept
Atomic< UInt64 > mState
Definition AsyncEvent.hpp:168
void set() noexcept
Set the state of the event to 'set'.
Atomic< Operation * > mNewWaiters
Definition AsyncEvent.hpp:169
void reset() noexcept
Set the state of the event to 'not-set'.
const AsyncManualResetEvent & mEvent
Definition AsyncEvent.hpp:83
CoroutineHandle mAwaiter
Definition AsyncEvent.hpp:85
Operation * mNext
Definition AsyncEvent.hpp:84
Operation(const AsyncManualResetEvent &event) noexcept
An async manual-reset event is a coroutine synchronisation abstraction that allows one or more corout...
Definition AsyncEvent.hpp:27
void reset() noexcept
Set the state of the event to 'not-set'.
Atomic< void * > mState
Definition AsyncEvent.hpp:97
AsyncManualResetEvent(bool initiallySet=false) noexcept
Initialise the event to either 'set' or 'not set' state.
bool isSet() const noexcept
Query if the event is currently in the 'set' state.
void set() noexcept
Set the state of the event to 'set'.
void set() noexcept
Definition AsyncEvent.hpp:177
Atomic< void * > mState
Definition AsyncEvent.hpp:235
SingleConsumerAsyncAutoResetEvent(const bool initiallySet=false) noexcept
Definition AsyncEvent.hpp:175
A manual-reset event that supports only a single awaiting coroutine at a time.
Definition AsyncEvent.hpp:250
state
Definition AsyncEvent.hpp:312
Atomic< state > mState
Definition AsyncEvent.hpp:321
CoroutineHandle mAwaiter
Definition AsyncEvent.hpp:322
SingleConsumerEvent(const bool initiallySet=false) noexcept
Construct a new event, initialising to either 'set' or 'not set' state.
Definition AsyncEvent.hpp:258
void set()
Transition this event to the 'set' state if it is not already set.
Definition AsyncEvent.hpp:268
bool is_set() const noexcept
Query if this event has been set.
Definition AsyncEvent.hpp:261
void reset() noexcept
Transition this event to the 'non set' state if it was in the set state.
Definition AsyncEvent.hpp:277
Definition Application.hpp:19
std::uint64_t UInt64
Definition DataTypes.hpp:26
std::coroutine_handle< T > CoroutineHandle
A type alias to the C++ standard library coroutine handle.
Definition Coroutine.hpp:26
std::atomic< T > Atomic
The Atomic template defines an atomic type.
Definition Atomic.hpp:16
constexpr size_t hash(const T &v)
Generates a hash for the provided type.
Definition Hash.hpp:25