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 }