Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // =================================================================== //
- // hedaer file ======================================================= //
- // =================================================================== //
- #include <cstddef>
- #include <functional>
- #include <span>
- #include <utility>
- #include <vector>
- class ISignalInterface {
- public:
- virtual ~ISignalInterface() = default;
- virtual void removeFunction(int id) = 0;
- };
- /// All interfaces are designed to be used by Signal and SlotGuard. Do NOT invoke manually,
- /// as they will not clean up the resource handled by the wrapper Signal.
- class SignalStub {
- public:
- enum {
- InvalidId = -1,
- };
- struct Connection {
- SlotGuard* guard;
- int slotId;
- int id = InvalidId; // If `InvalidId`, then this "spot" is unused
- bool isOccupied() const;
- };
- private:
- ISignalInterface* mInterface;
- std::vector<Connection> mConnections;
- public:
- SignalStub(ISignalInterface& iface);
- ~SignalStub();
- SignalStub(const SignalStub&) = delete;
- SignalStub& operator=(const SignalStub&) = delete;
- SignalStub(SignalStub&&) = default;
- SignalStub& operator=(SignalStub&&) = default;
- std::span<const Connection> getConnections() const { return mConnections; }
- Connection& insertConnection(SlotGuard* guard = nullptr);
- void removeConnection(int id);
- void removeAllConnections();
- private:
- void removeConnection(Connection& conn);
- };
- template <class... TArgs>
- class Signal : public ISignalInterface {
- 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 disconnectAll() {
- mStub.removeAllConnections();
- }
- virtual void removeFunction(int id) {
- mFunctions[id] = {};
- }
- };
- class SlotGuard {
- private:
- struct Connection {
- SignalStub* stub = nullptr;
- int stubId = SignalStub::InvalidId;
- };
- std::vector<Connection> mConnections;
- public:
- SlotGuard();
- ~SlotGuard();
- SlotGuard(const SlotGuard&) = delete;
- SlotGuard& operator=(const SlotGuard&) = delete;
- SlotGuard(SlotGuard&&) = default;
- SlotGuard& operator=(SlotGuard&&) = default;
- void disconnectAll();
- /// Interface for SignalStub and Signal, do NOT invoke manually.
- /// \return Slot id.
- int insertConnection(SignalStub& stub, int stubId);
- void removeConnection(int slotId);
- };
- // =================================================================== //
- // cpp file ========================================================== //
- // =================================================================== //
- #include "Sigslot.hpp"
- #include <doctest/doctest.h>
- bool SignalStub::Connection::isOccupied() const {
- return id != InvalidId;
- }
- SignalStub::SignalStub(ISignalInterface& iface)
- : mInterface{ &iface } {
- }
- SignalStub::~SignalStub() {
- removeAllConnections();
- }
- SignalStub::Connection& SignalStub::insertConnection(SlotGuard* guard) {
- Connection* result;
- int size = static_cast<int>(mConnections.size());
- for (int i = 0; i < size; ++i) {
- auto& conn = mConnections[i];
- if (!conn.isOccupied()) {
- result = &conn;
- result->id = i;
- goto setup;
- }
- }
- mConnections.push_back(Connection{});
- result = &mConnections.back();
- result->id = size;
- setup:
- if (guard) {
- result->guard = guard;
- result->slotId = guard->insertConnection(*this, result->id);
- }
- return *result;
- }
- void SignalStub::removeConnection(int id) {
- if (id >= 0 && id < mConnections.size()) {
- auto& conn = mConnections[id];
- if (conn.isOccupied()) {
- removeConnection(conn);
- }
- }
- }
- void SignalStub::removeAllConnections() {
- for (auto& conn : mConnections) {
- if (conn.isOccupied()) {
- removeConnection(conn);
- }
- }
- }
- void SignalStub::removeConnection(Connection& conn) {
- mInterface->removeFunction(conn.id);
- if (conn.guard) {
- conn.guard->removeConnection(conn.slotId);
- }
- conn.guard = nullptr;
- conn.slotId = InvalidId;
- conn.id = InvalidId;
- }
- SlotGuard::SlotGuard() {
- }
- SlotGuard::~SlotGuard() {
- disconnectAll();
- }
- void SlotGuard::disconnectAll() {
- for (auto& conn : mConnections) {
- if (conn.stub) {
- // Also calls SlotGuard::removeConnection, our copy of the data will be cleared in it
- conn.stub->removeConnection(conn.stubId);
- }
- }
- }
- int SlotGuard::insertConnection(SignalStub& stub, int stubId) {
- int size = static_cast<int>(mConnections.size());
- for (int i = 0; i < size; ++i) {
- auto& conn = mConnections[i];
- if (!conn.stub) {
- conn.stub = &stub;
- conn.stubId = stubId;
- return i;
- }
- }
- mConnections.push_back(Connection{});
- auto& conn = mConnections.back();
- conn.stub = &stub;
- conn.stubId = stubId;
- return size;
- }
- void SlotGuard::removeConnection(int slotId) {
- mConnections[slotId] = {};
- }
- TEST_CASE("Signal connect and disconnect") {
- Signal<> sig;
- int counter = 0;
- int id = sig.connect([&]() { counter++; });
- sig();
- CHECK(counter == 1);
- sig();
- CHECK(counter == 2);
- sig.disconnect(id);
- sig();
- CHECK(counter == 2);
- }
- TEST_CASE("Signal with parameters") {
- Signal<int> sig;
- int counter = 0;
- int id = sig.connect([&](int i) { counter += i; });
- sig(1);
- CHECK(counter == 1);
- sig(0);
- CHECK(counter == 1);
- sig(4);
- CHECK(counter == 5);
- sig.disconnect(id);
- sig(1);
- CHECK(counter == 5);
- }
- TEST_CASE("Signal disconnectAll()") {
- Signal<> sig;
- int counter1 = 0;
- int counter2 = 0;
- sig.connect([&]() { counter1++; });
- sig.connect([&]() { counter2++; });
- sig();
- CHECK(counter1 == 1);
- CHECK(counter2 == 1);
- sig();
- CHECK(counter1 == 2);
- CHECK(counter2 == 2);
- sig.disconnectAll();
- sig();
- CHECK(counter1 == 2);
- CHECK(counter2 == 2);
- }
- TEST_CASE("SlotGuard auto-disconnection") {
- int counter1 = 0;
- int counter2 = 0;
- Signal<> sig;
- {
- SlotGuard guard;
- sig.connect(guard, [&]() { counter1 += 1; });
- sig.connect(guard, [&]() { counter2 += 1; });
- sig();
- CHECK(counter1 == 1);
- CHECK(counter2 == 1);
- sig();
- CHECK(counter1 == 2);
- CHECK(counter2 == 2);
- }
- sig();
- CHECK(counter1 == 2);
- CHECK(counter2 == 2);
- }
- TEST_CASE("Signal destruct before SlotGuard") {
- int counter = 0;
- SlotGuard guard;
- {
- Signal<> sig2;
- sig2.connect(guard, [&]() { counter++; });
- sig2();
- CHECK(counter == 1);
- }
- // Shouldn't error
- guard.disconnectAll();
- }
Add Comment
Please, Sign In to add comment