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 /*          Copyright Digital Mars 2000 - 2009.
57  * Distributed under the Boost Software License, Version 1.0.
58  *    (See accompanying file LICENSE_1_0.txt or copy at
59  *          http://www.boost.org/LICENSE_1_0.txt)
60  */
61 module puppeteer.signal;
62 
63 // Special function for internal use only.
64 // Use of this is where the slot had better be a delegate
65 // to an object or an interface that is part of an object.
66 extern (C) Object _d_toObject(void* p);
67 
68 // Used in place of Object.notifyRegister and Object.notifyUnRegister.
69 alias DisposeEvt = void delegate(Object) shared;
70 extern (C) void  rt_attachDisposeEvent( Object obj, DisposeEvt evt );
71 extern (C) void  rt_detachDisposeEvent( Object obj, DisposeEvt evt );
72 //debug=signal;
73 
74 /************************
75  * Mixin to create a signal within a class object.
76  *
77  * Different signals can be added to a class by naming the mixins.
78  */
79 
80 mixin template Signal(T1...)
81 {
82     import std.stdio;
83     import core.stdc.stdlib : calloc, realloc, free;
84     import core.exception : onOutOfMemoryError;
85     import core.atomic : atomicOp;
86 
87     static import core.stdc.stdlib;
88     static import core.exception;
89     /***
90      * A slot is implemented as a delegate.
91      * The slot_t is the type of the delegate.
92      * The delegate must be to an instance of a class or an interface
93      * to a class instance.
94      * Delegates to struct instances or nested functions must not be
95      * used as slots.
96      */
97     alias slot_t = void delegate(T1) shared;
98 
99     /***
100      * Call each of the connected slots, passing the argument(s) i to them.
101      */
102     final void emit( T1 i ) shared
103     {
104         foreach (slot; slots[0 .. slots_idx])
105         {   if (slot)
106                 slot(i);
107         }
108     }
109 
110     /***
111      * Add a slot to the list of slots to be called when emit() is called.
112      */
113     final void connect(slot_t slot) shared
114     {
115         /* Do this:
116          *    slots ~= slot;
117          * but use malloc() and friends instead
118          */
119         auto len = slots.length;
120         if (slots_idx == len)
121         {
122             if (slots.length == 0)
123             {
124                 len = 4;
125                 auto p = core.stdc.stdlib.calloc(slot_t.sizeof, len);
126                 if (!p)
127                     core.exception.onOutOfMemoryError();
128                 slots = cast(shared slot_t[])(cast(slot_t*)p)[0 .. len];
129             }
130             else
131             {
132                 len = len * 2 + 4;
133                 auto p = core.stdc.stdlib.realloc(cast(void*) slots.ptr, slot_t.sizeof * len);
134                 if (!p)
135                     core.exception.onOutOfMemoryError();
136                 slots = cast(shared slot_t[])(cast(slot_t*)p)[0 .. len];
137                 slots[slots_idx + 1 .. $] = null;
138             }
139         }
140         slots[slots_idx] = slot;
141         slots_idx.atomicOp!"+="(1);
142 
143      L1:
144         Object o = _d_toObject(slot.ptr);
145         rt_attachDisposeEvent(o, &unhook);
146     }
147 
148     /***
149      * Remove a slot from the list of slots to be called when emit() is called.
150      */
151     final void disconnect(slot_t slot) shared
152     {
153         debug (signal) writefln("Signal.disconnect(slot)");
154         for (size_t i = 0; i < slots_idx; )
155         {
156             if (slots[i] == slot)
157             {
158                 slots_idx.atomicOp!"-="(1);
159                 slots[i] = slots[slots_idx];
160                 slots[slots_idx] = null;        // not strictly necessary
161 
162                 Object o = _d_toObject(slot.ptr);
163                 rt_detachDisposeEvent(o, &unhook);
164             }
165             else
166                 i++;
167         }
168     }
169 
170     /* **
171      * Special function called when o is destroyed.
172      * It causes any slots dependent on o to be removed from the list
173      * of slots to be called by emit().
174      */
175     final private void unhook(Object o) shared
176     {
177         debug (signal) writefln("Signal.unhook(o = %s)", cast(void*)o);
178         for (size_t i = 0; i < slots_idx; )
179         {
180             if (_d_toObject(slots[i].ptr) is o)
181             {
182                 slots_idx.atomicOp!"-="(1);
183                 slots[i] = slots[slots_idx];
184                 slots[slots_idx] = null;        // not strictly necessary
185             }
186             else
187                 i++;
188         }
189     }
190 
191     /* **
192      * There can be multiple destructors inserted by mixins.
193      */
194     ~this() shared
195     {
196         /* **
197          * When this object is destroyed, need to let every slot
198          * know that this object is destroyed so they are not left
199          * with dangling references to it.
200          */
201         if (slots.length)
202         {
203             foreach (slot; slots[0 .. slots_idx])
204             {
205                 if (slot)
206                 {   Object o = _d_toObject(slot.ptr);
207                     rt_detachDisposeEvent(o, &unhook);
208                 }
209             }
210             core.stdc.stdlib.free(cast(void*) slots.ptr);
211             slots = null;
212         }
213     }
214 
215   private:
216     shared slot_t[] slots;             // the slots to call from emit()
217     shared size_t slots_idx;           // used length of slots[]
218 }
219 
220 ///
221 unittest
222 {
223     int observedMessageCounter = 0;
224 
225     shared class Observer
226     {   // our slot
227         void watch(string msg, int value)
228         {
229             switch (observedMessageCounter++)
230             {
231                 case 0:
232                     assert(msg == "setting new value");
233                     assert(value == 4);
234                     break;
235                 case 1:
236                     assert(msg == "setting new value");
237                     assert(value == 6);
238                     break;
239                 default:
240                     assert(0, "Unknown observation");
241             }
242         }
243     }
244 
245     shared class Foo
246     {
247         int value() { return _value; }
248 
249         int value(int v)
250         {
251             if (v != _value)
252             {   _value = v;
253                 // call all the connected slots with the two parameters
254                 emit("setting new value", v);
255             }
256             return v;
257         }
258 
259         // Mix in all the code we need to make Foo into a signal
260         mixin Signal!(string, int);
261 
262       private :
263         int _value;
264     }
265 
266     shared Foo a = new shared Foo;
267     shared Observer o = new shared Observer;
268 
269     a.value = 3;                // should not call o.watch()
270     a.connect(&o.watch);        // o.watch is the slot
271     a.value = 4;                // should call o.watch()
272     a.disconnect(&o.watch);     // o.watch is no longer a slot
273     a.value = 5;                // so should not call o.watch()
274     a.connect(&o.watch);        // connect again
275     a.value = 6;                // should call o.watch()
276     destroy(o);                 // destroying o should automatically disconnect it
277     a.value = 7;                // should not call o.watch()
278 
279     assert(observedMessageCounter == 2);
280 }
281 
282 // A function whose sole purpose is to get this module linked in
283 // so the unittest will run.
284 void linkin() { }
285 
286 unittest
287 {
288     shared class Observer
289     {
290         void watch(string msg, int i)
291         {
292             //writefln("Observed msg '%s' and value %s", msg, i);
293             captured_value = i;
294             captured_msg   = msg;
295         }
296 
297         int    captured_value;
298         string captured_msg;
299     }
300 
301     shared class Foo
302     {
303         @property int value() { return _value; }
304 
305         @property int value(int v)
306         {
307             if (v != _value)
308             {   _value = v;
309                 emit("setting new value", v);
310             }
311             return v;
312         }
313 
314         mixin Signal!(string, int);
315 
316       private:
317         int _value;
318     }
319 
320     shared Foo a = new shared Foo;
321     shared Observer o = new shared Observer;
322 
323     // check initial condition
324     assert(o.captured_value == 0);
325     assert(o.captured_msg == "");
326 
327     // set a value while no observation is in place
328     a.value = 3;
329     assert(o.captured_value == 0);
330     assert(o.captured_msg == "");
331 
332     // connect the watcher and trigger it
333     a.connect(&o.watch);
334     a.value = 4;
335     assert(o.captured_value == 4);
336     assert(o.captured_msg == "setting new value");
337 
338     // disconnect the watcher and make sure it doesn't trigger
339     a.disconnect(&o.watch);
340     a.value = 5;
341     assert(o.captured_value == 4);
342     assert(o.captured_msg == "setting new value");
343 
344     // reconnect the watcher and make sure it triggers
345     a.connect(&o.watch);
346     a.value = 6;
347     assert(o.captured_value == 6);
348     assert(o.captured_msg == "setting new value");
349 
350     // destroy the underlying object and make sure it doesn't cause
351     // a crash or other problems
352     destroy(o);
353     a.value = 7;
354 }
355 
356 unittest
357 {
358     shared class Observer
359     {
360         int    i;
361         long   l;
362         string str;
363 
364         void watchInt(string str, int i)
365         {
366             this.str = str;
367             this.i = i;
368         }
369 
370         void watchLong(string str, long l)
371         {
372             this.str = str;
373             this.l = l;
374         }
375     }
376 
377     shared class Bar
378     {
379         @property void value1(int v)  { s1.emit("str1", v); }
380         @property void value2(int v)  { s2.emit("str2", v); }
381         @property void value3(long v) { s3.emit("str3", v); }
382 
383         mixin Signal!(string, int)  s1;
384         mixin Signal!(string, int)  s2;
385         mixin Signal!(string, long) s3;
386     }
387 
388     void test(T)(T a) {
389         auto o1 = new shared Observer;
390         auto o2 = new shared Observer;
391         auto o3 = new shared Observer;
392 
393         // connect the watcher and trigger it
394         a.s1.connect(&o1.watchInt);
395         a.s2.connect(&o2.watchInt);
396         a.s3.connect(&o3.watchLong);
397 
398         assert(!o1.i && !o1.l && o1.str == null);
399         assert(!o2.i && !o2.l && o2.str == null);
400         assert(!o3.i && !o3.l && o3.str == null);
401 
402         a.value1 = 11;
403         assert(o1.i == 11 && !o1.l && o1.str == "str1");
404         assert(!o2.i && !o2.l && o2.str == null);
405         assert(!o3.i && !o3.l && o3.str == null);
406         o1.i = -11; o1.str = "x1";
407 
408         a.value2 = 12;
409         assert(o1.i == -11 && !o1.l && o1.str == "x1");
410         assert(o2.i == 12 && !o2.l && o2.str == "str2");
411         assert(!o3.i && !o3.l && o3.str == null);
412         o2.i = -12; o2.str = "x2";
413 
414         a.value3 = 13;
415         assert(o1.i == -11 && !o1.l && o1.str == "x1");
416         assert(o2.i == -12 && !o1.l && o2.str == "x2");
417         assert(!o3.i && o3.l == 13 && o3.str == "str3");
418         o3.l = -13; o3.str = "x3";
419 
420         // disconnect the watchers and make sure it doesn't trigger
421         a.s1.disconnect(&o1.watchInt);
422         a.s2.disconnect(&o2.watchInt);
423         a.s3.disconnect(&o3.watchLong);
424 
425         a.value1 = 21;
426         a.value2 = 22;
427         a.value3 = 23;
428         assert(o1.i == -11 && !o1.l && o1.str == "x1");
429         assert(o2.i == -12 && !o1.l && o2.str == "x2");
430         assert(!o3.i && o3.l == -13 && o3.str == "x3");
431 
432         // reconnect the watcher and make sure it triggers
433         a.s1.connect(&o1.watchInt);
434         a.s2.connect(&o2.watchInt);
435         a.s3.connect(&o3.watchLong);
436 
437         a.value1 = 31;
438         a.value2 = 32;
439         a.value3 = 33;
440         assert(o1.i == 31 && !o1.l && o1.str == "str1");
441         assert(o2.i == 32 && !o1.l && o2.str == "str2");
442         assert(!o3.i && o3.l == 33 && o3.str == "str3");
443 
444         // destroy observers
445         destroy(o1);
446         destroy(o2);
447         destroy(o3);
448         a.value1 = 41;
449         a.value2 = 42;
450         a.value3 = 43;
451     }
452 
453     test(new shared Bar);
454 
455     shared class BarDerived: Bar
456     {
457         @property void value4(int v)  { s4.emit("str4", v); }
458         @property void value5(int v)  { s5.emit("str5", v); }
459         @property void value6(long v) { s6.emit("str6", v); }
460 
461         mixin Signal!(string, int)  s4;
462         mixin Signal!(string, int)  s5;
463         mixin Signal!(string, long) s6;
464     }
465 
466     auto a = new shared BarDerived;
467 
468     test!(shared Bar)(a);
469     test!(shared BarDerived)(a);
470 
471     auto o4 = new shared Observer;
472     auto o5 = new shared Observer;
473     auto o6 = new shared Observer;
474 
475     // connect the watcher and trigger it
476     a.s4.connect(&o4.watchInt);
477     a.s5.connect(&o5.watchInt);
478     a.s6.connect(&o6.watchLong);
479 
480     assert(!o4.i && !o4.l && o4.str == null);
481     assert(!o5.i && !o5.l && o5.str == null);
482     assert(!o6.i && !o6.l && o6.str == null);
483 
484     a.value4 = 44;
485     assert(o4.i == 44 && !o4.l && o4.str == "str4");
486     assert(!o5.i && !o5.l && o5.str == null);
487     assert(!o6.i && !o6.l && o6.str == null);
488     o4.i = -44; o4.str = "x4";
489 
490     a.value5 = 45;
491     assert(o4.i == -44 && !o4.l && o4.str == "x4");
492     assert(o5.i == 45 && !o5.l && o5.str == "str5");
493     assert(!o6.i && !o6.l && o6.str == null);
494     o5.i = -45; o5.str = "x5";
495 
496     a.value6 = 46;
497     assert(o4.i == -44 && !o4.l && o4.str == "x4");
498     assert(o5.i == -45 && !o4.l && o5.str == "x5");
499     assert(!o6.i && o6.l == 46 && o6.str == "str6");
500     o6.l = -46; o6.str = "x6";
501 
502     // disconnect the watchers and make sure it doesn't trigger
503     a.s4.disconnect(&o4.watchInt);
504     a.s5.disconnect(&o5.watchInt);
505     a.s6.disconnect(&o6.watchLong);
506 
507     a.value4 = 54;
508     a.value5 = 55;
509     a.value6 = 56;
510     assert(o4.i == -44 && !o4.l && o4.str == "x4");
511     assert(o5.i == -45 && !o4.l && o5.str == "x5");
512     assert(!o6.i && o6.l == -46 && o6.str == "x6");
513 
514     // reconnect the watcher and make sure it triggers
515     a.s4.connect(&o4.watchInt);
516     a.s5.connect(&o5.watchInt);
517     a.s6.connect(&o6.watchLong);
518 
519     a.value4 = 64;
520     a.value5 = 65;
521     a.value6 = 66;
522     assert(o4.i == 64 && !o4.l && o4.str == "str4");
523     assert(o5.i == 65 && !o4.l && o5.str == "str5");
524     assert(!o6.i && o6.l == 66 && o6.str == "str6");
525 
526     // destroy observers
527     destroy(o4);
528     destroy(o5);
529     destroy(o6);
530     a.value4 = 44;
531     a.value5 = 45;
532     a.value6 = 46;
533 }
534 
535 version(none) // Disabled because of dmd @@@BUG5028@@@
536 unittest
537 {
538     class A
539     {
540         mixin Signal!(string, int) s1;
541     }
542 
543     class B : A
544     {
545         mixin Signal!(string, int) s2;
546     }
547 }