1 // Written in the D programming language. 2 3 /** 4 * Signals and Slots are an implementation of the Observer Pattern. 5 * Essentially, when a Signal is emitted, a list of connected Observers 6 * (called slots) are called. 7 * 8 * There have been several D implementations of Signals and Slots. 9 * This version makes use of several new features in D, which make 10 * using it simpler and less error prone. In particular, it is no 11 * longer necessary to instrument the slots. 12 * 13 * References: 14 * $(LUCKY A Deeper Look at Signals and Slots)$(BR) 15 * $(LINK2 http://en.wikipedia.org/wiki/Observer_pattern, Observer pattern)$(BR) 16 * $(LINK2 http://en.wikipedia.org/wiki/Signals_and_slots, Wikipedia)$(BR) 17 * $(LINK2 http://boost.org/doc/html/$(SIGNALS).html, Boost Signals)$(BR) 18 * $(LINK2 http://qt-project.org/doc/qt-5/signalsandslots.html, Qt)$(BR) 19 * 20 * There has been a great deal of discussion in the D newsgroups 21 * over this, and several implementations: 22 * 23 * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/announce/signal_slots_library_4825.html, signal slots library)$(BR) 24 * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Signals_and_Slots_in_D_42387.html, Signals and Slots in D)$(BR) 25 * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Dynamic_binding_--_Qt_s_Signals_and_Slots_vs_Objective-C_42260.html, Dynamic binding -- Qt's Signals and Slots vs Objective-C)$(BR) 26 * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Dissecting_the_SS_42377.html, Dissecting the SS)$(BR) 27 * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/dwt/about_harmonia_454.html, about harmonia)$(BR) 28 * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/announce/1502.html, Another event handling module)$(BR) 29 * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/41825.html, Suggestion: signal/slot mechanism)$(BR) 30 * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/13251.html, Signals and slots?)$(BR) 31 * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/10714.html, Signals and slots ready for evaluation)$(BR) 32 * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/1393.html, Signals & Slots for Walter)$(BR) 33 * $(LINK2 http://www.digitalmars.com/d/archives/28456.html, Signal/Slot mechanism?)$(BR) 34 * $(LINK2 http://www.digitalmars.com/d/archives/19470.html, Modern Features?)$(BR) 35 * $(LINK2 http://www.digitalmars.com/d/archives/16592.html, Delegates vs interfaces)$(BR) 36 * $(LINK2 http://www.digitalmars.com/d/archives/16583.html, The importance of component programming (properties$(COMMA) signals and slots$(COMMA) etc))$(BR) 37 * $(LINK2 http://www.digitalmars.com/d/archives/16368.html, signals and slots)$(BR) 38 * 39 * Bugs: 40 * Slots can only be delegates formed from class objects or 41 * interfaces to class objects. If a delegate to something else 42 * is passed to connect(), such as a struct member function, 43 * a nested function or a COM interface, undefined behavior 44 * will result. 45 * 46 * Not safe for multiple threads operating on the same signals 47 * or slots. 48 * Macros: 49 * SIGNALS=signals 50 * 51 * Copyright: Copyright Digital Mars 2000 - 2009. 52 * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 53 * Authors: $(HTTP digitalmars.com, Walter Bright) 54 * Source: $(PHOBOSSRC std/_signals.d) 55 */ 56 /* Copyright Digital Mars 2000 - 2009. 57 * Distributed under the Boost Software License, Version 1.0. 58 * (See accompanying file LICENSE_1_0.txt or copy at 59 * http://www.boost.org/LICENSE_1_0.txt) 60 */ 61 module puppeteer.signal; 62 63 // Special function for internal use only. 64 // Use of this is where the slot had better be a delegate 65 // to an object or an interface that is part of an object. 66 extern (C) Object _d_toObject(void* p); 67 68 // Used in place of Object.notifyRegister and Object.notifyUnRegister. 69 alias DisposeEvt = void delegate(Object) shared; 70 extern (C) void rt_attachDisposeEvent( Object obj, DisposeEvt evt ); 71 extern (C) void rt_detachDisposeEvent( Object obj, DisposeEvt evt ); 72 //debug=signal; 73 74 /************************ 75 * Mixin to create a signal within a class object. 76 * 77 * Different signals can be added to a class by naming the mixins. 78 */ 79 80 mixin template Signal(T1...) 81 { 82 import std.stdio; 83 import core.stdc.stdlib : calloc, realloc, free; 84 import core.exception : onOutOfMemoryError; 85 import core.atomic : atomicOp; 86 87 static import core.stdc.stdlib; 88 static import core.exception; 89 /*** 90 * A slot is implemented as a delegate. 91 * The slot_t is the type of the delegate. 92 * The delegate must be to an instance of a class or an interface 93 * to a class instance. 94 * Delegates to struct instances or nested functions must not be 95 * used as slots. 96 */ 97 alias slot_t = void delegate(T1) shared; 98 99 /*** 100 * Call each of the connected slots, passing the argument(s) i to them. 101 */ 102 final void emit( T1 i ) shared 103 { 104 foreach (slot; slots[0 .. slots_idx]) 105 { if (slot) 106 slot(i); 107 } 108 } 109 110 /*** 111 * Add a slot to the list of slots to be called when emit() is called. 112 */ 113 final void connect(slot_t slot) shared 114 { 115 /* Do this: 116 * slots ~= slot; 117 * but use malloc() and friends instead 118 */ 119 auto len = slots.length; 120 if (slots_idx == len) 121 { 122 if (slots.length == 0) 123 { 124 len = 4; 125 auto p = core.stdc.stdlib.calloc(slot_t.sizeof, len); 126 if (!p) 127 core.exception.onOutOfMemoryError(); 128 slots = cast(shared slot_t[])(cast(slot_t*)p)[0 .. len]; 129 } 130 else 131 { 132 len = len * 2 + 4; 133 auto p = core.stdc.stdlib.realloc(cast(void*) slots.ptr, slot_t.sizeof * len); 134 if (!p) 135 core.exception.onOutOfMemoryError(); 136 slots = cast(shared slot_t[])(cast(slot_t*)p)[0 .. len]; 137 slots[slots_idx + 1 .. $] = null; 138 } 139 } 140 slots[slots_idx] = slot; 141 slots_idx.atomicOp!"+="(1); 142 143 L1: 144 Object o = _d_toObject(slot.ptr); 145 rt_attachDisposeEvent(o, &unhook); 146 } 147 148 /*** 149 * Remove a slot from the list of slots to be called when emit() is called. 150 */ 151 final void disconnect(slot_t slot) shared 152 { 153 debug (signal) writefln("Signal.disconnect(slot)"); 154 for (size_t i = 0; i < slots_idx; ) 155 { 156 if (slots[i] == slot) 157 { 158 slots_idx.atomicOp!"-="(1); 159 slots[i] = slots[slots_idx]; 160 slots[slots_idx] = null; // not strictly necessary 161 162 Object o = _d_toObject(slot.ptr); 163 rt_detachDisposeEvent(o, &unhook); 164 } 165 else 166 i++; 167 } 168 } 169 170 /* ** 171 * Special function called when o is destroyed. 172 * It causes any slots dependent on o to be removed from the list 173 * of slots to be called by emit(). 174 */ 175 final private void unhook(Object o) shared 176 { 177 debug (signal) writefln("Signal.unhook(o = %s)", cast(void*)o); 178 for (size_t i = 0; i < slots_idx; ) 179 { 180 if (_d_toObject(slots[i].ptr) is o) 181 { 182 slots_idx.atomicOp!"-="(1); 183 slots[i] = slots[slots_idx]; 184 slots[slots_idx] = null; // not strictly necessary 185 } 186 else 187 i++; 188 } 189 } 190 191 /* ** 192 * There can be multiple destructors inserted by mixins. 193 */ 194 ~this() shared 195 { 196 /* ** 197 * When this object is destroyed, need to let every slot 198 * know that this object is destroyed so they are not left 199 * with dangling references to it. 200 */ 201 if (slots.length) 202 { 203 foreach (slot; slots[0 .. slots_idx]) 204 { 205 if (slot) 206 { Object o = _d_toObject(slot.ptr); 207 rt_detachDisposeEvent(o, &unhook); 208 } 209 } 210 core.stdc.stdlib.free(cast(void*) slots.ptr); 211 slots = null; 212 } 213 } 214 215 private: 216 shared slot_t[] slots; // the slots to call from emit() 217 shared size_t slots_idx; // used length of slots[] 218 } 219 220 /// 221 unittest 222 { 223 int observedMessageCounter = 0; 224 225 shared class Observer 226 { // our slot 227 void watch(string msg, int value) 228 { 229 switch (observedMessageCounter++) 230 { 231 case 0: 232 assert(msg == "setting new value"); 233 assert(value == 4); 234 break; 235 case 1: 236 assert(msg == "setting new value"); 237 assert(value == 6); 238 break; 239 default: 240 assert(0, "Unknown observation"); 241 } 242 } 243 } 244 245 shared class Foo 246 { 247 int value() { return _value; } 248 249 int value(int v) 250 { 251 if (v != _value) 252 { _value = v; 253 // call all the connected slots with the two parameters 254 emit("setting new value", v); 255 } 256 return v; 257 } 258 259 // Mix in all the code we need to make Foo into a signal 260 mixin Signal!(string, int); 261 262 private : 263 int _value; 264 } 265 266 shared Foo a = new shared Foo; 267 shared Observer o = new shared Observer; 268 269 a.value = 3; // should not call o.watch() 270 a.connect(&o.watch); // o.watch is the slot 271 a.value = 4; // should call o.watch() 272 a.disconnect(&o.watch); // o.watch is no longer a slot 273 a.value = 5; // so should not call o.watch() 274 a.connect(&o.watch); // connect again 275 a.value = 6; // should call o.watch() 276 destroy(o); // destroying o should automatically disconnect it 277 a.value = 7; // should not call o.watch() 278 279 assert(observedMessageCounter == 2); 280 } 281 282 // A function whose sole purpose is to get this module linked in 283 // so the unittest will run. 284 void linkin() { } 285 286 unittest 287 { 288 shared class Observer 289 { 290 void watch(string msg, int i) 291 { 292 //writefln("Observed msg '%s' and value %s", msg, i); 293 captured_value = i; 294 captured_msg = msg; 295 } 296 297 int captured_value; 298 string captured_msg; 299 } 300 301 shared class Foo 302 { 303 @property int value() { return _value; } 304 305 @property int value(int v) 306 { 307 if (v != _value) 308 { _value = v; 309 emit("setting new value", v); 310 } 311 return v; 312 } 313 314 mixin Signal!(string, int); 315 316 private: 317 int _value; 318 } 319 320 shared Foo a = new shared Foo; 321 shared Observer o = new shared Observer; 322 323 // check initial condition 324 assert(o.captured_value == 0); 325 assert(o.captured_msg == ""); 326 327 // set a value while no observation is in place 328 a.value = 3; 329 assert(o.captured_value == 0); 330 assert(o.captured_msg == ""); 331 332 // connect the watcher and trigger it 333 a.connect(&o.watch); 334 a.value = 4; 335 assert(o.captured_value == 4); 336 assert(o.captured_msg == "setting new value"); 337 338 // disconnect the watcher and make sure it doesn't trigger 339 a.disconnect(&o.watch); 340 a.value = 5; 341 assert(o.captured_value == 4); 342 assert(o.captured_msg == "setting new value"); 343 344 // reconnect the watcher and make sure it triggers 345 a.connect(&o.watch); 346 a.value = 6; 347 assert(o.captured_value == 6); 348 assert(o.captured_msg == "setting new value"); 349 350 // destroy the underlying object and make sure it doesn't cause 351 // a crash or other problems 352 destroy(o); 353 a.value = 7; 354 } 355 356 unittest 357 { 358 shared class Observer 359 { 360 int i; 361 long l; 362 string str; 363 364 void watchInt(string str, int i) 365 { 366 this.str = str; 367 this.i = i; 368 } 369 370 void watchLong(string str, long l) 371 { 372 this.str = str; 373 this.l = l; 374 } 375 } 376 377 shared class Bar 378 { 379 @property void value1(int v) { s1.emit("str1", v); } 380 @property void value2(int v) { s2.emit("str2", v); } 381 @property void value3(long v) { s3.emit("str3", v); } 382 383 mixin Signal!(string, int) s1; 384 mixin Signal!(string, int) s2; 385 mixin Signal!(string, long) s3; 386 } 387 388 void test(T)(T a) { 389 auto o1 = new shared Observer; 390 auto o2 = new shared Observer; 391 auto o3 = new shared Observer; 392 393 // connect the watcher and trigger it 394 a.s1.connect(&o1.watchInt); 395 a.s2.connect(&o2.watchInt); 396 a.s3.connect(&o3.watchLong); 397 398 assert(!o1.i && !o1.l && o1.str == null); 399 assert(!o2.i && !o2.l && o2.str == null); 400 assert(!o3.i && !o3.l && o3.str == null); 401 402 a.value1 = 11; 403 assert(o1.i == 11 && !o1.l && o1.str == "str1"); 404 assert(!o2.i && !o2.l && o2.str == null); 405 assert(!o3.i && !o3.l && o3.str == null); 406 o1.i = -11; o1.str = "x1"; 407 408 a.value2 = 12; 409 assert(o1.i == -11 && !o1.l && o1.str == "x1"); 410 assert(o2.i == 12 && !o2.l && o2.str == "str2"); 411 assert(!o3.i && !o3.l && o3.str == null); 412 o2.i = -12; o2.str = "x2"; 413 414 a.value3 = 13; 415 assert(o1.i == -11 && !o1.l && o1.str == "x1"); 416 assert(o2.i == -12 && !o1.l && o2.str == "x2"); 417 assert(!o3.i && o3.l == 13 && o3.str == "str3"); 418 o3.l = -13; o3.str = "x3"; 419 420 // disconnect the watchers and make sure it doesn't trigger 421 a.s1.disconnect(&o1.watchInt); 422 a.s2.disconnect(&o2.watchInt); 423 a.s3.disconnect(&o3.watchLong); 424 425 a.value1 = 21; 426 a.value2 = 22; 427 a.value3 = 23; 428 assert(o1.i == -11 && !o1.l && o1.str == "x1"); 429 assert(o2.i == -12 && !o1.l && o2.str == "x2"); 430 assert(!o3.i && o3.l == -13 && o3.str == "x3"); 431 432 // reconnect the watcher and make sure it triggers 433 a.s1.connect(&o1.watchInt); 434 a.s2.connect(&o2.watchInt); 435 a.s3.connect(&o3.watchLong); 436 437 a.value1 = 31; 438 a.value2 = 32; 439 a.value3 = 33; 440 assert(o1.i == 31 && !o1.l && o1.str == "str1"); 441 assert(o2.i == 32 && !o1.l && o2.str == "str2"); 442 assert(!o3.i && o3.l == 33 && o3.str == "str3"); 443 444 // destroy observers 445 destroy(o1); 446 destroy(o2); 447 destroy(o3); 448 a.value1 = 41; 449 a.value2 = 42; 450 a.value3 = 43; 451 } 452 453 test(new shared Bar); 454 455 shared class BarDerived: Bar 456 { 457 @property void value4(int v) { s4.emit("str4", v); } 458 @property void value5(int v) { s5.emit("str5", v); } 459 @property void value6(long v) { s6.emit("str6", v); } 460 461 mixin Signal!(string, int) s4; 462 mixin Signal!(string, int) s5; 463 mixin Signal!(string, long) s6; 464 } 465 466 auto a = new shared BarDerived; 467 468 test!(shared Bar)(a); 469 test!(shared BarDerived)(a); 470 471 auto o4 = new shared Observer; 472 auto o5 = new shared Observer; 473 auto o6 = new shared Observer; 474 475 // connect the watcher and trigger it 476 a.s4.connect(&o4.watchInt); 477 a.s5.connect(&o5.watchInt); 478 a.s6.connect(&o6.watchLong); 479 480 assert(!o4.i && !o4.l && o4.str == null); 481 assert(!o5.i && !o5.l && o5.str == null); 482 assert(!o6.i && !o6.l && o6.str == null); 483 484 a.value4 = 44; 485 assert(o4.i == 44 && !o4.l && o4.str == "str4"); 486 assert(!o5.i && !o5.l && o5.str == null); 487 assert(!o6.i && !o6.l && o6.str == null); 488 o4.i = -44; o4.str = "x4"; 489 490 a.value5 = 45; 491 assert(o4.i == -44 && !o4.l && o4.str == "x4"); 492 assert(o5.i == 45 && !o5.l && o5.str == "str5"); 493 assert(!o6.i && !o6.l && o6.str == null); 494 o5.i = -45; o5.str = "x5"; 495 496 a.value6 = 46; 497 assert(o4.i == -44 && !o4.l && o4.str == "x4"); 498 assert(o5.i == -45 && !o4.l && o5.str == "x5"); 499 assert(!o6.i && o6.l == 46 && o6.str == "str6"); 500 o6.l = -46; o6.str = "x6"; 501 502 // disconnect the watchers and make sure it doesn't trigger 503 a.s4.disconnect(&o4.watchInt); 504 a.s5.disconnect(&o5.watchInt); 505 a.s6.disconnect(&o6.watchLong); 506 507 a.value4 = 54; 508 a.value5 = 55; 509 a.value6 = 56; 510 assert(o4.i == -44 && !o4.l && o4.str == "x4"); 511 assert(o5.i == -45 && !o4.l && o5.str == "x5"); 512 assert(!o6.i && o6.l == -46 && o6.str == "x6"); 513 514 // reconnect the watcher and make sure it triggers 515 a.s4.connect(&o4.watchInt); 516 a.s5.connect(&o5.watchInt); 517 a.s6.connect(&o6.watchLong); 518 519 a.value4 = 64; 520 a.value5 = 65; 521 a.value6 = 66; 522 assert(o4.i == 64 && !o4.l && o4.str == "str4"); 523 assert(o5.i == 65 && !o4.l && o5.str == "str5"); 524 assert(!o6.i && o6.l == 66 && o6.str == "str6"); 525 526 // destroy observers 527 destroy(o4); 528 destroy(o5); 529 destroy(o6); 530 a.value4 = 44; 531 a.value5 = 45; 532 a.value6 = 46; 533 } 534 535 version(none) // Disabled because of dmd @@@BUG5028@@@ 536 unittest 537 { 538 class A 539 { 540 mixin Signal!(string, int) s1; 541 } 542 543 class B : A 544 { 545 mixin Signal!(string, int) s2; 546 } 547 }