#include "Sigslot.hpp" #include bool SignalStub::Connection::IsOccupied() const { return id != InvalidId; } SignalStub::SignalStub(IWrapper& wrapper) : mWrapper{ &wrapper } { } SignalStub::~SignalStub() { RemoveAllConnections(); } std::span SignalStub::GetConnections() const { return mConnections; } SignalStub::Connection& SignalStub::InsertConnection(SlotGuard* guard) { Connection* result; int size = static_cast(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()) { mWrapper->RemoveFunction(conn.id); if (conn.guard) { conn.guard->RemoveConnection(conn.slotId); } conn.guard = nullptr; conn.slotId = SignalStub::InvalidId; conn.id = SignalStub::InvalidId; } } } void SignalStub::RemoveConnectionFor(SlotGuard& guard) { guard.RemoveConnectionFor(*this); } void SignalStub::RemoveAllConnections() { for (size_t i = 0; i < mConnections.size(); ++i) { RemoveConnection(i); } } 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(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::RemoveConnectionFor(SignalStub& stub) { for (auto& conn : mConnections) { if (conn.stub == &stub) { conn.stub->RemoveConnection(conn.stubId); } } } 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 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(); }