#pragma once #include "Utils/fwd.hpp" #include #include #include #include #include class SignalStub { public: /// Non-template interface for Signal 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 mConnections; IWrapper* mWrapper; private: template 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 GetConnections() const; Connection& InsertConnection(SlotGuard* guard = nullptr); void RemoveConnection(int id); void RemoveConnectionFor(SlotGuard& guard); void RemoveAllConnections(); }; template class Signal : public SignalStub::IWrapper { private: // Must be in this order so that mFunctions is still intact when mStub's destructor runs std::vector> 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(args)...); } } } template 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 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 mConnections; public: friend class SignalStub; SlotGuard(); ~SlotGuard(); SlotGuard(const SlotGuard&) = delete; SlotGuard& operator=(const SlotGuard&) = delete; SlotGuard(SlotGuard&&) = default; SlotGuard& operator=(SlotGuard&&) = default; /// Disconnect 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); /// Disconnect all connections from the given stub associated with this SlotGuard. /// Implementation for SignalStub::RemoveConnectionsFor(SlotGuard&) void RemoveConnectionFor(SignalStub& stub); };