1 #pragma once 2 3 #include "pajlada/signals/connection.hpp" 4 5 #include <atomic> 6 #include <functional> 7 #include <memory> 8 #include <mutex> 9 #include <vector> 10 11 namespace pajlada { 12 namespace Signals { 13 14 template <typename... Args> 15 class Signal 16 { 17 public: 18 using CallbackBodyType = detail::CallbackBody<Args...>; 19 20 Connection connect(typename CallbackBodyType::FunctionSignature func)21 connect(typename CallbackBodyType::FunctionSignature func) 22 { 23 uint64_t connectionIndex = this->nextConnection(); 24 25 auto callback = std::make_shared<CallbackBodyType>(connectionIndex); 26 callback->func = std::move(func); 27 28 std::weak_ptr<CallbackBodyType> weakCallback(callback); 29 30 this->registerBody(std::move(callback)); 31 32 return Connection(weakCallback); 33 } 34 35 void invoke(Args...args)36 invoke(Args... args) 37 { 38 auto activeBodies = this->getActiveBodies(); 39 40 for (const auto &cb : activeBodies) { 41 cb->func(std::forward<Args>(args)...); 42 } 43 } 44 45 void disconnectAll()46 disconnectAll() 47 { 48 std::unique_lock<std::mutex> lock(this->callbackBodiesMutex); 49 50 for (auto &&body : this->callbackBodies) { 51 body->disconnect(); 52 } 53 } 54 55 private: 56 std::atomic<uint64_t> latestConnection{0}; 57 58 std::mutex callbackBodiesMutex; 59 std::vector<std::shared_ptr<CallbackBodyType>> callbackBodies; 60 61 std::vector<std::shared_ptr<CallbackBodyType>> getActiveBodies()62 getActiveBodies() 63 { 64 std::vector<std::shared_ptr<CallbackBodyType>> activeBodies; 65 66 std::unique_lock<std::mutex> lock(this->callbackBodiesMutex); 67 68 for (auto it = this->callbackBodies.begin(); 69 it != this->callbackBodies.end();) { 70 auto &callback = *it; 71 72 if (!callback->isConnected()) { 73 // Clean up disconnected callbacks 74 it = this->callbackBodies.erase(it); 75 continue; 76 } 77 78 if (!callback->isBlocked()) { 79 activeBodies.emplace_back(callback); 80 } 81 82 ++it; 83 } 84 85 return activeBodies; 86 } 87 88 void registerBody(std::shared_ptr<CallbackBodyType> && body)89 registerBody(std::shared_ptr<CallbackBodyType> &&body) 90 { 91 std::unique_lock<std::mutex> lock(this->callbackBodiesMutex); 92 93 this->callbackBodies.emplace_back(std::move(body)); 94 } 95 96 uint64_t nextConnection()97 nextConnection() 98 { 99 return ++this->latestConnection; 100 } 101 }; 102 103 using NoArgSignal = Signal<>; 104 105 /// Bolt Signals (1-time use) 106 // connect is fast 107 // disconnect doesn't exist 108 // invoke is fast 109 template <class... Args> 110 class BoltSignal 111 { 112 protected: 113 typedef std::function<void(Args...)> CallbackType; 114 115 public: 116 void connect(CallbackType cb)117 connect(CallbackType cb) 118 { 119 this->callbacks.push_back(std::move(cb)); 120 } 121 122 void invoke(Args...args)123 invoke(Args... args) 124 { 125 for (auto &callback : this->callbacks) { 126 callback.func(std::forward<Args>(args)...); 127 } 128 129 this->callbacks.clear(); 130 } 131 132 protected: 133 std::vector<CallbackType> callbacks; 134 }; 135 136 using NoArgBoltSignal = BoltSignal<>; 137 138 139 /// Disconnects callback when callback is true 140 template <class... Args> 141 class SelfDisconnectingSignal 142 { 143 protected: 144 typedef std::function<bool(Args...)> CallbackType; 145 146 public: 147 void connect(CallbackType cb)148 connect(CallbackType cb) 149 { 150 this->callbacks.push_back(std::move(cb)); 151 } 152 153 void invoke(Args...args)154 invoke(Args... args) 155 { 156 callbacks.erase(std::remove_if(callbacks.begin(), callbacks.end(), [&](CallbackType callback) { 157 return callback(std::forward<Args>(args)...); 158 }), callbacks.end()); 159 } 160 161 protected: 162 std::vector<CallbackType> callbacks; 163 }; 164 165 using NoArgSelfDisconnectingSignal = SelfDisconnectingSignal<>; 166 167 } // namespace Signals 168 } // namespace pajlada 169