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