1 // Written in the D programming language. 2 3 /** 4 5 This module contains definition of signals / listeners. 6 7 Similar to std.signals. 8 9 Unlike std.signals, supports any types of delegates, and as well interfaces with single method. 10 11 Unlike std.signals, can support return types for slots. 12 13 Caution: unlike std.signals, does not disconnect signal from slots belonging to destroyed objects. 14 15 Listener here stand for holder of single delegate (slot). 16 17 Signal is the same but supports multiple slots. 18 19 Listener has smaller memory footprint, but allows only single slot. 20 21 22 Can be declared either using list of result value and argument types, or by interface name with single method. 23 24 25 Synopsis: 26 27 ---- 28 import dlangui.core.signals; 29 30 interface SomeInterface { 31 bool someMethod(string s, int n); 32 } 33 class Foo : SomeInterface { 34 override bool someMethod(string s, int n) { 35 writeln("someMethod called ", s, ", ", n); 36 return n > 10; // can return value 37 } 38 } 39 40 // Listener! can hold arbitrary number of connected slots 41 42 // declare using list of return value and parameter types 43 Listener!(bool, string, n) signal1; 44 45 Foo f = new Foo(); 46 // when signal is defined as type list, you can use delegate 47 signal1 = bool delegate(string s, int n) { writeln("inside delegate - ", s, n); return false; } 48 // or method reference 49 signal1 = &f.someMethod; 50 51 // declare using interface with single method 52 Listener!SomeInterface signal2; 53 // you still can use any delegate 54 signal2 = bool delegate(string s, int n) { writeln("inside delegate - ", s, n); return false; } 55 // but for class method which overrides interface method, you can use simple syntax 56 signal2 = f; // it will automatically take &f.someMethod 57 58 59 // call listener(s) either by opcall or explicit emit 60 signal1("text", 1); 61 signal1.emit("text", 2); 62 signal2.emit("text", 3); 63 64 // check if any slit is connected 65 if (signal1.assigned) 66 writeln("has listeners"); 67 68 // Signal! can hold arbitrary number of connected slots 69 70 // declare using list of return value and parameter types 71 Signal!(bool, string, n) signal3; 72 73 // add listeners via connect call 74 signal3.connect(bool delegate(string, int) { return false; }); 75 // or via ~= operator 76 signal3 ~= bool delegate(string, int) { return false; }; 77 78 // declare using interface with single method 79 Signal!SomeInterface signal4; 80 81 // you can connect several slots to signal 82 signal4 ~= f; 83 signal4 ~= bool delegate(string, int) { return true; } 84 85 // calling of listeners of Signal! is similar to Listener! 86 // using opCall 87 bool res = signal4("blah", 5); 88 // call listeners using emit 89 bool res = signal4.emit("blah", 5); 90 91 // you can disconnect individual slots 92 // using disconnect() 93 signal4.disconnect(f); 94 // or -= operator 95 signal4 -= f; 96 97 ---- 98 99 Copyright: Vadim Lopatin, 2014 100 License: Boost License 1.0 101 Authors: Vadim Lopatin, coolreader.org@gmail.com 102 */ 103 module dlangui.core.signals; 104 105 import std.traits; 106 import dlangui.core.collections; 107 108 /// Single listener; parameter is interface with single method 109 struct Listener(T1) if (is(T1 == interface) && __traits(allMembers, T1).length == 1) { 110 alias return_t = ReturnType!(__traits(getMember, T1, __traits(allMembers, T1)[0])); 111 alias params_t = ParameterTypeTuple!(__traits(getMember, T1, __traits(allMembers, T1)[0])); 112 alias slot_t = return_t delegate(params_t); 113 private slot_t _listener; 114 /// returns true if listener is assigned 115 final bool assigned() { 116 return _listener !is null; 117 } 118 /// assign delegate 119 final void opAssign(slot_t listenerDelegate) { 120 _listener = listenerDelegate; 121 } 122 /// assign object implementing interface 123 final void opAssign(T1 listenerObject) { 124 _listener = &(__traits(getMember, listenerObject, __traits(allMembers, T1)[0])); 125 } 126 final return_t opCall(params_t params) { 127 static if (is(return_t == void)) { 128 if (_listener !is null) 129 _listener(params); 130 } else { 131 if (_listener !is null) 132 return _listener(params); 133 return return_t.init; 134 } 135 } 136 final slot_t get() { 137 return _listener; 138 } 139 alias get this; 140 } 141 142 /// Single listener; implicitly specified return and parameter types 143 struct Listener(RETURN_T, T1...) 144 { 145 alias slot_t = RETURN_T delegate(T1); 146 private slot_t _listener; 147 /// returns true if listener is assigned 148 final bool assigned() { 149 return _listener !is null; 150 } 151 final void opAssign(slot_t listener) { 152 _listener = listener; 153 } 154 final RETURN_T opCall(T1 params) { 155 static if (is (RETURN_T == void)) { 156 if (_listener !is null) 157 _listener(params); 158 } else { 159 if (_listener !is null) 160 return _listener(params); 161 return RETURN_T.init; 162 } 163 } 164 final slot_t get() { 165 return _listener; 166 } 167 /// disconnect all listeners 168 final void clear() { 169 _listener = null; 170 } 171 alias get this; 172 } 173 174 /// Multiple listeners; implicitly specified return and parameter types 175 struct Signal(T1) if (is(T1 == interface) && __traits(allMembers, T1).length == 1) { 176 alias return_t = ReturnType!(__traits(getMember, T1, __traits(allMembers, T1)[0])); 177 alias params_t = ParameterTypeTuple!(__traits(getMember, T1, __traits(allMembers, T1)[0])); 178 alias slot_t = return_t delegate(params_t); 179 private Collection!slot_t _listeners; 180 181 this(ref Signal!T1 v) { 182 _listeners.addAll(v._listeners); 183 } 184 185 /// returns true if listener is assigned 186 final bool assigned() { 187 return _listeners.length > 0; 188 } 189 /// replace all previously assigned listeners with new one (if null passed, remove all listeners) 190 final void opAssign(slot_t listener) { 191 _listeners.clear(); 192 if (listener !is null) 193 _listeners ~= listener; 194 } 195 /// replace all previously assigned listeners with new one (if null passed, remove all listeners) 196 final void opAssign(T1 listener) { 197 opAssign(&__traits(getMember, listener, __traits(allMembers, T1)[0])); 198 } 199 /// call all listeners; for signals having non-void return type, stop iterating when first return value is nonzero 200 static if (is (return_t == void)) { 201 // call all listeners 202 final return_t opCall(params_t params) { 203 foreach(listener; _listeners) 204 listener(params); 205 } 206 // call all listeners 207 final return_t emit(params_t params) { 208 foreach(listener; _listeners) 209 listener(params); 210 } 211 } else { 212 // call listeners, stop calling on first non-zero -- if (res) return res 213 final return_t opCall(params_t params) { 214 return emit(params); 215 } 216 // call listeners, stop calling on first non-zero -- if (res) return res 217 final return_t emit(params_t params) { 218 foreach(listener; _listeners) { 219 return_t res = listener(params); 220 if (res) 221 return res; 222 } 223 return return_t.init; 224 } 225 } 226 /// add listener 227 final void connect(slot_t listener) { 228 _listeners ~= listener; 229 } 230 /// remove listener 231 final void disconnect(slot_t listener) { 232 _listeners -= listener; 233 } 234 /// add listener - as interface member 235 final void connect(T1 listener) { 236 connect(&__traits(getMember, listener, __traits(allMembers, T1)[0])); 237 } 238 /// add listener - as interface member 239 final void disconnect(T1 listener) { 240 disconnect(&__traits(getMember, listener, __traits(allMembers, T1)[0])); 241 } 242 /// disconnect all listeners 243 final void clear() { 244 _listeners.clear(); 245 } 246 247 /// Provides `~=` syntax for connecting to this `Signal` 248 void opOpAssign(string op, T1)(T1 listener) 249 if(op == "~=") 250 { 251 connect(listener); 252 } 253 } 254 255 /// Multiple listeners; implicitly specified return and parameter types 256 struct Signal(RETURN_T, T1...) 257 { 258 alias slot_t = RETURN_T delegate(T1); 259 private Collection!slot_t _listeners; 260 /// returns true if listener is assigned 261 final bool assigned() { 262 return _listeners.length > 0; 263 } 264 /// replace all previously assigned listeners with new one (if null passed, remove all listeners) 265 final void opAssign(slot_t listener) { 266 _listeners.clear(); 267 if (listener !is null) 268 _listeners ~= listener; 269 } 270 /// call all listeners; for signals having non-void return type, stop iterating when first return value is nonzero 271 static if (is (RETURN_T == void)) { 272 // call all listeners 273 final RETURN_T opCall(T1 params) { 274 foreach(listener; _listeners) 275 listener(params); 276 } 277 // call all listeners 278 final RETURN_T emit(T1 params) { 279 foreach(listener; _listeners) 280 listener(params); 281 } 282 } else { 283 // call listeners, stop calling on first non-zero -- if (res) return res 284 final RETURN_T opCall(T1 params) { 285 return emit(params); 286 } 287 // call listeners, stop calling on first non-zero -- if (res) return res 288 final RETURN_T emit(T1 params) { 289 foreach(listener; _listeners) { 290 RETURN_T res = listener(params); 291 if (res) 292 return res; 293 } 294 return RETURN_T.init; 295 } 296 } 297 /// add listener 298 final void connect(slot_t listener) { 299 _listeners ~= listener; 300 } 301 /// remove listener 302 final void disconnect(slot_t listener) { 303 _listeners -= listener; 304 } 305 /// disconnect all listeners 306 final void clear() { 307 _listeners.clear(); 308 } 309 310 /// Provides `~=` syntax for connecting to this `Signal` 311 void opOpAssign(string op, T1)(T1 listener) 312 if(op == "~=") 313 { 314 connect(listener); 315 } 316 } 317 318 unittest 319 { 320 interface IHandler 321 { 322 void onHandler(); 323 } 324 325 class Component 326 { 327 Signal!IHandler signal; 328 329 void doStuff() 330 { 331 signal(); 332 } 333 } 334 335 class Handler : IHandler 336 { 337 this() 338 { 339 c = new Component(); 340 c.signal = this; 341 } 342 343 void onHandler() 344 { 345 res = 1; 346 } 347 348 int res = 0; 349 Component c; 350 } 351 352 scope Handler h = new Handler(); 353 h.c.doStuff(); 354 assert(h.c.signal.assigned); 355 assert(h.res == 1); 356 }