comparison tango/tango/core/Signal.d @ 132:1700239cab2e trunk

[svn r136] MAJOR UNSTABLE UPDATE!!! Initial commit after moving to Tango instead of Phobos. Lots of bugfixes... This build is not suitable for most things.
author lindquist
date Fri, 11 Jan 2008 17:57:40 +0100
parents
children
comparison
equal deleted inserted replaced
131:5825d48b27d1 132:1700239cab2e
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 }