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