hnOsmium0001

sigslot

Jan 14th, 2021 (edited)
233
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // =================================================================== //
  2. // hedaer file ======================================================= //
  3. // =================================================================== //
  4.  
  5. #include <cstddef>
  6. #include <functional>
  7. #include <span>
  8. #include <utility>
  9. #include <vector>
  10.  
  11. class ISignalInterface {
  12. public:
  13.     virtual ~ISignalInterface() = default;
  14.     virtual void removeFunction(int id) = 0;
  15. };
  16.  
  17. /// All interfaces are designed to be used by Signal and SlotGuard. Do NOT invoke manually,
  18. /// as they will not clean up the resource handled by the wrapper Signal.
  19. class SignalStub {
  20. public:
  21.     enum {
  22.         InvalidId = -1,
  23.     };
  24.  
  25.     struct Connection {
  26.         SlotGuard* guard;
  27.         int slotId;
  28.         int id = InvalidId; // If `InvalidId`, then this "spot" is unused
  29.  
  30.         bool isOccupied() const;
  31.     };
  32.  
  33. private:
  34.     ISignalInterface* mInterface;
  35.     std::vector<Connection> mConnections;
  36.  
  37. public:
  38.     SignalStub(ISignalInterface& iface);
  39.     ~SignalStub();
  40.  
  41.     SignalStub(const SignalStub&) = delete;
  42.     SignalStub& operator=(const SignalStub&) = delete;
  43.     SignalStub(SignalStub&&) = default;
  44.     SignalStub& operator=(SignalStub&&) = default;
  45.  
  46.     std::span<const Connection> getConnections() const { return mConnections; }
  47.     Connection& insertConnection(SlotGuard* guard = nullptr);
  48.     void removeConnection(int id);
  49.     void removeAllConnections();
  50.  
  51. private:
  52.     void removeConnection(Connection& conn);
  53. };
  54.  
  55. template <class... TArgs>
  56. class Signal : public ISignalInterface {
  57. private:
  58.     // Must be in this order so that mFunctions is still intact when mStub's destructor runs
  59.     std::vector<std::function<void(TArgs...)>> mFunctions;
  60.     SignalStub mStub;
  61.  
  62. public:
  63.     Signal()
  64.         : mStub(*this) {
  65.     }
  66.  
  67.     virtual ~Signal() = default;
  68.  
  69.     Signal(const Signal&) = delete;
  70.     Signal& operator=(const Signal&) = delete;
  71.     Signal(Signal&&) = default;
  72.     Signal& operator=(Signal&&) = default;
  73.  
  74.     void operator()(TArgs... args) {
  75.         for (auto& conn : mStub.getConnections()) {
  76.             if (conn.isOccupied()) {
  77.                 mFunctions[conn.id](std::forward<TArgs>(args)...);
  78.             }
  79.         }
  80.     }
  81.  
  82.     template <class TFunction>
  83.     int connect(TFunction slot) {
  84.         auto& conn = mStub.insertConnection();
  85.         mFunctions.resize(std::max(mFunctions.size(), (size_t)conn.id + 1));
  86.         mFunctions[conn.id] = std::move(slot);
  87.         return conn.id;
  88.     }
  89.  
  90.     template <class TFunction>
  91.     int connect(SlotGuard& guard, TFunction slot) {
  92.         auto& conn = mStub.insertConnection(&guard);
  93.         mFunctions.resize(std::max(mFunctions.size(), (size_t)conn.id + 1));
  94.         mFunctions[conn.id] = std::move(slot);
  95.         return conn.id;
  96.     }
  97.  
  98.     void disconnect(int id) {
  99.         mStub.removeConnection(id);
  100.     }
  101.  
  102.     void disconnectAll() {
  103.         mStub.removeAllConnections();
  104.     }
  105.  
  106.     virtual void removeFunction(int id) {
  107.         mFunctions[id] = {};
  108.     }
  109. };
  110.  
  111. class SlotGuard {
  112. private:
  113.     struct Connection {
  114.         SignalStub* stub = nullptr;
  115.         int stubId = SignalStub::InvalidId;
  116.     };
  117.     std::vector<Connection> mConnections;
  118.  
  119. public:
  120.     SlotGuard();
  121.     ~SlotGuard();
  122.  
  123.     SlotGuard(const SlotGuard&) = delete;
  124.     SlotGuard& operator=(const SlotGuard&) = delete;
  125.     SlotGuard(SlotGuard&&) = default;
  126.     SlotGuard& operator=(SlotGuard&&) = default;
  127.  
  128.     void disconnectAll();
  129.  
  130.     /// Interface for SignalStub and Signal, do NOT invoke manually.
  131.     /// \return Slot id.
  132.     int insertConnection(SignalStub& stub, int stubId);
  133.     void removeConnection(int slotId);
  134. };
  135.  
  136. // =================================================================== //
  137. // cpp file ========================================================== //
  138. // =================================================================== //
  139.  
  140. #include "Sigslot.hpp"
  141.  
  142. #include <doctest/doctest.h>
  143.  
  144. bool SignalStub::Connection::isOccupied() const {
  145.     return id != InvalidId;
  146. }
  147.  
  148. SignalStub::SignalStub(ISignalInterface& iface)
  149.     : mInterface{ &iface } {
  150. }
  151.  
  152. SignalStub::~SignalStub() {
  153.     removeAllConnections();
  154. }
  155.  
  156. SignalStub::Connection& SignalStub::insertConnection(SlotGuard* guard) {
  157.     Connection* result;
  158.     int size = static_cast<int>(mConnections.size());
  159.     for (int i = 0; i < size; ++i) {
  160.         auto& conn = mConnections[i];
  161.         if (!conn.isOccupied()) {
  162.             result = &conn;
  163.             result->id = i;
  164.             goto setup;
  165.         }
  166.     }
  167.  
  168.     mConnections.push_back(Connection{});
  169.     result = &mConnections.back();
  170.     result->id = size;
  171.  
  172. setup:
  173.     if (guard) {
  174.         result->guard = guard;
  175.         result->slotId = guard->insertConnection(*this, result->id);
  176.     }
  177.     return *result;
  178. }
  179.  
  180. void SignalStub::removeConnection(int id) {
  181.     if (id >= 0 && id < mConnections.size()) {
  182.         auto& conn = mConnections[id];
  183.         if (conn.isOccupied()) {
  184.             removeConnection(conn);
  185.         }
  186.     }
  187. }
  188.  
  189. void SignalStub::removeAllConnections() {
  190.     for (auto& conn : mConnections) {
  191.         if (conn.isOccupied()) {
  192.             removeConnection(conn);
  193.         }
  194.     }
  195. }
  196.  
  197. void SignalStub::removeConnection(Connection& conn) {
  198.     mInterface->removeFunction(conn.id);
  199.     if (conn.guard) {
  200.         conn.guard->removeConnection(conn.slotId);
  201.     }
  202.  
  203.     conn.guard = nullptr;
  204.     conn.slotId = InvalidId;
  205.     conn.id = InvalidId;
  206. }
  207.  
  208. SlotGuard::SlotGuard() {
  209. }
  210.  
  211. SlotGuard::~SlotGuard() {
  212.     disconnectAll();
  213. }
  214.  
  215. void SlotGuard::disconnectAll() {
  216.     for (auto& conn : mConnections) {
  217.         if (conn.stub) {
  218.             // Also calls SlotGuard::removeConnection, our copy of the data will be cleared in it
  219.             conn.stub->removeConnection(conn.stubId);
  220.         }
  221.     }
  222. }
  223.  
  224. int SlotGuard::insertConnection(SignalStub& stub, int stubId) {
  225.     int size = static_cast<int>(mConnections.size());
  226.     for (int i = 0; i < size; ++i) {
  227.         auto& conn = mConnections[i];
  228.         if (!conn.stub) {
  229.             conn.stub = &stub;
  230.             conn.stubId = stubId;
  231.             return i;
  232.         }
  233.     }
  234.  
  235.     mConnections.push_back(Connection{});
  236.     auto& conn = mConnections.back();
  237.     conn.stub = &stub;
  238.     conn.stubId = stubId;
  239.     return size;
  240. }
  241.  
  242. void SlotGuard::removeConnection(int slotId) {
  243.     mConnections[slotId] = {};
  244. }
  245.  
  246. TEST_CASE("Signal connect and disconnect") {
  247.     Signal<> sig;
  248.  
  249.     int counter = 0;
  250.     int id = sig.connect([&]() { counter++; });
  251.  
  252.     sig();
  253.     CHECK(counter == 1);
  254.  
  255.     sig();
  256.     CHECK(counter == 2);
  257.  
  258.     sig.disconnect(id);
  259.     sig();
  260.     CHECK(counter == 2);
  261. }
  262.  
  263. TEST_CASE("Signal with parameters") {
  264.     Signal<int> sig;
  265.  
  266.     int counter = 0;
  267.     int id = sig.connect([&](int i) { counter += i; });
  268.  
  269.     sig(1);
  270.     CHECK(counter == 1);
  271.  
  272.     sig(0);
  273.     CHECK(counter == 1);
  274.  
  275.     sig(4);
  276.     CHECK(counter == 5);
  277.  
  278.     sig.disconnect(id);
  279.     sig(1);
  280.     CHECK(counter == 5);
  281. }
  282.  
  283. TEST_CASE("Signal disconnectAll()") {
  284.     Signal<> sig;
  285.  
  286.     int counter1 = 0;
  287.     int counter2 = 0;
  288.     sig.connect([&]() { counter1++; });
  289.     sig.connect([&]() { counter2++; });
  290.  
  291.     sig();
  292.     CHECK(counter1 == 1);
  293.     CHECK(counter2 == 1);
  294.  
  295.     sig();
  296.     CHECK(counter1 == 2);
  297.     CHECK(counter2 == 2);
  298.  
  299.     sig.disconnectAll();
  300.     sig();
  301.     CHECK(counter1 == 2);
  302.     CHECK(counter2 == 2);
  303. }
  304.  
  305. TEST_CASE("SlotGuard auto-disconnection") {
  306.     int counter1 = 0;
  307.     int counter2 = 0;
  308.     Signal<> sig;
  309.  
  310.     {
  311.         SlotGuard guard;
  312.         sig.connect(guard, [&]() { counter1 += 1; });
  313.         sig.connect(guard, [&]() { counter2 += 1; });
  314.  
  315.         sig();
  316.         CHECK(counter1 == 1);
  317.         CHECK(counter2 == 1);
  318.  
  319.         sig();
  320.         CHECK(counter1 == 2);
  321.         CHECK(counter2 == 2);
  322.     }
  323.  
  324.     sig();
  325.     CHECK(counter1 == 2);
  326.     CHECK(counter2 == 2);
  327. }
  328.  
  329. TEST_CASE("Signal destruct before SlotGuard") {
  330.     int counter = 0;
  331.     SlotGuard guard;
  332.  
  333.     {
  334.         Signal<> sig2;
  335.         sig2.connect(guard, [&]() { counter++; });
  336.  
  337.         sig2();
  338.         CHECK(counter == 1);
  339.     }
  340.  
  341.     // Shouldn't error
  342.     guard.disconnectAll();
  343. }
  344.  
RAW Paste Data