1 // Licensed GNU LGPL v3 or later: http://www.gnu.org/licenses/lgpl.html
2 
3 #ifndef SPECTMORPH_SIGNAL_HH
4 #define SPECTMORPH_SIGNAL_HH
5 
6 #include "smutils.hh"
7 #include <assert.h>
8 #include <functional>
9 #include <vector>
10 #include <list>
11 
12 namespace SpectMorph
13 {
14 
15 template<class... Args>
16 class Signal;
17 
18 struct SignalBase
19 {
20   static uint64
next_signal_idSpectMorph::SignalBase21   next_signal_id()
22   {
23     static uint64 next_id = 1;
24 
25     return next_id++;
26   }
27   virtual void disconnect_impl (uint64 id) = 0;
28   virtual
~SignalBaseSpectMorph::SignalBase29   ~SignalBase()
30   {
31   }
32 };
33 
34 class SignalReceiver
35 {
36   struct SignalSource
37   {
38     SignalBase *signal;
39     uint64      id;
40   };
41   struct SignalReceiverData
42   {
43     int ref_count = 1;
44 
45     SignalReceiverData *
refSpectMorph::SignalReceiver::SignalReceiverData46     ref()
47     {
48       assert (ref_count > 0);
49       ref_count++;
50       return this;
51     }
52     void
unrefSpectMorph::SignalReceiver::SignalReceiverData53     unref (bool cleanup)
54     {
55       assert (ref_count > 0);
56       ref_count--;
57 
58       if (cleanup && ref_count == 1) /* ensure nobody is iterating over the data */
59         {
60           sources.remove_if ([](SignalSource& signal_source) -> bool
61             {
62               return signal_source.id == 0;
63             });
64         }
65       else if (ref_count == 0)
66         delete this;
67     }
68     std::list<SignalSource> sources;
69   };
70   struct SignalReceiverData *signal_receiver_data;
71 
72 public:
73   template<class... Args, class CbFunction>
74   uint64
connect(Signal<Args...> & signal,const CbFunction & callback)75   connect (Signal<Args...>& signal, const CbFunction& callback)
76   {
77     assert (signal_receiver_data);
78 
79     SignalReceiverData *data = signal_receiver_data->ref();
80 
81     auto id = signal.connect_impl (this, callback);
82     data->sources.push_back ({ &signal, id });
83     data->unref (true);
84 
85     return id;
86   }
87   template<class... Args, class Instance, class Method>
88   uint64
connect(Signal<Args...> & signal,Instance * instance,const Method & method)89   connect (Signal<Args...>& signal, Instance *instance, const Method& method)
90   {
91     return SignalReceiver::connect (signal, [instance, method](Args&&... args)
92       {
93         (instance->*method) (std::forward<Args>(args)...);
94       });
95   }
96   void
disconnect(uint64 id)97   disconnect (uint64 id)
98   {
99     assert (signal_receiver_data);
100 
101     SignalReceiverData *data = signal_receiver_data->ref();
102 
103     for (auto& signal_source : data->sources)
104       {
105         if (signal_source.id == id)
106           {
107             signal_source.signal->disconnect_impl (id);
108             signal_source.id = 0;
109           }
110       }
111     data->unref (true);
112   }
SignalReceiver()113   SignalReceiver() :
114     signal_receiver_data (new SignalReceiverData())
115   {
116   }
117   virtual
~SignalReceiver()118   ~SignalReceiver()
119   {
120     assert (signal_receiver_data);
121 
122     for (auto& signal_source : signal_receiver_data->sources)
123       {
124         if (signal_source.id)
125           {
126             signal_source.signal->disconnect_impl (signal_source.id);
127             signal_source.id = 0;
128           }
129       }
130     signal_receiver_data->unref (false);
131     signal_receiver_data = nullptr;
132   }
133   void
dead_signal(uint64 id)134   dead_signal (uint64 id)
135   {
136     SignalReceiverData *data = signal_receiver_data->ref();
137 
138     for (auto& signal_source : data->sources)
139       {
140         if (signal_source.id == id)
141           signal_source.id = 0;
142       }
143 
144     data->unref (true);
145   }
146 };
147 
148 template<class... Args>
149 class Signal : public SignalBase
150 {
151   typedef std::function<void (Args...)> CbFunction;
152 
153   struct Connection
154   {
155     CbFunction      func;
156     uint64          id;
157     SignalReceiver *receiver;
158   };
159   struct Data
160   {
161     int ref_count = 1;
162 
163     Data *
refSpectMorph::Signal::Data164     ref()
165     {
166       assert (ref_count > 0);
167       ref_count++;
168 
169       return this;
170     }
171     void
unrefSpectMorph::Signal::Data172     unref (bool cleanup)
173     {
174       assert (ref_count > 0);
175       ref_count--;
176 
177       if (cleanup && ref_count == 1) /* ensure nobody is iterating over the data */
178         {
179           connections.remove_if ([](Connection& conn) -> bool
180             {
181               return conn.id == 0;
182             });
183         }
184       else if (ref_count == 0)
185         delete this;
186     }
187 
188     std::list<Connection> connections;
189   };
190   Data *signal_data;
191 public:
192   uint64
connect_impl(SignalReceiver * receiver,const CbFunction & callback)193   connect_impl (SignalReceiver *receiver, const CbFunction& callback)
194   {
195     assert (signal_data);
196 
197     Data *data = signal_data->ref();
198     uint64 id = next_signal_id();
199     data->connections.push_back ({callback, id, receiver});
200     data->unref (true);
201 
202     return id;
203   }
204   void
disconnect_impl(uint64 id)205   disconnect_impl (uint64 id) override
206   {
207     assert (signal_data);
208 
209     Data *data = signal_data->ref();
210     for (auto& conn : data->connections)
211       {
212         if (conn.id == id)
213           conn.id = 0;
214       }
215     data->unref (true);
216   }
217   void
operator ()(Args...args)218   operator()(Args... args)
219   {
220     assert (signal_data);
221 
222     Data *data = signal_data->ref();
223 
224     for (auto& conn : data->connections)
225       {
226         if (conn.id)
227           conn.func (args...);
228       }
229 
230     data->unref (true);
231   }
Signal()232   Signal() :
233     signal_data (new Data())
234   {
235   }
~Signal()236   ~Signal()
237   {
238     assert (signal_data);
239 
240     for (auto& conn : signal_data->connections)
241       {
242         if (conn.id)
243           {
244             conn.receiver->dead_signal (conn.id);
245             conn.id = 0;
246           }
247       }
248 
249     signal_data->unref (false);
250     signal_data = nullptr;
251   }
252 };
253 
254 }
255 
256 #endif
257