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