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 */
63module std.signals;
64
65import core.exception : onOutOfMemoryError;
66import core.stdc.stdlib : calloc, realloc, free;
67import 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.
72extern (C) Object _d_toObject(void* p);
73
74// Used in place of Object.notifyRegister and Object.notifyUnRegister.
75alias DisposeEvt = void delegate(Object);
76extern (C) void  rt_attachDisposeEvent( Object obj, DisposeEvt evt );
77extern (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
86mixin 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.
376void 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 issue 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
655version (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 issue 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}
777