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