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 * $(RED Slots can only be delegates referring directly to 41 * class or interface member functions. If a delegate to something else 42 * is passed to connect(), such as a struct member function, 43 * a nested function, a COM interface, a closure, 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 The D Language Foundation 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 * $(SCRIPT inhibitQuickIndex = 1;) 57 */ 58 /* Copyright The D Language Foundation 2000 - 2009. 59 * Distributed under the Boost Software License, Version 1.0. 60 * (See accompanying file LICENSE_1_0.txt or copy at 61 * http://www.boost.org/LICENSE_1_0.txt) 62 */ 63 module std.signals; 64 65 import core.exception : onOutOfMemoryError; 66 import core.stdc.stdlib : calloc, realloc, free; 67 import std.stdio; 68 69 // Special function for internal use only. 70 // Use of this is where the slot had better be a delegate 71 // to an object or an interface that is part of an object. 72 extern (C) Object _d_toObject(void* p); 73 74 // Used in place of Object.notifyRegister and Object.notifyUnRegister. 75 alias DisposeEvt = void delegate(Object); 76 extern (C) void rt_attachDisposeEvent( Object obj, DisposeEvt evt ); 77 extern (C) void rt_detachDisposeEvent( Object obj, DisposeEvt evt ); 78 //debug=signal; 79 80 /************************ 81 * Mixin to create a signal within a class object. 82 * 83 * Different signals can be added to a class by naming the mixins. 84 */ 85 86 mixin template Signal(T1...) 87 { 88 static import core.exception; 89 static import core.stdc.stdlib; 90 /*** 91 * A slot is implemented as a delegate. 92 * The slot_t is the type of the delegate. 93 * The delegate must be to an instance of a class or an interface 94 * to a class instance. 95 * Delegates to struct instances or nested functions must not be 96 * used as slots. This applies even if the nested function does not access 97 * it's parent function variables. 98 */ 99 alias slot_t = void delegate(T1); 100 101 /*** 102 * Call each of the connected slots, passing the argument(s) i to them. 103 * Nested call will be ignored. 104 */ 105 final void emit( T1 i ) 106 { 107 if (status >= ST.inemitting || !slots.length) 108 return; // should not nest 109 110 status = ST.inemitting; 111 scope (exit) 112 status = ST.idle; 113 114 foreach (slot; slots[0 .. slots_idx]) 115 { if (slot) 116 slot(i); 117 } 118 119 assert(status >= ST.inemitting); 120 if (status == ST.inemitting_disconnected) 121 { 122 for (size_t j = 0; j < slots_idx;) 123 { 124 if (slots[j] is null) 125 { 126 slots_idx--; 127 slots[j] = slots[slots_idx]; 128 } 129 else 130 j++; 131 } 132 } 133 } 134 135 /*** 136 * Add a slot to the list of slots to be called when emit() is called. 137 */ 138 final void connect(slot_t slot) 139 { 140 /* Do this: 141 * slots ~= slot; 142 * but use malloc() and friends instead 143 */ 144 auto len = slots.length; 145 if (slots_idx == len) 146 { 147 if (slots.length == 0) 148 { 149 len = 4; 150 auto p = core.stdc.stdlib.calloc(slot_t.sizeof, len); 151 if (!p) 152 core.exception.onOutOfMemoryError(); 153 slots = (cast(slot_t*) p)[0 .. len]; 154 } 155 else 156 { 157 import core.checkedint : addu, mulu; 158 bool overflow; 159 len = addu(mulu(len, 2, overflow), 4, overflow); // len = len * 2 + 4 160 const nbytes = mulu(len, slot_t.sizeof, overflow); 161 if (overflow) assert(0); 162 163 auto p = core.stdc.stdlib.realloc(slots.ptr, nbytes); 164 if (!p) 165 core.exception.onOutOfMemoryError(); 166 slots = (cast(slot_t*) p)[0 .. len]; 167 slots[slots_idx + 1 .. $] = null; 168 } 169 } 170 slots[slots_idx++] = slot; 171 172 L1: 173 Object o = _d_toObject(slot.ptr); 174 rt_attachDisposeEvent(o, &unhook); 175 } 176 177 /*** 178 * Remove a slot from the list of slots to be called when emit() is called. 179 */ 180 final void disconnect(slot_t slot) 181 { 182 debug (signal) writefln("Signal.disconnect(slot)"); 183 size_t disconnectedSlots = 0; 184 size_t instancePreviousSlots = 0; 185 if (status >= ST.inemitting) 186 { 187 foreach (i, sloti; slots[0 .. slots_idx]) 188 { 189 if (sloti.ptr == slot.ptr && 190 ++instancePreviousSlots && 191 sloti == slot) 192 { 193 disconnectedSlots++; 194 slots[i] = null; 195 status = ST.inemitting_disconnected; 196 } 197 } 198 } 199 else 200 { 201 for (size_t i = 0; i < slots_idx; ) 202 { 203 if (slots[i].ptr == slot.ptr && 204 ++instancePreviousSlots && 205 slots[i] == slot) 206 { 207 slots_idx--; 208 disconnectedSlots++; 209 slots[i] = slots[slots_idx]; 210 slots[slots_idx] = null; // not strictly necessary 211 } 212 else 213 i++; 214 } 215 } 216 217 // detach object from dispose event if all its slots have been removed 218 if (instancePreviousSlots == disconnectedSlots) 219 { 220 Object o = _d_toObject(slot.ptr); 221 rt_detachDisposeEvent(o, &unhook); 222 } 223 } 224 225 /*** 226 * Disconnect all the slots. 227 */ 228 final void disconnectAll() 229 { 230 debug (signal) writefln("Signal.disconnectAll"); 231 __dtor(); 232 slots_idx = 0; 233 status = ST.idle; 234 } 235 236 /* ** 237 * Special function called when o is destroyed. 238 * It causes any slots dependent on o to be removed from the list 239 * of slots to be called by emit(). 240 */ 241 final void unhook(Object o) 242 in { assert( status == ST.idle ); } 243 do 244 { 245 debug (signal) writefln("Signal.unhook(o = %s)", cast(void*) o); 246 for (size_t i = 0; i < slots_idx; ) 247 { 248 if (_d_toObject(slots[i].ptr) is o) 249 { slots_idx--; 250 slots[i] = slots[slots_idx]; 251 slots[slots_idx] = null; // not strictly necessary 252 } 253 else 254 i++; 255 } 256 } 257 258 /* ** 259 * There can be multiple destructors inserted by mixins. 260 */ 261 ~this() 262 { 263 /* ** 264 * When this object is destroyed, need to let every slot 265 * know that this object is destroyed so they are not left 266 * with dangling references to it. 267 */ 268 if (slots.length) 269 { 270 foreach (slot; slots[0 .. slots_idx]) 271 { 272 if (slot) 273 { Object o = _d_toObject(slot.ptr); 274 rt_detachDisposeEvent(o, &unhook); 275 } 276 } 277 core.stdc.stdlib.free(slots.ptr); 278 slots = null; 279 } 280 } 281 282 private: 283 slot_t[] slots; // the slots to call from emit() 284 size_t slots_idx; // used length of slots[] 285 286 enum ST { idle, inemitting, inemitting_disconnected } 287 ST status; 288 } 289 290 /// 291 @system unittest 292 { 293 import std.signals; 294 295 int observedMessageCounter = 0; 296 297 class Observer 298 { // our slot 299 void watch(string msg, int value) 300 { 301 switch (observedMessageCounter++) 302 { 303 case 0: 304 assert(msg == "setting new value"); 305 assert(value == 4); 306 break; 307 case 1: 308 assert(msg == "setting new value"); 309 assert(value == 6); 310 break; 311 default: 312 assert(0, "Unknown observation"); 313 } 314 } 315 } 316 317 class Observer2 318 { // our slot 319 void watch(string msg, int value) 320 { 321 } 322 } 323 324 class Foo 325 { 326 int value() { return _value; } 327 328 int value(int v) 329 { 330 if (v != _value) 331 { _value = v; 332 // call all the connected slots with the two parameters 333 emit("setting new value", v); 334 } 335 return v; 336 } 337 338 // Mix in all the code we need to make Foo into a signal 339 mixin Signal!(string, int); 340 341 private : 342 int _value; 343 } 344 345 Foo a = new Foo; 346 Observer o = new Observer; 347 auto o2 = new Observer2; 348 auto o3 = new Observer2; 349 auto o4 = new Observer2; 350 auto o5 = new Observer2; 351 352 a.value = 3; // should not call o.watch() 353 a.connect(&o.watch); // o.watch is the slot 354 a.connect(&o2.watch); 355 a.connect(&o3.watch); 356 a.connect(&o4.watch); 357 a.connect(&o5.watch); 358 a.value = 4; // should call o.watch() 359 a.disconnect(&o.watch); // o.watch is no longer a slot 360 a.disconnect(&o3.watch); 361 a.disconnect(&o5.watch); 362 a.disconnect(&o4.watch); 363 a.disconnect(&o2.watch); 364 a.value = 5; // so should not call o.watch() 365 a.connect(&o2.watch); 366 a.connect(&o.watch); // connect again 367 a.value = 6; // should call o.watch() 368 destroy(o); // destroying o should automatically disconnect it 369 a.value = 7; // should not call o.watch() 370 371 assert(observedMessageCounter == 2); 372 } 373 374 // A function whose sole purpose is to get this module linked in 375 // so the unittest will run. 376 void linkin() { } 377 378 @system unittest 379 { 380 class Observer 381 { 382 void watch(string msg, int i) 383 { 384 //writefln("Observed msg '%s' and value %s", msg, i); 385 captured_value = i; 386 captured_msg = msg; 387 } 388 389 int captured_value; 390 string captured_msg; 391 } 392 393 class Foo 394 { 395 @property int value() { return _value; } 396 397 @property int value(int v) 398 { 399 if (v != _value) 400 { _value = v; 401 emit("setting new value", v); 402 } 403 return v; 404 } 405 406 mixin Signal!(string, int); 407 408 private: 409 int _value; 410 } 411 412 Foo a = new Foo; 413 Observer o = new Observer; 414 415 // check initial condition 416 assert(o.captured_value == 0); 417 assert(o.captured_msg == ""); 418 419 // set a value while no observation is in place 420 a.value = 3; 421 assert(o.captured_value == 0); 422 assert(o.captured_msg == ""); 423 424 // connect the watcher and trigger it 425 a.connect(&o.watch); 426 a.value = 4; 427 assert(o.captured_value == 4); 428 assert(o.captured_msg == "setting new value"); 429 430 // disconnect the watcher and make sure it doesn't trigger 431 a.disconnect(&o.watch); 432 a.value = 5; 433 assert(o.captured_value == 4); 434 assert(o.captured_msg == "setting new value"); 435 436 // reconnect the watcher and make sure it triggers 437 a.connect(&o.watch); 438 a.value = 6; 439 assert(o.captured_value == 6); 440 assert(o.captured_msg == "setting new value"); 441 442 // destroy the underlying object and make sure it doesn't cause 443 // a crash or other problems 444 destroy(o); 445 a.value = 7; 446 } 447 448 @system unittest 449 { 450 class Observer 451 { 452 int i; 453 long l; 454 string str; 455 456 void watchInt(string str, int i) 457 { 458 this.str = str; 459 this.i = i; 460 } 461 462 void watchLong(string str, long l) 463 { 464 this.str = str; 465 this.l = l; 466 } 467 } 468 469 class Bar 470 { 471 @property void value1(int v) { s1.emit("str1", v); } 472 @property void value2(int v) { s2.emit("str2", v); } 473 @property void value3(long v) { s3.emit("str3", v); } 474 475 mixin Signal!(string, int) s1; 476 mixin Signal!(string, int) s2; 477 mixin Signal!(string, long) s3; 478 } 479 480 void test(T)(T a) { 481 auto o1 = new Observer; 482 auto o2 = new Observer; 483 auto o3 = new Observer; 484 485 // connect the watcher and trigger it 486 a.s1.connect(&o1.watchInt); 487 a.s2.connect(&o2.watchInt); 488 a.s3.connect(&o3.watchLong); 489 490 assert(!o1.i && !o1.l && o1.str == null); 491 assert(!o2.i && !o2.l && o2.str == null); 492 assert(!o3.i && !o3.l && o3.str == null); 493 494 a.value1 = 11; 495 assert(o1.i == 11 && !o1.l && o1.str == "str1"); 496 assert(!o2.i && !o2.l && o2.str == null); 497 assert(!o3.i && !o3.l && o3.str == null); 498 o1.i = -11; o1.str = "x1"; 499 500 a.value2 = 12; 501 assert(o1.i == -11 && !o1.l && o1.str == "x1"); 502 assert(o2.i == 12 && !o2.l && o2.str == "str2"); 503 assert(!o3.i && !o3.l && o3.str == null); 504 o2.i = -12; o2.str = "x2"; 505 506 a.value3 = 13; 507 assert(o1.i == -11 && !o1.l && o1.str == "x1"); 508 assert(o2.i == -12 && !o1.l && o2.str == "x2"); 509 assert(!o3.i && o3.l == 13 && o3.str == "str3"); 510 o3.l = -13; o3.str = "x3"; 511 512 // disconnect the watchers and make sure it doesn't trigger 513 a.s1.disconnect(&o1.watchInt); 514 a.s2.disconnect(&o2.watchInt); 515 a.s3.disconnect(&o3.watchLong); 516 517 a.value1 = 21; 518 a.value2 = 22; 519 a.value3 = 23; 520 assert(o1.i == -11 && !o1.l && o1.str == "x1"); 521 assert(o2.i == -12 && !o1.l && o2.str == "x2"); 522 assert(!o3.i && o3.l == -13 && o3.str == "x3"); 523 524 // reconnect the watcher and make sure it triggers 525 a.s1.connect(&o1.watchInt); 526 a.s2.connect(&o2.watchInt); 527 a.s3.connect(&o3.watchLong); 528 529 a.value1 = 31; 530 a.value2 = 32; 531 a.value3 = 33; 532 assert(o1.i == 31 && !o1.l && o1.str == "str1"); 533 assert(o2.i == 32 && !o1.l && o2.str == "str2"); 534 assert(!o3.i && o3.l == 33 && o3.str == "str3"); 535 536 // destroy observers 537 destroy(o1); 538 destroy(o2); 539 destroy(o3); 540 a.value1 = 41; 541 a.value2 = 42; 542 a.value3 = 43; 543 } 544 545 test(new Bar); 546 547 class BarDerived: Bar 548 { 549 @property void value4(int v) { s4.emit("str4", v); } 550 @property void value5(int v) { s5.emit("str5", v); } 551 @property void value6(long v) { s6.emit("str6", v); } 552 553 mixin Signal!(string, int) s4; 554 mixin Signal!(string, int) s5; 555 mixin Signal!(string, long) s6; 556 } 557 558 auto a = new BarDerived; 559 560 test!Bar(a); 561 test!BarDerived(a); 562 563 auto o4 = new Observer; 564 auto o5 = new Observer; 565 auto o6 = new Observer; 566 567 // connect the watcher and trigger it 568 a.s4.connect(&o4.watchInt); 569 a.s5.connect(&o5.watchInt); 570 a.s6.connect(&o6.watchLong); 571 572 assert(!o4.i && !o4.l && o4.str == null); 573 assert(!o5.i && !o5.l && o5.str == null); 574 assert(!o6.i && !o6.l && o6.str == null); 575 576 a.value4 = 44; 577 assert(o4.i == 44 && !o4.l && o4.str == "str4"); 578 assert(!o5.i && !o5.l && o5.str == null); 579 assert(!o6.i && !o6.l && o6.str == null); 580 o4.i = -44; o4.str = "x4"; 581 582 a.value5 = 45; 583 assert(o4.i == -44 && !o4.l && o4.str == "x4"); 584 assert(o5.i == 45 && !o5.l && o5.str == "str5"); 585 assert(!o6.i && !o6.l && o6.str == null); 586 o5.i = -45; o5.str = "x5"; 587 588 a.value6 = 46; 589 assert(o4.i == -44 && !o4.l && o4.str == "x4"); 590 assert(o5.i == -45 && !o4.l && o5.str == "x5"); 591 assert(!o6.i && o6.l == 46 && o6.str == "str6"); 592 o6.l = -46; o6.str = "x6"; 593 594 // disconnect the watchers and make sure it doesn't trigger 595 a.s4.disconnect(&o4.watchInt); 596 a.s5.disconnect(&o5.watchInt); 597 a.s6.disconnect(&o6.watchLong); 598 599 a.value4 = 54; 600 a.value5 = 55; 601 a.value6 = 56; 602 assert(o4.i == -44 && !o4.l && o4.str == "x4"); 603 assert(o5.i == -45 && !o4.l && o5.str == "x5"); 604 assert(!o6.i && o6.l == -46 && o6.str == "x6"); 605 606 // reconnect the watcher and make sure it triggers 607 a.s4.connect(&o4.watchInt); 608 a.s5.connect(&o5.watchInt); 609 a.s6.connect(&o6.watchLong); 610 611 a.value4 = 64; 612 a.value5 = 65; 613 a.value6 = 66; 614 assert(o4.i == 64 && !o4.l && o4.str == "str4"); 615 assert(o5.i == 65 && !o4.l && o5.str == "str5"); 616 assert(!o6.i && o6.l == 66 && o6.str == "str6"); 617 618 // destroy observers 619 destroy(o4); 620 destroy(o5); 621 destroy(o6); 622 a.value4 = 44; 623 a.value5 = 45; 624 a.value6 = 46; 625 } 626 627 // Triggers bug from https://issues.dlang.org/show_bug.cgi?id=15341 628 @system unittest 629 { 630 class Observer 631 { 632 void watch() { } 633 void watch2() { } 634 } 635 636 class Bar 637 { 638 mixin Signal!(); 639 } 640 641 auto a = new Bar; 642 auto o = new Observer; 643 644 //Connect both observer methods for the same instance 645 a.connect(&o.watch); 646 a.connect(&o.watch2); // not connecting watch2() or disconnecting it manually fixes the issue 647 648 //Disconnect a single method of the two 649 a.disconnect(&o.watch); // NOT disconnecting watch() fixes the issue 650 651 destroy(o); // destroying o should automatically call unhook and disconnect the slot for watch2 652 a.emit(); // should not raise segfault since &o.watch2 is no longer connected 653 } 654 655 version (none) // Disabled because of https://issues.dlang.org/show_bug.cgi?id=5028 656 @system unittest 657 { 658 class A 659 { 660 mixin Signal!(string, int) s1; 661 } 662 663 class B : A 664 { 665 mixin Signal!(string, int) s2; 666 } 667 } 668 669 // Triggers bug from https://issues.dlang.org/show_bug.cgi?id=16249 670 @system unittest 671 { 672 class myLINE 673 { 674 mixin Signal!( myLINE, int ); 675 676 void value( int v ) 677 { 678 if ( v >= 0 ) emit( this, v ); 679 else emit( new myLINE, v ); 680 } 681 } 682 683 class Dot 684 { 685 int value; 686 687 myLINE line_; 688 void line( myLINE line_x ) 689 { 690 if ( line_ is line_x ) return; 691 692 if ( line_ !is null ) 693 { 694 line_.disconnect( &watch ); 695 } 696 line_ = line_x; 697 line_.connect( &watch ); 698 } 699 700 void watch( myLINE line_x, int value_x ) 701 { 702 line = line_x; 703 value = value_x; 704 } 705 } 706 707 auto dot1 = new Dot; 708 auto dot2 = new Dot; 709 auto line = new myLINE; 710 dot1.line = line; 711 dot2.line = line; 712 713 line.value = 11; 714 assert( dot1.value == 11 ); 715 assert( dot2.value == 11 ); 716 717 line.value = -22; 718 assert( dot1.value == -22 ); 719 assert( dot2.value == -22 ); 720 } 721 722 @system unittest 723 { 724 import std.signals; 725 726 class Observer 727 { // our slot 728 void watch(string msg, int value) 729 { 730 if (value != 0) 731 { 732 assert(msg == "setting new value"); 733 assert(value == 1); 734 } 735 } 736 } 737 738 class Foo 739 { 740 int value() { return _value; } 741 742 int value(int v) 743 { 744 if (v != _value) 745 { 746 _value = v; 747 // call all the connected slots with the parameters 748 emit("setting new value", v); 749 } 750 return v; 751 } 752 753 // Mix in all the code we need to make Foo into a signal 754 mixin Signal!(string, int); 755 756 private : 757 int _value; 758 } 759 760 Foo a = new Foo; 761 Observer o = new Observer; 762 auto o2 = new Observer; 763 764 a.value = 3; // should not call o.watch() 765 a.connect(&o.watch); // o.watch is the slot 766 a.connect(&o2.watch); 767 a.value = 1; // should call o.watch() 768 a.disconnectAll(); 769 a.value = 5; // so should not call o.watch() 770 a.connect(&o.watch); // connect again 771 a.connect(&o2.watch); 772 a.value = 1; // should call o.watch() 773 destroy(o); // destroying o should automatically disconnect it 774 destroy(o2); 775 a.value = 7; // should not call o.watch() 776 }