"Fossies" - the Fresh Open Source Software Archive

Member "dmd2/src/phobos/std/signals.d" (20 Nov 2020, 22804 Bytes) of package /linux/misc/dmd.2.094.2.linux.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) D source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    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 }