xref: /netbsd-src/external/gpl3/gcc.old/dist/libphobos/src/std/signals.d (revision 627f7eb200a4419d89b531d55fccd2ee3ffdcde0)
1 // Written in the D programming language.
2 
3 /**
4  * Signals and Slots are an implementation of the Observer Pattern.
5  * Essentially, when a Signal is emitted, a list of connected Observers
6  * (called slots) are called.
7  *
8  * There have been several D implementations of Signals and Slots.
9  * This version makes use of several new features in D, which make
10  * using it simpler and less error prone. In particular, it is no
11  * longer necessary to instrument the slots.
12  *
13  * References:
14  *      $(LUCKY A Deeper Look at Signals and Slots)$(BR)
15  *      $(LINK2 http://en.wikipedia.org/wiki/Observer_pattern, Observer pattern)$(BR)
16  *      $(LINK2 http://en.wikipedia.org/wiki/Signals_and_slots, Wikipedia)$(BR)
17  *      $(LINK2 http://boost.org/doc/html/$(SIGNALS).html, Boost Signals)$(BR)
18  *      $(LINK2 http://qt-project.org/doc/qt-5/signalsandslots.html, Qt)$(BR)
19  *
20  *      There has been a great deal of discussion in the D newsgroups
21  *      over this, and several implementations:
22  *
23  *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/announce/signal_slots_library_4825.html, signal slots library)$(BR)
24  *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Signals_and_Slots_in_D_42387.html, Signals and Slots in D)$(BR)
25  *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Dynamic_binding_--_Qt_s_Signals_and_Slots_vs_Objective-C_42260.html, Dynamic binding -- Qt's Signals and Slots vs Objective-C)$(BR)
26  *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Dissecting_the_SS_42377.html, Dissecting the SS)$(BR)
27  *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/dwt/about_harmonia_454.html, about harmonia)$(BR)
28  *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/announce/1502.html, Another event handling module)$(BR)
29  *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/41825.html, Suggestion: signal/slot mechanism)$(BR)
30  *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/13251.html, Signals and slots?)$(BR)
31  *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/10714.html, Signals and slots ready for evaluation)$(BR)
32  *      $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/1393.html, Signals & Slots for Walter)$(BR)
33  *      $(LINK2 http://www.digitalmars.com/d/archives/28456.html, Signal/Slot mechanism?)$(BR)
34  *      $(LINK2 http://www.digitalmars.com/d/archives/19470.html, Modern Features?)$(BR)
35  *      $(LINK2 http://www.digitalmars.com/d/archives/16592.html, Delegates vs interfaces)$(BR)
36  *      $(LINK2 http://www.digitalmars.com/d/archives/16583.html, The importance of component programming (properties$(COMMA) signals and slots$(COMMA) etc))$(BR)
37  *      $(LINK2 http://www.digitalmars.com/d/archives/16368.html, signals and slots)$(BR)
38  *
39  * Bugs:
40  *      Slots can only be delegates formed from class objects or
41  *      interfaces to class objects. If a delegate to something else
42  *      is passed to connect(), such as a struct member function,
43  *      a nested function or a COM interface, undefined behavior
44  *      will result.
45  *
46  *      Not safe for multiple threads operating on the same signals
47  *      or slots.
48  * Macros:
49  *      SIGNALS=signals
50  *
51  * Copyright: Copyright Digital Mars 2000 - 2009.
52  * License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
53  * Authors:   $(HTTP digitalmars.com, Walter Bright)
54  * Source:    $(PHOBOSSRC std/_signals.d)
55  *
56  * $(SCRIPT inhibitQuickIndex = 1;)
57  */
58 /*          Copyright Digital Mars 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 
Signal(T1...)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      * Special function called when o is destroyed.
226      * It causes any slots dependent on o to be removed from the list
227      * of slots to be called by emit().
228      */
229     final void unhook(Object o)
230     in { assert( status == ST.idle ); }
231     body {
232         debug (signal) writefln("Signal.unhook(o = %s)", cast(void*) o);
233         for (size_t i = 0; i < slots_idx; )
234         {
235             if (_d_toObject(slots[i].ptr) is o)
236             {   slots_idx--;
237                 slots[i] = slots[slots_idx];
238                 slots[slots_idx] = null;        // not strictly necessary
239             }
240             else
241                 i++;
242         }
243     }
244 
245     /* **
246      * There can be multiple destructors inserted by mixins.
247      */
248     ~this()
249     {
250         /* **
251          * When this object is destroyed, need to let every slot
252          * know that this object is destroyed so they are not left
253          * with dangling references to it.
254          */
255         if (slots.length)
256         {
257             foreach (slot; slots[0 .. slots_idx])
258             {
259                 if (slot)
260                 {   Object o = _d_toObject(slot.ptr);
261                     rt_detachDisposeEvent(o, &unhook);
262                 }
263             }
264             core.stdc.stdlib.free(slots.ptr);
265             slots = null;
266         }
267     }
268 
269   private:
270     slot_t[] slots;             // the slots to call from emit()
271     size_t slots_idx;           // used length of slots[]
272 
273     enum ST { idle, inemitting, inemitting_disconnected }
274     ST status;
275 }
276 
277 ///
278 @system unittest
279 {
280     import std.signals;
281 
282     int observedMessageCounter = 0;
283 
284     class Observer
285     {   // our slot
watch(string msg,int value)286         void watch(string msg, int value)
287         {
288             switch (observedMessageCounter++)
289             {
290                 case 0:
291                     assert(msg == "setting new value");
292                     assert(value == 4);
293                     break;
294                 case 1:
295                     assert(msg == "setting new value");
296                     assert(value == 6);
297                     break;
298                 default:
299                     assert(0, "Unknown observation");
300             }
301         }
302     }
303 
304     class Observer2
305     {   // our slot
watch(string msg,int value)306         void watch(string msg, int value)
307         {
308         }
309     }
310 
311     class Foo
312     {
value()313         int value() { return _value; }
314 
value(int v)315         int value(int v)
316         {
317             if (v != _value)
318             {   _value = v;
319                 // call all the connected slots with the two parameters
320                 emit("setting new value", v);
321             }
322             return v;
323         }
324 
325         // Mix in all the code we need to make Foo into a signal
326         mixin Signal!(string, int);
327 
328       private :
329         int _value;
330     }
331 
332     Foo a = new Foo;
333     Observer o = new Observer;
334     auto o2 = new Observer2;
335     auto o3 = new Observer2;
336     auto o4 = new Observer2;
337     auto o5 = new Observer2;
338 
339     a.value = 3;                // should not call o.watch()
340     a.connect(&o.watch);        // o.watch is the slot
341     a.connect(&o2.watch);
342     a.connect(&o3.watch);
343     a.connect(&o4.watch);
344     a.connect(&o5.watch);
345     a.value = 4;                // should call o.watch()
346     a.disconnect(&o.watch);     // o.watch is no longer a slot
347     a.disconnect(&o3.watch);
348     a.disconnect(&o5.watch);
349     a.disconnect(&o4.watch);
350     a.disconnect(&o2.watch);
351     a.value = 5;                // so should not call o.watch()
352     a.connect(&o2.watch);
353     a.connect(&o.watch);        // connect again
354     a.value = 6;                // should call o.watch()
355     destroy(o);                 // destroying o should automatically disconnect it
356     a.value = 7;                // should not call o.watch()
357 
358     assert(observedMessageCounter == 2);
359 }
360 
361 // A function whose sole purpose is to get this module linked in
362 // so the unittest will run.
linkin()363 void linkin() { }
364 
365 @system unittest
366 {
367     class Observer
368     {
watch(string msg,int i)369         void watch(string msg, int i)
370         {
371             //writefln("Observed msg '%s' and value %s", msg, i);
372             captured_value = i;
373             captured_msg   = msg;
374         }
375 
376         int    captured_value;
377         string captured_msg;
378     }
379 
380     class Foo
381     {
value()382         @property int value() { return _value; }
383 
value(int v)384         @property int value(int v)
385         {
386             if (v != _value)
387             {   _value = v;
388                 emit("setting new value", v);
389             }
390             return v;
391         }
392 
393         mixin Signal!(string, int);
394 
395       private:
396         int _value;
397     }
398 
399     Foo a = new Foo;
400     Observer o = new Observer;
401 
402     // check initial condition
403     assert(o.captured_value == 0);
404     assert(o.captured_msg == "");
405 
406     // set a value while no observation is in place
407     a.value = 3;
408     assert(o.captured_value == 0);
409     assert(o.captured_msg == "");
410 
411     // connect the watcher and trigger it
412     a.connect(&o.watch);
413     a.value = 4;
414     assert(o.captured_value == 4);
415     assert(o.captured_msg == "setting new value");
416 
417     // disconnect the watcher and make sure it doesn't trigger
418     a.disconnect(&o.watch);
419     a.value = 5;
420     assert(o.captured_value == 4);
421     assert(o.captured_msg == "setting new value");
422 
423     // reconnect the watcher and make sure it triggers
424     a.connect(&o.watch);
425     a.value = 6;
426     assert(o.captured_value == 6);
427     assert(o.captured_msg == "setting new value");
428 
429     // destroy the underlying object and make sure it doesn't cause
430     // a crash or other problems
431     destroy(o);
432     a.value = 7;
433 }
434 
435 @system unittest
436 {
437     class Observer
438     {
439         int    i;
440         long   l;
441         string str;
442 
watchInt(string str,int i)443         void watchInt(string str, int i)
444         {
445             this.str = str;
446             this.i = i;
447         }
448 
watchLong(string str,long l)449         void watchLong(string str, long l)
450         {
451             this.str = str;
452             this.l = l;
453         }
454     }
455 
456     class Bar
457     {
value1(int v)458         @property void value1(int v)  { s1.emit("str1", v); }
value2(int v)459         @property void value2(int v)  { s2.emit("str2", v); }
value3(long v)460         @property void value3(long v) { s3.emit("str3", v); }
461 
462         mixin Signal!(string, int)  s1;
463         mixin Signal!(string, int)  s2;
464         mixin Signal!(string, long) s3;
465     }
466 
test(T)467     void test(T)(T a) {
468         auto o1 = new Observer;
469         auto o2 = new Observer;
470         auto o3 = new Observer;
471 
472         // connect the watcher and trigger it
473         a.s1.connect(&o1.watchInt);
474         a.s2.connect(&o2.watchInt);
475         a.s3.connect(&o3.watchLong);
476 
477         assert(!o1.i && !o1.l && o1.str == null);
478         assert(!o2.i && !o2.l && o2.str == null);
479         assert(!o3.i && !o3.l && o3.str == null);
480 
481         a.value1 = 11;
482         assert(o1.i == 11 && !o1.l && o1.str == "str1");
483         assert(!o2.i && !o2.l && o2.str == null);
484         assert(!o3.i && !o3.l && o3.str == null);
485         o1.i = -11; o1.str = "x1";
486 
487         a.value2 = 12;
488         assert(o1.i == -11 && !o1.l && o1.str == "x1");
489         assert(o2.i == 12 && !o2.l && o2.str == "str2");
490         assert(!o3.i && !o3.l && o3.str == null);
491         o2.i = -12; o2.str = "x2";
492 
493         a.value3 = 13;
494         assert(o1.i == -11 && !o1.l && o1.str == "x1");
495         assert(o2.i == -12 && !o1.l && o2.str == "x2");
496         assert(!o3.i && o3.l == 13 && o3.str == "str3");
497         o3.l = -13; o3.str = "x3";
498 
499         // disconnect the watchers and make sure it doesn't trigger
500         a.s1.disconnect(&o1.watchInt);
501         a.s2.disconnect(&o2.watchInt);
502         a.s3.disconnect(&o3.watchLong);
503 
504         a.value1 = 21;
505         a.value2 = 22;
506         a.value3 = 23;
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 == "x3");
510 
511         // reconnect the watcher and make sure it triggers
512         a.s1.connect(&o1.watchInt);
513         a.s2.connect(&o2.watchInt);
514         a.s3.connect(&o3.watchLong);
515 
516         a.value1 = 31;
517         a.value2 = 32;
518         a.value3 = 33;
519         assert(o1.i == 31 && !o1.l && o1.str == "str1");
520         assert(o2.i == 32 && !o1.l && o2.str == "str2");
521         assert(!o3.i && o3.l == 33 && o3.str == "str3");
522 
523         // destroy observers
524         destroy(o1);
525         destroy(o2);
526         destroy(o3);
527         a.value1 = 41;
528         a.value2 = 42;
529         a.value3 = 43;
530     }
531 
532     test(new Bar);
533 
534     class BarDerived: Bar
535     {
value4(int v)536         @property void value4(int v)  { s4.emit("str4", v); }
value5(int v)537         @property void value5(int v)  { s5.emit("str5", v); }
value6(long v)538         @property void value6(long v) { s6.emit("str6", v); }
539 
540         mixin Signal!(string, int)  s4;
541         mixin Signal!(string, int)  s5;
542         mixin Signal!(string, long) s6;
543     }
544 
545     auto a = new BarDerived;
546 
547     test!Bar(a);
548     test!BarDerived(a);
549 
550     auto o4 = new Observer;
551     auto o5 = new Observer;
552     auto o6 = new Observer;
553 
554     // connect the watcher and trigger it
555     a.s4.connect(&o4.watchInt);
556     a.s5.connect(&o5.watchInt);
557     a.s6.connect(&o6.watchLong);
558 
559     assert(!o4.i && !o4.l && o4.str == null);
560     assert(!o5.i && !o5.l && o5.str == null);
561     assert(!o6.i && !o6.l && o6.str == null);
562 
563     a.value4 = 44;
564     assert(o4.i == 44 && !o4.l && o4.str == "str4");
565     assert(!o5.i && !o5.l && o5.str == null);
566     assert(!o6.i && !o6.l && o6.str == null);
567     o4.i = -44; o4.str = "x4";
568 
569     a.value5 = 45;
570     assert(o4.i == -44 && !o4.l && o4.str == "x4");
571     assert(o5.i == 45 && !o5.l && o5.str == "str5");
572     assert(!o6.i && !o6.l && o6.str == null);
573     o5.i = -45; o5.str = "x5";
574 
575     a.value6 = 46;
576     assert(o4.i == -44 && !o4.l && o4.str == "x4");
577     assert(o5.i == -45 && !o4.l && o5.str == "x5");
578     assert(!o6.i && o6.l == 46 && o6.str == "str6");
579     o6.l = -46; o6.str = "x6";
580 
581     // disconnect the watchers and make sure it doesn't trigger
582     a.s4.disconnect(&o4.watchInt);
583     a.s5.disconnect(&o5.watchInt);
584     a.s6.disconnect(&o6.watchLong);
585 
586     a.value4 = 54;
587     a.value5 = 55;
588     a.value6 = 56;
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 == "x6");
592 
593     // reconnect the watcher and make sure it triggers
594     a.s4.connect(&o4.watchInt);
595     a.s5.connect(&o5.watchInt);
596     a.s6.connect(&o6.watchLong);
597 
598     a.value4 = 64;
599     a.value5 = 65;
600     a.value6 = 66;
601     assert(o4.i == 64 && !o4.l && o4.str == "str4");
602     assert(o5.i == 65 && !o4.l && o5.str == "str5");
603     assert(!o6.i && o6.l == 66 && o6.str == "str6");
604 
605     // destroy observers
606     destroy(o4);
607     destroy(o5);
608     destroy(o6);
609     a.value4 = 44;
610     a.value5 = 45;
611     a.value6 = 46;
612 }
613 
614 // Triggers bug from issue 15341
615 @system unittest
616 {
617     class Observer
618     {
watch()619        void watch() { }
watch2()620        void watch2() { }
621     }
622 
623     class Bar
624     {
625        mixin Signal!();
626     }
627 
628    auto a = new Bar;
629    auto o = new Observer;
630 
631    //Connect both observer methods for the same instance
632    a.connect(&o.watch);
633    a.connect(&o.watch2); // not connecting watch2() or disconnecting it manually fixes the issue
634 
635    //Disconnect a single method of the two
636    a.disconnect(&o.watch); // NOT disconnecting watch() fixes the issue
637 
638    destroy(o); // destroying o should automatically call unhook and disconnect the slot for watch2
639    a.emit(); // should not raise segfault since &o.watch2 is no longer connected
640 }
641 
version(none)642 version (none) // Disabled because of dmd @@@BUG5028@@@
643 @system unittest
644 {
645     class A
646     {
647         mixin Signal!(string, int) s1;
648     }
649 
650     class B : A
651     {
652         mixin Signal!(string, int) s2;
653     }
654 }
655 
656 // Triggers bug from issue 16249
657 @system unittest
658 {
659     class myLINE
660     {
661         mixin Signal!( myLINE, int );
662 
value(int v)663         void value( int v )
664         {
665             if ( v >= 0 ) emit( this, v );
666             else          emit( new myLINE, v );
667         }
668     }
669 
670     class Dot
671     {
672         int value;
673 
674         myLINE line_;
line(myLINE line_x)675         void line( myLINE line_x )
676         {
677             if ( line_ is line_x ) return;
678 
679             if ( line_ !is null )
680             {
681                 line_.disconnect( &watch );
682             }
683             line_ = line_x;
684             line_.connect( &watch );
685         }
686 
watch(myLINE line_x,int value_x)687         void watch( myLINE line_x, int value_x )
688         {
689             line = line_x;
690             value = value_x;
691         }
692     }
693 
694     auto dot1 = new Dot;
695     auto dot2 = new Dot;
696     auto line = new myLINE;
697     dot1.line = line;
698     dot2.line = line;
699 
700     line.value = 11;
701     assert( dot1.value == 11 );
702     assert( dot2.value == 11 );
703 
704     line.value = -22;
705     assert( dot1.value == -22 );
706     assert( dot2.value == -22 );
707 }
708 
709