CeresEngine 0.2.0
A game development framework
Loading...
Searching...
No Matches
Event.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#pragma once
8
14
15#include <algorithm>
16#include <cassert>
17#include <functional>
18#include <iterator>
19#include <mutex>
20#include <thread>
21#include <type_traits>
22#include <utility>
23
24namespace CeresEngine {
25
27 template<typename P, typename T, template<typename> typename F> class TEvent;
28
31 virtual void operator()(std::size_t index) const = 0;
33 };
34
45 public:
48
49 // Connection are not copy constructible or copy assignable
51
53
56 WeakEventConnection(WeakEventConnection&& other) noexcept : weakDisconnector(std::move(other.weakDisconnector)), index(other.index) {}
57
61 weakDisconnector = std::move(other.weakDisconnector);
62 index = other.index;
63 return *this;
64 }
65
68 [[nodiscard]] bool connected() const noexcept { return !weakDisconnector.expired(); }
69
76 void disconnect();
77
78 private:
82 template<typename P, typename T, template<typename> typename F> friend class TEvent;
83
91 WeakEventConnection(SPtr<EventDisconnector> const& sharedDisconnector, const std::size_t index) : weakDisconnector(sharedDisconnector), index(index) {}
92
96 std::size_t index;
97 };
98
105 public:
107 EventConnection() noexcept = default;
108
111
113 EventConnection& operator=(EventConnection const&) = delete;
114
116 EventConnection(EventConnection&& other) noexcept : connection(std::move(other.connection)) {}
117
121 reset(std::move(other.connection));
122 return *this;
123 }
124
127 EventConnection(WeakEventConnection&& c) noexcept // NOLINT
128 : connection(std::forward<WeakEventConnection>(c)) {}
129
131 ~EventConnection() { disconnect(); }
132
139 reset(std::forward<WeakEventConnection>(c));
140 return *this;
141 }
142
147 void reset(WeakEventConnection&& c = {}) {
148 disconnect();
149 connection = std::move(c);
150 }
151
154 [[nodiscard]] WeakEventConnection release() noexcept {
155 WeakEventConnection c = std::move(connection);
156 connection = WeakEventConnection{};
157 return c;
158 }
159
163 [[nodiscard]] bool connected() const noexcept { return connection.connected(); }
164
171 void disconnect() { connection.disconnect(); }
172
173 private:
176 };
177
189
192 static void yield() noexcept { std::this_thread::yield(); }
193 };
194
205 struct MutexType {};
206
211 explicit constexpr MutexLockType(const MutexType&) noexcept {}
212 };
213
216 static constexpr void yield() noexcept {}
217 };
218
237 template<typename S, typename T, typename F, typename... A> class EventAccumulator final {
238 public:
240 using ResultType = decltype(std::declval<F>()(std::declval<T>(), std::declval<typename S::SlotType::result_type>()));
241
243 //
262 EventAccumulator(S const& event, T init, F func) : event(event), init(init), func(func) {}
263
276 ResultType operator()(A&&... args) const { return event.triggerWithAccumulator(init, func, std::forward<A>(args)...); }
277
278 private:
280 S const& event;
281
284
287 };
288
311 template<typename P, typename R, template<typename> typename F, typename... A> class TEvent<P, R(A...), F> final {
312 public:
314 TEvent(TEvent const&) = delete;
315
317 TEvent& operator=(TEvent const&) = delete;
318
320 TEvent() noexcept : slotCount(0) {}
321
322 // Destruct the event object.
323 ~TEvent() { invalidateDisconnector(); }
324
326 using SlotType = F<R(A...)>;
327
330
339 template<typename T> [[nodiscard]] WeakEventConnection connect(T&& slot) {
340 MutexLockType lock{mutex};
341 slots.push_back(std::forward<T>(slot));
342 const std::size_t index = slots.size() - 1;
343 if(sharedDisconnector == nullptr) {
344 disconnector = Disconnector{this};
345 sharedDisconnector = SPtr<EventDisconnector>(&disconnector, [](auto) {});
346 }
347 ++slotCount;
348 return WeakEventConnection{sharedDisconnector, index};
349 }
350
351 template<typename T, typename Func> [[nodiscard]] WeakEventConnection connect(T* target, Func&& ptr) {
352 return connect([target, ptr](A&&... args) { return (target->*ptr)(std::forward<A>(args)...); });
353 }
354
365 void operator()(A const&... args) const {
366 for(const SlotType& slot : copySlots()) {
367 if(slot) {
368 slot(args...);
369 }
370 }
371 }
372
407 template<typename T, typename O> [[nodiscard]] EventAccumulator<TEvent, T, O, A...> accumulate(T init, O op) const {
408 static_assert(!std::is_same<R, void>::value,
409 "Unable to accumulate slot return values "
410 "with 'void' as return type.");
411 return {*this, init, op};
412 }
413
422 template<typename C> [[nodiscard]] C aggregate(A const&... args) const {
423 static_assert(!std::is_same<R, void>::value,
424 "Unable to aggregate slot return values "
425 "with 'void' as return type.");
426 C container;
427 auto iterator = std::back_inserter(container);
428 for(const SlotType& slot : copySlots()) {
429 if(slot) {
430 (*iterator) = slot(args...);
431 }
432 }
433 return container;
434 }
435
438 [[nodiscard]] SizeType getSlotCount() const noexcept { return slotCount; }
439
444 [[nodiscard]] bool empty() const noexcept { return getSlotCount() == 0; }
445
449 MutexLockType lock{mutex};
450 slots.clear();
451 slotCount = 0;
452 invalidateDisconnector();
453 }
454
455 private:
456 template<typename, typename, typename, typename...> friend class EventAccumulator;
459
461 using MutexType = typename ThreadPolicy::MutexType;
462
464 using MutexLockType = typename ThreadPolicy::MutexLockType;
465
472 void invalidateDisconnector() noexcept {
473 // If we are unlucky, some of the connected slots
474 // might be in the process of disconnecting from other threads.
475 // If this happens, we are risking to destruct the disconnector
476 // object managed by our shared pointer before they are done
477 // disconnecting. This would be bad. To solve this problem, we
478 // discard the shared pointer (that is pointing to the disconnector
479 // object within our own instance), but keep a weak pointer to that
480 // instance. We then stall the destruction until all other weak
481 // pointers have released their "lock" (indicated by the fact that
482 // we will get a nullptr when locking our weak pointer).
483 const WPtr<EventDisconnector> weak{sharedDisconnector};
484 sharedDisconnector.reset();
485 while(weak.lock() != nullptr) {
486 // we just yield here, allowing the OS to reschedule. We do
487 // this until all threads has released the disconnector object.
488 ThreadPolicy::yield();
489 }
490 }
491
499 [[nodiscard]] Vector<SlotType> copySlots() const {
500 MutexLockType lock{mutex};
501 return slots;
502 }
503
505 template<typename T, typename O>
506 [[nodiscard]] typename EventAccumulator<TEvent, T, O, A...>::ResultType triggerWithAccumulator(T value, O& func, A const&... args) const {
507 for(const SlotType& slot : copySlots()) {
508 if(slot) {
509 value = func(value, slot(args...));
510 }
511 }
512 return value;
513 }
514
521 void disconnect(std::size_t index) {
522 MutexLockType lock(mutex);
523 assert(slots.size() > index);
524 if(slots[index] != nullptr) {
525 --slotCount;
526 }
527 slots[index] = SlotType{};
528 while(!slots.empty() && !slots.back()) {
529 slots.pop_back();
530 }
531 }
532
538 struct Disconnector final : EventDisconnector {
540 Disconnector() noexcept : ptr(nullptr) {}
541
546 explicit Disconnector(TEvent<P, R(A...), F>* ptr) noexcept : ptr(ptr) {}
547
549 void operator()(const std::size_t index) const override {
550 if(ptr) {
551 ptr->disconnect(index);
552 }
553 }
554
556 TEvent<P, R(A...), F>* ptr;
557 };
558
561
564
567
570 Disconnector disconnector;
571
575 };
576
577 // Implementation of the disconnect operation of the connection class
579 if(const auto ptr = weakDisconnector.lock()) {
580 (*ptr)(index);
581 }
582 weakDisconnector.reset();
583 }
584
589 template<typename P, template<typename> typename F> auto operator co_await(TEvent<P, void(), F>& event) noexcept {
590 class Awaiter {
591 private:
593 TEvent<P, void(), F>& mEvent;
594
596 WeakEventConnection mConnection;
597
598 public:
600 explicit constexpr Awaiter(TEvent<P, void(), F>& event) noexcept : mEvent(event) {}
601
604 ~Awaiter() noexcept { mConnection.disconnect(); }
605
607 [[nodiscard]] static bool await_ready() { // NOLINT(readability-identifier-naming)
608 return false;
609 }
610
613 [[nodiscard]] bool await_suspend( // NOLINT(readability-identifier-naming)
614 CoroutineHandle<> coroutineHandle) {
615 mConnection = mEvent.connect([this, coroutineHandle]() mutable {
616 coroutineHandle.resume();
617 mConnection.disconnect();
618 });
619 return true;
620 }
621
624 static void await_resume() {} // NOLINT(readability-identifier-naming)
625 };
626
627 // Create a new awaitable and return to the user.
628 return Awaiter{event};
629 }
630
636 template<typename P, template<typename> typename F, typename... A> auto operator co_await(TEvent<P, void(A...), F>& event) noexcept {
637 class Awaiter {
638 private:
640 using ValueType = Tuple<A...>;
641
643 TEvent<P, void(A...), F>& mEvent;
644
646 EventConnection mConnection;
647
649 ValueType* mValue;
650
651 public:
653 explicit constexpr Awaiter(TEvent<P, void(A...), F>& event) noexcept : mEvent(event) {}
654
657 ~Awaiter() noexcept { mConnection.disconnect(); }
658
660 [[nodiscard]] static bool await_ready() { // NOLINT(readability-identifier-naming)
661 return false;
662 }
663
666 [[nodiscard]] bool await_suspend( // NOLINT(readability-identifier-naming)
667 CoroutineHandle<> coroutineHandle) {
668 mConnection = mEvent.connect([this, coroutineHandle](A... args) mutable {
669 ValueType value = std::tie(args...);
670 mValue = &value;
671 coroutineHandle.resume();
672 mConnection.disconnect();
673 });
674 return true;
675 }
676
679 [[nodiscard]] decltype(auto) await_resume() { // NOLINT(readability-identifier-naming)
680 if constexpr(sizeof...(A) == 1) {
681 return std::get<0>(*mValue);
682 } else {
683 return *mValue;
684 }
685 }
686 };
687
688 // Create a new awaitable and return to the user.
689 return Awaiter{event};
690 }
691
699 template<typename T, template<typename> typename F = std::function> using Event = TEvent<MultithreadPolicy, T, F>;
700
707 template<typename T, template<typename> typename F = std::function> using UnsafeEvent = TEvent<SinglethreadPolicy, T, F>;
708
709} // namespace CeresEngine
Event accumulator class template.
Definition Event.hpp:237
T init
Initial value of the accumulate algorithm.
Definition Event.hpp:283
EventAccumulator(S const &event, T init, F func)
Construct a EventAccumulator as a proxy to a given event.
Definition Event.hpp:262
ResultType operator()(A &&... args) const
Function call operator.
Definition Event.hpp:276
S const & event
Reference to the underlying event to proxy.
Definition Event.hpp:280
F func
Accumulator function.
Definition Event.hpp:286
decltype(std::declval< F >()(std::declval< T >(), std::declval< typename S::SlotType::result_type >())) ResultType
Result type when calling the accumulating function operator.
Definition Event.hpp:240
Scoped connection class.
Definition Event.hpp:104
WeakEventConnection release() noexcept
Release the underlying connection, without disconnecting it.
Definition Event.hpp:154
~EventConnection()
destructor
Definition Event.hpp:131
EventConnection & operator=(WeakEventConnection &&c)
Assignment operator moving a new connection into the instance.
Definition Event.hpp:138
bool connected() const noexcept
Definition Event.hpp:163
WeakEventConnection connection
Underlying connection object.
Definition Event.hpp:175
EventConnection(WeakEventConnection &&c) noexcept
Construct a scoped connection from a connection object.
Definition Event.hpp:127
void disconnect()
Disconnect the slot from the connection.
Definition Event.hpp:171
void reset(WeakEventConnection &&c={})
Reset the underlying connection to another connection.
Definition Event.hpp:147
EventConnection() noexcept=default
Scoped are default constructible.
EventConnection & operator=(EventConnection &&other) noexcept
Move assign operator.
Definition Event.hpp:120
SPtr< EventDisconnector > sharedDisconnector
Shared pointer to the disconnector.
Definition Event.hpp:574
typename Vector< SlotType >::size_type SizeType
Type that is used for counting the slots connected to this event.
Definition Event.hpp:329
EventAccumulator< TEvent, T, O, A... >::ResultType triggerWithAccumulator(T value, O &func, A const &... args) const
Implementation of the event accumulator function call.
Definition Event.hpp:506
EventAccumulator< TEvent, T, O, A... > accumulate(T init, O op) const
Construct a accumulator proxy object for the event.
Definition Event.hpp:407
C aggregate(A const &... args) const
Trigger the event, calling the slots and aggregate all the slot return values into a container.
Definition Event.hpp:422
WeakEventConnection connect(T &&slot)
Connect a new slot to the event.
Definition Event.hpp:339
Vector< SlotType > slots
Vector of all connected slots.
Definition Event.hpp:563
Vector< SlotType > copySlots() const
Retrieve a copy of the current slots.
Definition Event.hpp:499
TEvent(TEvent const &)=delete
events are not copy constructible
void disconnectAllSlots()
Disconnects all slots.
Definition Event.hpp:448
void invalidateDisconnector() noexcept
Invalidate the internal disconnector object in a way that is safe according to the current thread pol...
Definition Event.hpp:472
MutexType mutex
Mutex to synchronize access to the slot vector.
Definition Event.hpp:560
SizeType getSlotCount() const noexcept
Count the number of slots connected to this event.
Definition Event.hpp:438
SizeType slotCount
Number of connected slots.
Definition Event.hpp:566
void operator()(A const &... args) const
Function call operator.
Definition Event.hpp:365
friend class EventAccumulator
Definition Event.hpp:456
typename ThreadPolicy::MutexLockType MutexLockType
Type of mutex lock, provided by threading policy.
Definition Event.hpp:464
typename ThreadPolicy::MutexType MutexType
Type of mutex, provided by threading policy.
Definition Event.hpp:461
Disconnector disconnector
Disconnector operation, used for executing disconnection in a type erased manner.
Definition Event.hpp:570
~TEvent()
Definition Event.hpp:323
F< R(A...)> SlotType
Type that will be used to store the slots for this event type.
Definition Event.hpp:326
void disconnect(std::size_t index)
Implementation of the disconnection operation.
Definition Event.hpp:521
WeakEventConnection connect(T *target, Func &&ptr)
Definition Event.hpp:351
TEvent & operator=(TEvent const &)=delete
events are not copy assignable
TEvent() noexcept
events are default constructible
Definition Event.hpp:320
P ThreadPolicy
Thread policy currently in use.
Definition Event.hpp:458
bool empty() const noexcept
Determine if the event is empty, i.e.
Definition Event.hpp:444
Base template for the event class.
Definition Event.hpp:27
Tuple is a fixed-size collection of heterogeneous values.
Definition Tuple.hpp:15
The class UniqueLock is a general-purpose mutex ownership wrapper allowing deferred locking,...
Definition Threading.hpp:270
Connection class.
Definition Event.hpp:44
WPtr< EventDisconnector > weakDisconnector
Weak pointer to the current disconnector functor.
Definition Event.hpp:94
bool connected() const noexcept
Definition Event.hpp:68
WeakEventConnection() noexcept
Default constructor.
Definition Event.hpp:47
std::size_t index
Slot index of the connected slot.
Definition Event.hpp:96
WeakEventConnection & operator=(WeakEventConnection const &)=delete
WeakEventConnection(WeakEventConnection &&other) noexcept
Move constructor.
Definition Event.hpp:56
WeakEventConnection(WeakEventConnection const &)=delete
void disconnect()
Disconnect the slot from the connection.
Definition Event.hpp:578
WeakEventConnection & operator=(WeakEventConnection &&other) noexcept
Move assign operator.
Definition Event.hpp:60
WeakEventConnection(SPtr< EventDisconnector > const &sharedDisconnector, const std::size_t index)
Create a connection.
Definition Event.hpp:91
Definition Application.hpp:19
decltype(auto) lock(Func &&func, Ts &... objects)
Definition Threading.hpp:667
std::shared_ptr< T > SPtr
SPtr is a smart pointer that retains shared ownership of an object through a pointer.
Definition SmartPtr.hpp:37
std::coroutine_handle< T > CoroutineHandle
A type alias to the C++ standard library coroutine handle.
Definition Coroutine.hpp:26
std::vector< T, ScopedAllocatorAdaptor< StdAllocator< T, RawAllocator > > > Vector
Vector is a sequence container that encapsulates dynamic size arrays.
Definition Vector.hpp:17
std::mutex Mutex
The Mutex class is a synchronization primitive that can be used to protect shared data from being sim...
Definition Threading.hpp:73
std::weak_ptr< T > WPtr
WPtr is a smart pointer that holds a non-owning ("weak") reference to an object that is managed by SP...
Definition SmartPtr.hpp:42
constexpr size_t hash(const T &v)
Generates a hash for the provided type.
Definition Hash.hpp:25
Definition Span.hpp:668
Interface for type erasure when disconnecting slots.
Definition Event.hpp:30
virtual void operator()(std::size_t index) const =0
virtual ~EventDisconnector() noexcept=default
Policy for multi threaded use of events.
Definition Event.hpp:186
static void yield() noexcept
Function that yields the current thread, allowing the OS to reschedule.
Definition Event.hpp:192
Mutex MutexType
Definition Event.hpp:187
Dummy lock type, that doesn't do any locking.
Definition Event.hpp:208
constexpr MutexLockType(const MutexType &) noexcept
A lock type must be constructible from a mutex type from the same thread policy.
Definition Event.hpp:211
Dummy mutex type that doesn't do anything.
Definition Event.hpp:205
Policy for single threaded use of events.
Definition Event.hpp:203
static constexpr void yield() noexcept
Dummy implementation of thread yielding, that doesn't do any actual yielding.
Definition Event.hpp:216
Disconnector(TEvent< P, R(A...), F > *ptr) noexcept
Create a disconnector that works with a given event instance.
Definition Event.hpp:546
Disconnector() noexcept
Default constructor, resulting in a no-op disconnector.
Definition Event.hpp:540
void operator()(const std::size_t index) const override
Definition Event.hpp:549
TEvent< P, R(A...), F > * ptr
Pointer to the current event.
Definition Event.hpp:556