diff options
Diffstat (limited to 'app/source/Cplt/Utils/Sigslot.hpp')
-rw-r--r-- | app/source/Cplt/Utils/Sigslot.hpp | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/app/source/Cplt/Utils/Sigslot.hpp b/app/source/Cplt/Utils/Sigslot.hpp new file mode 100644 index 0000000..a4ab94e --- /dev/null +++ b/app/source/Cplt/Utils/Sigslot.hpp @@ -0,0 +1,165 @@ +#pragma once + +#include <Cplt/Utils/fwd.hpp> + +#include <cstddef> +#include <functional> +#include <span> +#include <utility> +#include <vector> + +class SignalStub +{ +public: + /// Non-template interface for Signal<T...> to implement (a barrier to stop template + /// arguments propagation). + class IWrapper + { + public: + virtual ~IWrapper() = default; + virtual void RemoveFunction(int id) = 0; + }; + + enum + { + InvalidId = -1, + }; + + struct Connection + { + SlotGuard* guard; + int slotId; + int id = InvalidId; // If `InvalidId`, then this "spot" is unused + + bool IsOccupied() const; + }; + +private: + std::vector<Connection> mConnections; + IWrapper* mWrapper; + +private: + template <class...> + friend class Signal; + friend class SlotGuard; + + SignalStub(IWrapper& wrapper); + ~SignalStub(); + + SignalStub(const SignalStub&) = delete; + SignalStub& operator=(const SignalStub&) = delete; + SignalStub(SignalStub&&) = default; + SignalStub& operator=(SignalStub&&) = default; + + std::span<const Connection> GetConnections() const; + Connection& InsertConnection(SlotGuard* guard = nullptr); + void RemoveConnection(int id); + void RemoveConnectionFor(SlotGuard& guard); + void RemoveAllConnections(); +}; + +template <class... TArgs> +class Signal : public SignalStub::IWrapper +{ +private: + // Must be in this order so that mFunctions is still intact when mStub's destructor runs + std::vector<std::function<void(TArgs...)>> mFunctions; + SignalStub mStub; + +public: + Signal() + : mStub(*this) + { + } + + virtual ~Signal() = default; + + Signal(const Signal&) = delete; + Signal& operator=(const Signal&) = delete; + Signal(Signal&&) = default; + Signal& operator=(Signal&&) = default; + + void operator()(TArgs... args) + { + for (auto& conn : mStub.GetConnections()) { + if (conn.IsOccupied()) { + mFunctions[conn.id](std::forward<TArgs>(args)...); + } + } + } + + template <class TFunction> + int Connect(TFunction slot) + { + auto& conn = mStub.InsertConnection(); + mFunctions.resize(std::max(mFunctions.size(), (size_t)conn.id + 1)); + mFunctions[conn.id] = std::move(slot); + return conn.id; + } + + template <class TFunction> + int Connect(SlotGuard& guard, TFunction slot) + { + auto& conn = mStub.InsertConnection(&guard); + mFunctions.resize(std::max(mFunctions.size(), (size_t)conn.id + 1)); + mFunctions[conn.id] = std::move(slot); + return conn.id; + } + + void Disconnect(int id) + { + mStub.RemoveConnection(id); + } + + void DisconnectFor(SlotGuard& guard) + { + mStub.RemoveConnectionFor(guard); + } + + void DisconnectAll() + { + mStub.RemoveAllConnections(); + } + + virtual void RemoveFunction(int id) + { + mFunctions[id] = {}; + } +}; + +/// Automatic disconnection mechanism for Signal<>. +/// Bind connection to this guard by using the Connect(SlotGuard&, TFunction) overload. +/// Either DisconnectAll() or the destructor disconnects all connections bound to this guard. +class SlotGuard +{ +private: + struct Connection + { + SignalStub* stub = nullptr; + int stubId = SignalStub::InvalidId; + }; + std::vector<Connection> mConnections; + +public: + friend class SignalStub; + SlotGuard(); + ~SlotGuard(); + + SlotGuard(const SlotGuard&) = delete; + SlotGuard& operator=(const SlotGuard&) = delete; + SlotGuard(SlotGuard&&) = default; + SlotGuard& operator=(SlotGuard&&) = default; + + /// DisconnectBySource all connection associated with this SlotGuard. + void DisconnectAll(); + +private: + /// \return Slot id. + int InsertConnection(SignalStub& stub, int stubId); + /// Remove the connection data in this associated with slotId. This does not invoke + /// the connections' stub's RemoveConnection function. + void RemoveConnection(int slotId); + /// DisconnectBySource all connections from the given stub associated with this SlotGuard. + /// Implementation for SignalStub::RemoveConnectionsFor(SlotGuard&) + void RemoveConnectionFor(SignalStub& stub); +}; |