add some explanatory comments to pbd/signals.h
[ardour.git] / libs / pbd / pbd / signals.h
1 /*
2     Copyright (C) 2009 Paul Davis 
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #ifndef __pbd_signals_h__
21 #define __pbd_signals_h__
22
23 #include <list>
24 #include <glibmm/thread.h>
25
26 #include <boost/signals2.hpp>
27 #include <boost/noncopyable.hpp>
28 #include <boost/bind.hpp>
29 #include <boost/bind/protect.hpp>
30
31 #include "pbd/event_loop.h"
32
33 namespace PBD {
34
35 typedef boost::signals2::connection UnscopedConnection;
36 typedef boost::signals2::scoped_connection ScopedConnection;
37
38 class ScopedConnectionList  : public boost::noncopyable
39 {
40   public:
41         ScopedConnectionList();
42         ~ScopedConnectionList ();
43         
44         void add_connection (const UnscopedConnection& c);
45         void drop_connections ();
46
47   private:
48         /* this class is not copyable */
49         ScopedConnectionList(const ScopedConnectionList&);
50
51         /* this lock is shared by all instances of a ScopedConnectionList.
52            We do not want one mutex per list, and since we only need the lock
53            when adding or dropping connections, which are generally occuring
54            in object creation and UI operations, the contention on this 
55            lock is low and not of significant consequence. Even though
56            boost::signals2 is thread-safe, this additional list of
57            scoped connections needs to be protected in 2 cases:
58
59            (1) (unlikely) we make a connection involving a callback on the
60                same object from 2 threads. (wouldn't that just be appalling 
61                programming style?)
62              
63            (2) where we are dropping connections in one thread and adding
64                one from another.
65          */
66
67         static Glib::StaticMutex _lock;
68
69         typedef std::list<ScopedConnection*> ConnectionList;
70         ConnectionList _list;
71 };
72
73 template<typename R>
74 class Signal0 {
75 public:
76     Signal0 () {}
77     typedef boost::signals2::signal<R()> SignalType;
78
79     /** Arrange for @a slot to be executed in the context of @a event_loop
80         whenever this signal is emitted. Store the connection that represents
81         this arrangement to @a c.
82
83         NOTE: @a slot will be executed in the same thread that the signal is
84         emitted in.
85     */
86
87     void connect_same_thread (ScopedConnection& c, 
88                   const typename SignalType::slot_function_type& slot) {
89             c = _signal.connect (slot);
90     }
91
92     /** Arrange for @a slot to be executed in the context of @a event_loop
93         whenever this signal is emitted. Add the connection that represents
94         this arrangement to @a clist.
95
96         NOTE: @a slot will be executed in the same thread that the signal is
97         emitted in.
98     */
99         
100     void connect_same_thread (ScopedConnectionList& clist, 
101                   const typename SignalType::slot_function_type& slot) {
102             clist.add_connection (_signal.connect (slot));
103     }
104
105     /** Arrange for @a slot to be executed in the context of @a event_loop
106         whenever this signal is emitted. Add the connection that represents
107         this arrangement to @a clist.
108         
109         If the event loop/thread in which @a slot will be executed will
110         outlive the lifetime of any object referenced in @a slot,
111         then an InvalidationRecord should be passed, allowing
112         any request sent to the @a event_loop and not executed
113         before the object is destroyed to be marked invalid.
114         
115         "outliving the lifetime" doesn't have a specific, detailed meaning,
116         but is best illustrated by two contrasting examples:
117         
118         1) the main GUI event loop/thread - this will outlive more or 
119         less all objects in the application, and thus when arranging for
120         @a slot to be called in that context, an invalidation record is 
121         highly advisable.
122         
123         2) a secondary event loop/thread which will be destroyed along
124         with the objects that are typically referenced by @a slot.
125         Assuming that the event loop is stopped before the objects are
126         destroyed, there is no reason to pass in an invalidation record,
127         and MISSING_INVALIDATOR may be used.
128     */
129
130     void connect (ScopedConnectionList& clist, 
131                   PBD::EventLoop::InvalidationRecord* ir, 
132                   const typename SignalType::slot_function_type& slot,
133                   PBD::EventLoop* event_loop) {
134             if (ir) {
135                     ir->event_loop = event_loop;
136             }
137             clist.add_connection (_signal.connect (boost::bind (&EventLoop::call_slot, event_loop, ir, slot)));
138     }
139
140     /** See notes for the ScopedConnectionList variant of this function. This
141      * differs in that it stores the connection to the signal in a single
142      * ScopedConnection rather than a ScopedConnectionList.
143      */
144
145     void connect (ScopedConnection& c, 
146                   PBD::EventLoop::InvalidationRecord* ir, 
147                   const typename SignalType::slot_function_type& slot,
148                   PBD::EventLoop* event_loop) {
149             if (ir) {
150                     ir->event_loop = event_loop;
151             }
152             c = _signal.connect (boost::bind (&EventLoop::call_slot, event_loop, ir, slot));
153     }
154
155     /** Emit this signal. This will cause all slots connected to it be executed
156         in the order that they were connected (cross-thread issues may alter
157         the precise execution time of cross-thread slots).
158     */
159     
160     typename SignalType::result_type operator()() {
161             return _signal ();
162     }
163
164     /** Return true if there is nothing connected to this signal, false
165      *  otherwise.
166      */
167
168     bool empty() const { return _signal.empty(); }
169     
170 private:
171     SignalType _signal;
172 };
173
174 template<typename R, typename A, typename C = boost::signals2::optional_last_value<R> >
175 class Signal1 {
176 public:
177     Signal1 () {}
178     typedef boost::signals2::signal<R(A), C> SignalType;
179
180     void connect_same_thread (ScopedConnectionList& clist, 
181                   const typename SignalType::slot_function_type& slot) {
182             clist.add_connection (_signal.connect (slot));
183     }
184
185     void connect_same_thread (ScopedConnection& c, 
186                      const typename SignalType::slot_function_type& slot) {
187             c = _signal.connect (slot);
188     }
189
190     static void compositor (typename boost::function<void(A)> f, EventLoop* event_loop, EventLoop::InvalidationRecord* ir, A arg) {
191             event_loop->call_slot (ir, boost::bind (f, arg));
192     }
193
194     void connect (ScopedConnectionList& clist, 
195                   PBD::EventLoop::InvalidationRecord* ir, 
196                   const typename SignalType::slot_function_type& slot,
197                   PBD::EventLoop* event_loop) {
198             if (ir) {
199                     ir->event_loop = event_loop;
200             }
201             clist.add_connection (_signal.connect (boost::bind (&compositor, slot, event_loop, ir, _1)));
202     }
203
204     void connect (ScopedConnection& c, 
205                   PBD::EventLoop::InvalidationRecord* ir, 
206                   const typename SignalType::slot_function_type& slot,
207                   PBD::EventLoop* event_loop) {
208             if (ir) {
209                     ir->event_loop = event_loop;
210             }
211             c = _signal.connect (boost::bind (&compositor, slot, event_loop, ir, _1));
212
213     }
214     
215     typename SignalType::result_type operator()(A arg1) {
216             return _signal (arg1);
217     }
218     
219     bool empty() const { return _signal.empty(); }
220
221 private:
222     SignalType _signal;
223 };
224
225 template<typename R, typename A1, typename A2>
226 class Signal2 {
227 public:
228     Signal2 () {}
229     typedef boost::signals2::signal<R(A1, A2)> SignalType;
230
231     void connect_same_thread (ScopedConnectionList& clist, 
232                   const typename SignalType::slot_function_type& slot) {
233             clist.add_connection (_signal.connect (slot));
234     }
235
236     void connect_same_thread (ScopedConnection& c, 
237                      const typename SignalType::slot_function_type& slot) {
238             c = _signal.connect (slot);
239     }
240
241     static void compositor (typename boost::function<void(A1,A2)> f, PBD::EventLoop* event_loop, 
242                             EventLoop::InvalidationRecord* ir,
243                             A1 arg1, A2 arg2) {
244             event_loop->call_slot (ir, boost::bind (f, arg1, arg2));
245     }
246
247     void connect (ScopedConnectionList& clist, 
248                   PBD::EventLoop::InvalidationRecord* ir, 
249                   const typename SignalType::slot_function_type& slot,
250                   PBD::EventLoop* event_loop) {
251             if (ir) {
252                     ir->event_loop = event_loop;
253             }
254             clist.add_connection (_signal.connect (boost::bind (&compositor, slot, event_loop, ir, _1, _2)));
255     }
256
257     void connect (ScopedConnection& c, 
258                   PBD::EventLoop::InvalidationRecord* ir, 
259                   const typename SignalType::slot_function_type& slot,
260                   PBD::EventLoop* event_loop) {
261             if (ir) {
262                     ir->event_loop = event_loop;
263             }
264             c = _signal.connect (boost::bind (&compositor, slot, event_loop, ir, _1, _2));
265     }
266
267     typename SignalType::result_type operator()(A1 arg1, A2 arg2) {
268             return _signal (arg1, arg2);
269     }
270     
271     bool empty() const { return _signal.empty(); }
272
273 private:
274     SignalType _signal;
275 };
276
277 template<typename R, typename A1, typename A2, typename A3>
278 class Signal3 {
279 public:
280     Signal3 () {}
281     typedef boost::signals2::signal<R(A1,A2,A3)> SignalType;
282
283     void connect_same_thread (ScopedConnectionList& clist, 
284                   const typename SignalType::slot_function_type& slot) {
285             clist.add_connection (_signal.connect (slot));
286     }
287
288     void connect_same_thread (ScopedConnection& c, 
289                               const typename SignalType::slot_function_type& slot) {
290             c = _signal.connect (slot);
291     }
292
293     static void compositor (typename boost::function<void(A1,A2,A3)> f, PBD::EventLoop* event_loop, 
294                             EventLoop::InvalidationRecord* ir, 
295                             A1 arg1, A2 arg2, A3 arg3) {
296             event_loop->call_slot (ir, boost::bind (f, arg1, arg2, arg3));
297     }
298
299     void connect (ScopedConnectionList& clist, 
300                   PBD::EventLoop::InvalidationRecord* ir, 
301                   const typename SignalType::slot_function_type& slot,
302                   PBD::EventLoop* event_loop) {
303             if (ir) {
304                     ir->event_loop = event_loop;
305             }
306             clist.add_connection (_signal.connect (boost::bind (&compositor, slot, event_loop, ir, _1, _2, _3)));
307     }
308     
309     void connect (ScopedConnection& c, 
310                   PBD::EventLoop::InvalidationRecord* ir, 
311                   const typename SignalType::slot_function_type& slot,
312                   PBD::EventLoop* event_loop) {
313             if (ir) {
314                     ir->event_loop = event_loop;
315             }
316             c = _signal.connect (_signal.connect (boost::bind (&compositor, slot, event_loop, ir, _1, _2, _3)));
317     }
318     
319     typename SignalType::result_type operator()(A1 arg1, A2 arg2, A3 arg3) {
320             return _signal (arg1, arg2, arg3);
321     }
322     
323     bool empty() const { return _signal.empty(); }
324
325 private:
326     SignalType _signal;
327 };
328
329 template<typename R, typename A1, typename A2, typename A3, typename A4>
330 class Signal4 {
331 public:
332     Signal4 () {}
333     typedef boost::signals2::signal<R(A1,A2,A3,A4)> SignalType;
334
335     void connect_same_thread (ScopedConnectionList& clist, 
336                   const typename SignalType::slot_function_type& slot) {
337             clist.add_connection (_signal.connect (slot));
338     }
339
340     void connect_same_thread (ScopedConnection& c, 
341                               const typename SignalType::slot_function_type& slot) {
342             c = _signal.connect (slot);
343     }
344
345     static void compositor (typename boost::function<void(A1,A2,A3)> f, PBD::EventLoop* event_loop, 
346                             EventLoop::InvalidationRecord* ir, 
347                             A1 arg1, A2 arg2, A3 arg3, A4 arg4) {
348             event_loop->call_slot (ir, boost::bind (f, arg1, arg2, arg3, arg4));
349     }
350
351     void connect (ScopedConnectionList& clist, 
352                   PBD::EventLoop::InvalidationRecord* ir, 
353                   const typename SignalType::slot_function_type& slot,
354                   PBD::EventLoop* event_loop) {
355             if (ir) {
356                     ir->event_loop = event_loop;
357             }
358             clist.add_connection (_signal.connect (boost::bind (&compositor, slot, event_loop, ir, _1, _2, _3, _4)));
359     }
360     
361     void connect (ScopedConnection& c, 
362                   PBD::EventLoop::InvalidationRecord* ir, 
363                   const typename SignalType::slot_function_type& slot,
364                   PBD::EventLoop* event_loop) {
365             if (ir) {
366                     ir->event_loop = event_loop;
367             }
368             c = _signal.connect (_signal.connect (boost::bind (&compositor, slot, event_loop, ir, _1, _2, _3, _4)));
369     }
370     
371     typename SignalType::result_type operator()(A1 arg1, A2 arg2, A3 arg3, A4 arg4) {
372             return _signal (arg1, arg2, arg3, arg4);
373     }
374     
375     bool empty() const { return _signal.empty(); }
376
377 private:
378     SignalType _signal;
379 };
380
381 template<typename R, typename A1, typename A2, typename A3, typename A4, typename A5>
382 class Signal5 {
383 public:
384     Signal5 () {}
385     typedef boost::signals2::signal<R(A1,A2,A3,A4,A5)> SignalType;
386
387     void connect_same_thread (ScopedConnectionList& clist, 
388                   const typename SignalType::slot_function_type& slot) {
389             clist.add_connection (_signal.connect (slot));
390     }
391
392     void connect_same_thread (ScopedConnection& c, 
393                               const typename SignalType::slot_function_type& slot) {
394             c = _signal.connect (slot);
395     }
396
397     static void compositor (typename boost::function<void(A1,A2,A3,A4,A5)> f, PBD::EventLoop* event_loop, 
398                             EventLoop::InvalidationRecord* ir, 
399                             A1 arg1, A2 arg2, A3 arg3, A4 arg4, A5 arg5) {
400             event_loop->call_slot (ir, boost::bind (f, arg1, arg2, arg3, arg4, arg5));
401     }
402
403     void connect (ScopedConnectionList& clist, 
404                   PBD::EventLoop::InvalidationRecord* ir, 
405                   const typename SignalType::slot_function_type& slot,
406                   PBD::EventLoop* event_loop) {
407             if (ir) {
408                     ir->event_loop = event_loop;
409             }
410             clist.add_connection (_signal.connect (boost::bind (&compositor, slot, event_loop, ir, _1, _2, _3, _4, _5)));
411     }
412     
413     void connect (ScopedConnection& c, 
414                   PBD::EventLoop::InvalidationRecord* ir, 
415                   const typename SignalType::slot_function_type& slot,
416                   PBD::EventLoop* event_loop) {
417             if (ir) {
418                     ir->event_loop = event_loop;
419             }
420             c = _signal.connect (_signal.connect (boost::bind (&compositor, slot, event_loop, ir, _1, _2, _3, _4, _5)));
421     }
422     
423     typename SignalType::result_type operator()(A1 arg1, A2 arg2, A3 arg3, A4 arg4, A5 arg5) {
424             return _signal (arg1, arg2, arg3, arg4, arg5);
425     }
426     
427     bool empty() const { return _signal.empty(); }
428
429 private:
430     SignalType _signal;
431 };
432         
433 } /* namespace */
434
435 #endif /* __pbd_signals_h__ */