1 /**
2  * The signal module provides a basic implementation of the listener pattern
3  * using the "Signals and Slots" model from Qt.
4  *
5  * Copyright: Copyright (C) 2005-2006 Sean Kelly.  All rights reserved.
6  * License:   BSD style: $(LICENSE)
7  * Author:    Sean Kelly
8  */
9 module tango.core.Signal;
10 
11 
12 private import tango.core.Array;
13 
14 
15 /**
16  * A signal is an event which contains a collection of listeners (called
17  * slots).  When a signal is called, that call will be propagated to each
18  * attached slot in a synchronous manner.  It is legal for a slot to call a
19  * signal's attach and detach methods when it is signaled.  When this occurs,
20  * attach events will be queued and processed after the signal has propagated
21  * to all slots, but detach events are processed immediately.  This ensures
22  * that it is safe for slots to be deleted at any time, even within a slot
23  * routine.
24  *
25  * Example:
26  * -----------------------------------------------------------------------------
27  * class Button
28  * {
29  *     Signal!(Button) press;
30  * }
31  *
32  * void wasPressed( Button b )
33  * {
34  *     printf( "Button was pressed.\n" );
35  * }
36  *
37  * Button b = new Button;
38  *
39  * b.press.attach( &wasPressed );
40  * b.press( b );
41  * -----------------------------------------------------------------------------
42  *
43  * Please note that this implementation does not use weak pointers to store
44  * references to slots.  This design was chosen because weak pointers are
45  * inherently unsafe when combined with non-deterministic destruction, with
46  * many of the same limitations as destructors in the same situation.  It is
47  * still possible to obtain weak-pointer behavior, but this must be done
48  * through a proxy object instead.
49  */
50 struct Signal( Args... )
51 {
52     alias void delegate(Args) SlotDg; ///
53     alias void function(Args) SlotFn; ///
54 
55     alias opCall call; /// Alias to simplify chained calling.
56 
57 
58     /**
59      * The signal procedure.  When called, each of the attached slots will be
60      * called synchronously.
61      * Params:
62      * args = The signal arguments.
63      */
64     void opCall( Args args )
65     {
66         synchronized
67         {
68             m_blk = true;
69 
70             for( size_t i = 0; i < m_dgs.length; ++i )
71             {
72                 if( m_dgs[i] !is null )
73                     m_dgs[i]( args );
74             }
75             m_dgs.length = m_dgs.remove( cast(SlotDg) null );
76 
77             for( size_t i = 0; i < m_fns.length; ++i )
78             {
79                 if( m_fns[i] !is null )
80                     m_fns[i]( args );
81             }
82             m_fns.length = m_fns.remove( cast(SlotFn) null );
83 
84             m_blk = false;
85 
86             procAdds();
87         }
88     }
89 
90 
91     /**
92      * Attaches a delegate to this signal.  A delegate may be either attached
93      * or detached, so successive calls to attach for the same delegate will
94      * have no effect.
95      * Params:
96      * dg = The delegate to attach.
97      */
98     void attach( SlotDg dg )
99     {
100         synchronized
101         {
102             if( m_blk )
103             {
104                 m_add ~= Add( dg );
105             }
106             else
107             {
108                 auto pos = m_dgs.find( dg );
109                 if( pos == m_dgs.length )
110                     m_dgs ~= dg;
111             }
112         }
113     }
114 
115 
116     /**
117      * Attaches a function to this signal.  A function may be either attached
118      * or detached, so successive calls to attach for the same function will
119      * have no effect.
120      * Params:
121      * fn = The function to attach.
122      */
123     void attach( SlotFn fn )
124     {
125         synchronized
126         {
127             if( m_blk )
128             {
129                 m_add ~= Add( fn );
130             }
131             else
132             {
133                 auto pos = m_fns.find( fn );
134                 if( pos == m_fns.length )
135                     m_fns ~= fn;
136             }
137         }
138     }
139 
140 
141     /**
142      * Detaches a delegate from this signal.
143      * Params:
144      * dg = The delegate to detach.
145      */
146     void detach( SlotDg dg )
147     {
148         synchronized
149         {
150             auto pos = m_dgs.find( dg );
151             if( pos < m_dgs.length )
152                 m_dgs[pos] = null;
153         }
154     }
155 
156 
157     /**
158      * Detaches a function from this signal.
159      * Params:
160      * fn = The function to detach.
161      */
162     void detach( SlotFn fn )
163     {
164         synchronized
165         {
166             auto pos = m_fns.find( fn );
167             if( pos < m_fns.length )
168                 m_fns[pos] = null;
169         }
170     }
171 
172 
173 private:
174     struct Add
175     {
176         enum Type
177         {
178             DG,
179             FN
180         }
181 
182         static Add opCall( SlotDg d )
183         {
184             Add e;
185             e.ty = Type.DG;
186             e.dg = d;
187             return e;
188         }
189 
190         static Add opCall( SlotFn f )
191         {
192             Add e;
193             e.ty = Type.FN;
194             e.fn = f;
195             return e;
196         }
197 
198         union
199         {
200             SlotDg  dg;
201             SlotFn  fn;
202         }
203         Type        ty;
204     }
205 
206 
207     void procAdds()
208     {
209         foreach( a; m_add )
210         {
211             if( a.ty == Add.Type.DG )
212                 m_dgs ~= a.dg;
213             else
214                 m_fns ~= a.fn;
215         }
216         m_add.length = 0;
217     }
218 
219 
220     SlotDg[]    m_dgs;
221     SlotFn[]    m_fns;
222     Add[]       m_add;
223     bool        m_blk;
224 }
225 
226 
227 debug( UnitTest )
228 {
229   unittest
230   {
231     class Button
232     {
233         Signal!(Button) press;
234     }
235 
236     int count = 0;
237 
238     void wasPressedA( Button b )
239     {
240         ++count;
241     }
242 
243     void wasPressedB( Button b )
244     {
245         ++count;
246     }
247 
248     Button b = new Button;
249 
250     b.press.attach( &wasPressedA );
251     b.press( b );
252     assert( count == 1 );
253 
254     count = 0;
255     b.press.attach( &wasPressedB );
256     b.press( b );
257     assert( count == 2 );
258 
259     count = 0;
260     b.press.attach( &wasPressedA );
261     b.press( b );
262     assert( count == 2 );
263 
264     count = 0;
265     b.press.detach( &wasPressedB );
266     b.press( b );
267     assert( count == 1 );
268 
269     count = 0;
270     b.press.detach( &wasPressedA );
271     b.press( b );
272     assert( count == 0 );
273   }
274 }