comparison d2/qtd/Signal.d @ 311:8674fd5f34f4 lifetime

Added d1/d2 top directories
author maxter <spambox@d-coding.com>
date Wed, 23 Dec 2009 16:17:22 +0200
parents
children 80b52f5e97b6
comparison
equal deleted inserted replaced
310:5bcfe9e7db7f 311:8674fd5f34f4
1 /**
2 *
3 * Copyright: Copyright QtD Team, 2008-2009
4 * Authors: Max Samukha, Eldar Insafutdinov
5 * License: <a href="http://www.boost.org/LICENSE_1_0.txt>Boost License 1.0</a>
6 *
7 * Copyright QtD Team, 2008-2009
8 * Distributed under the Boost Software License, Version 1.0.
9 * (See accompanying file boost-license-1.0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
10 *
11 */
12 module qt.Signal;
13
14 import
15 qt.QGlobal,
16 qt.Memory;
17 public import std.metastrings;
18
19 import core.stdc.stdlib : crealloc = realloc, cfree = free;
20 import core.stdc.string : memmove;
21 import
22 std.traits,
23 core.thread,
24 core.exception;
25 private: // private by default
26
27 void realloc(T)(ref T[] a, size_t length)
28 {
29 a = (cast(T*)crealloc(a.ptr, length * T.sizeof))[0..length];
30 if (!a.ptr)
31 new OutOfMemoryError(__FILE__, __LINE__);
32 }
33
34 unittest
35 {
36 int[] a;
37 realloc(a, 16);
38 assert(a.length == 16);
39 foreach (i, ref e; a)
40 e = i;
41 realloc(a, 4096);
42 assert(a.length == 4096);
43 foreach (i, e; a[0..16])
44 assert(e == i);
45 cfree(a.ptr);
46 }
47
48
49 //TODO: should be in the standard library
50 struct STuple(A...)
51 {
52 static string genSTuple()
53 {
54 string r = "";
55 foreach (i, e; A)
56 r ~= A[i].stringof ~ " _" ~ ToString!(i) ~ ";";
57 return r;
58 }
59
60 mixin (genSTuple);
61 template at(size_t i) { mixin("alias _" ~ ToString!(i) ~ " at;"); };
62 }
63
64 void move(T)(ref T[] a, size_t src, size_t dest, size_t length)
65 {
66 if (a.length > 1)
67 memmove(a.ptr + dest, a.ptr + src, length * T.sizeof);
68 }
69
70 enum SignalEventId
71 {
72 firstSlotConnected,
73 lastSlotDisconnected
74 }
75
76 public class SignalException : Exception
77 {
78 this(string msg)
79 {
80 super(msg);
81 }
82 }
83
84 struct Fn
85 {
86 void* funcptr;
87
88 static typeof(this) opCall(R, A...)(R function(A) fn)
89 {
90 typeof(this) r;
91 r.funcptr = fn;
92 return r;
93 }
94
95 template call(R)
96 {
97 R call(A...)(A args)
98 {
99 alias R function(A) Fn;
100 return (cast(Fn)funcptr)(args);
101 }
102 }
103
104 S get(S)()
105 {
106 static assert (is(typeof(*S.init) == function));
107 return cast(S)funcptr;
108 }
109 }
110
111 struct Dg
112 {
113 void* context;
114 void* funcptr;
115
116 static typeof(this) opCall(R, A...)(R delegate(A) dg)
117 {
118 typeof(this) r;
119 r.context = dg.ptr;
120 r.funcptr = dg.funcptr;
121 return r;
122 }
123
124 template call(R)
125 {
126 R call(A...)(A args)
127 {
128 R delegate(A) dg; // BUG: parameter storage classes are ignored
129 dg.ptr = context;
130 dg.funcptr = cast(typeof(dg.funcptr))funcptr;
131 return dg(args);
132 }
133 }
134
135 S get(S)()
136 {
137 static assert (is(S == delegate));
138 S r;
139 r.ptr = context;
140 r.funcptr = cast(typeof(r.funcptr))funcptr;
141 return r;
142 }
143 }
144
145 struct Slot(R)
146 {
147 alias R Receiver;
148
149 Receiver receiver;
150 Dg invoker;
151
152 static if (is(Receiver == Dg))
153 {
154 static const isDelegate = true;
155
156 void onReceiverDisposed(Object o)
157 {
158 assert (lock !is null);
159 synchronized(lock)
160 {
161 receiver.context = null;
162 receiver.funcptr = null;
163 }
164 }
165
166 // null if receiver doesn't point to a disposable object
167 Object lock;
168
169 bool isDisposed()
170 {
171 return !receiver.funcptr;
172 }
173
174 Object getObject()
175 {
176 return lock ? _d_toObject(receiver.context) : null;
177 }
178 }
179 else
180 static const isDelegate = false;
181 }
182
183 enum SlotListId
184 {
185 Func, // function pointers
186 Weak, // object delegates stored in C heap
187 Strong // delegates stored in GC heap
188 }
189
190 /**
191 Used to specify the type of a signal-to-slot connection.
192
193 Examples:
194 ----
195 class Sender
196 {
197 mixin Signal!("changed");
198 void change()
199 {
200 changed.emit;
201 }
202 }
203
204
205 class Receiver
206 {
207 void alarm() {}
208 }
209
210 void main()
211 {
212 auto s = new Sender;
213 auto r = new Receiver;
214 s.changed.connect(&r.alarm); // now s weakly references r
215
216 r = null;
217 // collect garbage (assume there is no more reachable pointers
218 // to the receiver and it gets finalized)
219 ...
220
221 s.change;
222 // weak reference to the receiving object
223 // has been removed from the sender's connection lists.
224
225 r = new Receiver;
226 s.changed.connect(&r.alarm, ConnectionFlags.Strong);
227
228 r = null;
229 // collect garbage
230 ...
231 // the receiving object has not been finalized because s strongly references it.
232
233 s.change; // the receiver is called.
234 delete r;
235 s.change; // the receiver is disconnected from the sender.
236
237 static void foo()
238 {
239 }
240
241 s.changed.connect(&foo);
242 s.changed.emit; // foo is called.
243 s.changed.disconnect(&foo); // must be explicitly disconnected.
244
245 void bar()
246 {
247 }
248
249 // ConnectionFlags.NoObject must be specified for delegates
250 // to non-static local functions or struct member functions.
251 s.changed.connect(&bar, ConnectionFlags.NoObject);
252 s.changed.emit; // bar is called.
253 s.changed.disconnect(&bar); // must be explicitly disconnected.
254 }
255 ----
256 */
257 public enum ConnectionFlags
258 {
259 ///
260 None,
261 /**
262 The receiver will be stored as weak reference (implied if ConnectionFlags.NoObject is not specified).
263 If the signal receiver is not a function pointer or a delegate referencing a D class instance.
264 the sender will not be notified when the receiving object is deleted and emitting the signal
265 connected to that receiving object will result in undefined behavior.
266 */
267 Weak = 0x0001,
268 /**
269 The receiver is stored as strong reference (implied if ConnectionFlags.NoObject is specified).
270 */
271 Strong = 0x0002,
272 /**
273 Must be specified if the receiver is not a function pointer or a delegate referencing a D class instance.
274 */
275 NoObject = 0x0004
276
277 // Queued = 0x0004,
278 // BlockingQueued = 0x0008
279 }
280
281
282 struct SlotList(SlotT, bool strong = false)
283 {
284 alias SlotT SlotType;
285 SlotType[] data;
286
287 void length(size_t length)
288 {
289 static if (strong)
290 data.length = length;
291 else
292 realloc(data, length);
293 }
294
295 SlotType* add(SlotType slot)
296 {
297 auto oldLen = data.length;
298 length = oldLen + 1;
299 auto p = &data[oldLen];
300 *p = slot;
301 return p;
302 }
303
304 SlotType* get(int slotId)
305 {
306 return &data[slotId];
307 }
308
309 void remove(int slotId)
310 {
311 move(data, slotId, slotId + 1, data.length - slotId - 1);
312 data = data[0..$ - 1];
313 }
314
315 size_t length()
316 {
317 return data.length;
318 }
319
320 void free()
321 {
322 static if (SlotType.isDelegate)
323 {
324 foreach (ref slot; data)
325 {
326 if (auto obj = slot.getObject)
327 rt_detachDisposeEvent(obj, &slot.onReceiverDisposed);
328 }
329 }
330 static if (!strong)
331 cfree(data.ptr);
332 }
333 }
334
335 public alias void delegate(int signalId, SignalEventId event) SignalEvent;
336
337 struct SignalConnections
338 {
339 bool isInUse;
340
341 STuple!(
342 SlotList!(Slot!(Fn)),
343 SlotList!(Slot!(Dg)),
344 SlotList!(Slot!(Dg), true)
345 ) slotLists;
346
347 STuple!(
348 Fn[],
349 Dg[]
350 ) delayedDisconnects;
351
352 void addDelayedDisconnect(Fn r)
353 {
354 delayedDisconnects.at!(0) ~= r;
355 }
356
357 void addDelayedDisconnect(Dg r)
358 {
359 delayedDisconnects.at!(1) ~= r;
360 }
361
362 SlotListType!(slotListId)* getSlotList(int slotListId)()
363 {
364 return &slotLists.tupleof[slotListId];
365 }
366
367 bool hasSlots()
368 {
369 foreach(i, e; slotLists.tupleof)
370 {
371 if (slotLists.tupleof[i].length)
372 return true;
373 }
374 return false;
375 }
376
377 int slotCount()
378 {
379 int count;
380 foreach(i, e; slotLists.tupleof)
381 count += slotLists.at!(i).length;
382 return count;
383 }
384
385 void slotListLengths(int[] lengths)
386 {
387 foreach(i, e; slotLists.tupleof)
388 lengths[i] = slotLists.at!(i).length;
389 }
390
391 SlotType!(slotListId)* addSlot(int slotListId)(SlotType!(slotListId) slot)
392 {
393 return getSlotList!(slotListId).add(slot);
394 }
395
396 void removeSlot(int slotListId)(int slotId)
397 {
398 slotLists.at!(slotListId).remove(slotId);
399 }
400
401 void free()
402 {
403 foreach(i, e; slotLists.tupleof)
404 {
405 static if (is(typeof(slotLists.at!(i).free)))
406 slotLists.at!(i).free;
407 }
408 }
409
410 template SlotListType(int slotListId)
411 {
412 alias typeof(slotLists.tupleof)[slotListId] SlotListType;
413 }
414
415 template SlotType(int slotListId)
416 {
417 alias SlotListType!(slotListId).SlotType SlotType;
418 }
419
420 template ReceiverType(int slotListId)
421 {
422 alias SlotType!(slotListId).Receiver ReceiverType;
423 }
424
425 static const slotListCount = slotLists.tupleof.length;
426 }
427
428
429 Object signalSender;
430
431 public class SignalHandler
432 {
433 SignalConnections[] connections;
434 Object owner;
435 int blocked;
436
437 SignalEvent signalEvent;
438
439 alias SignalConnections.SlotType SlotType;
440 alias SignalConnections.ReceiverType ReceiverType;
441
442 public this(Object owner_) {
443 owner = owner_;
444 }
445
446 private SignalConnections* getConnections(int signalId)
447 {
448 if (signalId < connections.length)
449 return &connections[signalId];
450 return null;
451 }
452
453 private SlotType!(slotListId)* addSlot(int slotListId)(int signalId, ReceiverType!(slotListId) receiver,
454 Dg invoker)
455 {
456 if (signalId >= connections.length)
457 connections.length = signalId + 1;
458 auto slot = connections[signalId].addSlot!(slotListId)(SlotType!(slotListId)(receiver, invoker));
459
460 if (signalEvent && connections[signalId].slotCount == 1)
461 signalEvent(signalId, SignalEventId.firstSlotConnected);
462
463 return slot;
464 }
465
466 private void removeSlot(int slotListId)(int signalId, int slotId)
467 {
468 connections[signalId].removeSlot!(slotListId)(slotId);
469
470 if (signalEvent && !connections[signalId].slotCount)
471 signalEvent(signalId, SignalEventId.lastSlotDisconnected);
472 }
473
474 private SlotType!(slotListId)* addObjectSlot(int slotListId)(size_t signalId, Object obj, Dg receiver,
475 Dg invoker)
476 {
477 auto slot = addSlot!(slotListId)(signalId, receiver, invoker);
478 slot.lock = this;
479 rt_attachDisposeEvent(obj, &slot.onReceiverDisposed);
480 return slot;
481 }
482
483 size_t slotCount(int signalId)
484 {
485 synchronized(this)
486 {
487 auto con = getConnections(signalId);
488 if (con)
489 return con.slotCount;
490 return 0;
491 }
492 }
493
494 void connect(Receiver)(size_t signalId, Receiver receiver,
495 Dg invoker, ConnectionFlags flags)
496 {
497 synchronized(this)
498 {
499 static if (is(typeof(receiver.context)))
500 {
501 Object obj;
502 if ((flags & ConnectionFlags.NoObject) || (obj = _d_toObject(receiver.context)) is null)
503 {
504 // strong by default
505 if (flags & ConnectionFlags.Weak)
506 addSlot!(SlotListId.Weak)(signalId, receiver, invoker);
507 else
508 addSlot!(SlotListId.Strong)(signalId, receiver, invoker);
509 }
510 else
511 {
512 // weak by default
513 if (flags & ConnectionFlags.Strong)
514 addObjectSlot!(SlotListId.Strong)(signalId, obj, receiver, invoker);
515 else
516 addObjectSlot!(SlotListId.Weak)(signalId, obj, receiver, invoker);
517 }
518 }
519 else
520 addSlot!(SlotListId.Func)(signalId, receiver, invoker);
521 }
522 }
523
524 void disconnect(Receiver)(int signalId, Receiver receiver)
525 {
526 synchronized(this)
527 {
528 auto cons = getConnections(signalId);
529 if (!cons)
530 return;
531
532 // if called from a slot being executed by this signal, delay disconnection
533 // until all slots has been called.
534 if (cons.isInUse)
535 {
536 cons.addDelayedDisconnect(receiver);
537 return;
538 }
539
540 TOP:
541 foreach (slotListId, e; cons.slotLists.tupleof)
542 {
543 /// COMPILER BUG: ReceiverType is evaluated to expression instead of type.
544 static if (is(typeof(cons.ReceiverType!(slotListId)) == Receiver))
545 {
546 auto slotList = cons.getSlotList!(slotListId);
547 for (int slotId; slotId < slotList.length;)
548 {
549 auto slot = slotList.get(slotId);
550 static if (slot.isDelegate)
551 {
552 if (slot.isDisposed)
553 {
554 removeSlot!(slotListId)(signalId, slotId);
555 continue;
556 }
557 }
558
559 if (slot.receiver == receiver)
560 {
561 static if (slot.isDelegate)
562 {
563 if (auto obj = slot.getObject)
564 rt_detachDisposeEvent(obj, &slot.onReceiverDisposed);
565 }
566 removeSlot!(slotListId)(signalId, slotId);
567 break TOP;
568 }
569
570 slotId++;
571 }
572 }
573 }
574 }
575 }
576
577 void emit(A...)(size_t signalId, A args)
578 {
579 synchronized(this)
580 {
581 if (signalId >= connections.length || blocked)
582 return;
583 auto cons = &connections[signalId];
584
585 if (cons.hasSlots)
586 {
587 {
588 cons.isInUse = true;
589 signalSender = owner;
590 scope(exit)
591 {
592 cons.isInUse = false;
593 signalSender = null;
594 }
595
596 // Store the lengths to avoid calling new slots
597 // connected in the slots being called.
598 // dmd bug: int[cons.slotListCount] fails
599 static const c = cons.slotListCount;
600 int[c] lengths = void;
601 cons.slotListLengths(lengths);
602
603 foreach (slotListId, e; cons.slotLists.tupleof)
604 {
605 auto slotList = cons.getSlotList!(slotListId);
606 for (size_t slotId; slotId < lengths[slotListId];)
607 {
608 auto slot = slotList.get(slotId);
609 static if (slot.isDelegate)
610 {
611 if (slot.isDisposed)
612 {
613 removeSlot!(slotListId)(signalId, slotId);
614 lengths[slotListId]--;
615 continue;
616 }
617 }
618
619 slot.invoker.call!(void)(slot.receiver, args);
620 ++slotId;
621 }
622 }
623 }
624
625
626 // process delayed disconnects if any
627 foreach(i, e; cons.delayedDisconnects.tupleof)
628 {
629 if (cons.delayedDisconnects.at!(i).length)
630 {
631 foreach (d; cons.delayedDisconnects.at!(i))
632 disconnect(signalId, d);
633 cons.delayedDisconnects.at!(i).length = 0;
634 }
635 }
636 }
637 }
638 }
639
640 // Adjusts signal arguments and calls the slot. S - slot signature, A - signal arguments
641 private void invokeSlot(S, Receiver, A...)(Receiver r, A args)
642 {
643 r.get!(S)()(args[0..ParameterTypeTuple!(S).length]);
644 }
645
646 void blockSignals()
647 {
648 synchronized(this)
649 blocked++;
650 }
651
652 void unblockSignals()
653 {
654 synchronized(this)
655 {
656 if(!blocked)
657 throw new SignalException("Signals are not blocked");
658 blocked--;
659 }
660 }
661
662 ~this()
663 {
664 foreach(ref c; connections)
665 c.free;
666 }
667 }
668
669 //TODO: this could be avoided if named mixins didn't suck.
670 public struct SignalOps(int sigId, A...)
671 {
672 private SignalHandler sh;
673 enum { signalId = sigId }
674
675 void connect(R, B...)(R function(B) fn, ConnectionFlags flags = ConnectionFlags.None)
676 {
677 alias CheckSlot!(typeof(fn), A) check;
678 auto invoker = Dg(&sh.invokeSlot!(typeof(fn), Fn, A));
679 sh.connect(signalId, Fn(fn), invoker, flags);
680 }
681
682 void connect(R, B...)(R delegate(B) dg, ConnectionFlags flags = ConnectionFlags.None)
683 {
684 alias CheckSlot!(typeof(dg), A) check;
685 auto invoker = Dg(&sh.invokeSlot!(typeof(dg), Dg, A));
686 sh.connect(signalId, Dg(dg), invoker, flags);
687 }
688
689 void disconnect(R, B...)(R function(B) fn)
690 {
691 sh.disconnect(signalId, Fn(fn));
692 }
693
694 void disconnect(R, B...)(R delegate(B) dg)
695 {
696 sh.disconnect(signalId, Dg(dg));
697 }
698
699 void emit(A args)
700 {
701 sh.emit(signalId, args);
702 }
703
704 debug size_t slotCount()
705 {
706 return sh.slotCount(signalId);
707 }
708 }
709
710 template CheckSlot(Slot, A...)
711 {
712 static assert(ParameterTypeTuple!(Slot).length <= A.length, "Slot " ~ ParameterTypeTuple!(Slot).stringof ~
713 " has more prameters than signal " ~ A.stringof);
714 alias CheckSlotImpl!(Slot, 0, A) check;
715 }
716
717 template CheckSlotImpl(Slot, int i, A...)
718 {
719 alias ParameterTypeTuple!(Slot) SlotArgs;
720 static if (i < SlotArgs.length)
721 {
722 static assert (is(SlotArgs[i] : A[i]), "Argument " ~ ToString!(i) ~
723 ":" ~ A[i].stringof ~ " of signal " ~ A.stringof ~ " is not implicitly convertible to parameter "
724
725
726
727
728 ~ SlotArgs[i].stringof ~ " of slot " ~ SlotArgs.stringof);
729 alias CheckSlotImpl!(Slot, i + 1, A) next;
730 }
731 }
732
733 public template SignalHandlerOps()
734 {
735 static assert (is(typeof(this.signalHandler)),
736 "SignalHandlerOps is already instantiated in " ~ typeof(this).stringof ~ " or one of its base classes");
737
738 protected:
739 SignalHandler signalHandler_; // manages signal-to-slot connections
740
741 final SignalHandler signalHandler()
742 {
743 if (!signalHandler_)
744 {
745 signalHandler_ = new SignalHandler(this);
746 onSignalHandlerCreated(signalHandler_);
747 }
748 return signalHandler_;
749 }
750
751 void onSignalHandlerCreated(ref SignalHandler sh)
752 {
753 }
754
755 public:
756 final void blockSignals()
757 {
758 signalHandler.blockSignals();
759 }
760
761 final void unblockSignals()
762 {
763 signalHandler.unblockSignals();
764 }
765 }
766
767 /**
768 Examples:
769 ----
770 struct Args
771 {
772 bool cancel;
773 }
774
775 class C
776 {
777 private int _x;
778 // reference parameters are not supported yet,
779 // so we pass arguments by pointer.
780 mixin Signal!("xChanging", int, Args*);
781 mixin Signal!("xChanged");
782
783 void x(int v)
784 {
785 if (v != _x)
786 {
787 Args args;
788 xChanging.emit(v, &args);
789 if (!args.cancel)
790 {
791 _x = v;
792 xChanged.emit;
793 }
794 }
795 }
796 }
797 ----
798 */
799 template Signal(string name, A...)
800 {
801 mixin SignalImpl!(0, name, A);
802 }
803
804 template SignalImpl(int index, string name, A...)
805 {
806 static if (is(typeof(mixin(typeof(this).stringof ~ ".__sig" ~ ToString!(index)))))
807 mixin SignalImpl!(index + 1, name, A);
808 else
809 {
810 // mixed-in once
811 static if (!is(typeof(this.signalHandler)))
812 {
813 mixin SignalHandlerOps;
814 }
815 mixin("private static const int __sig" ~ ToString!(index) ~ " = " ~ ToString!(index) ~ ";");
816 mixin("SignalOps!(" ~ ToString!(index) ~ ", A) " ~ name ~ "(){ return SignalOps!("
817 ~ ToString!(index) ~ ", A)(signalHandler); }");
818 }
819 }
820
821 extern(C) alias void function(void*) SlotConnector;
822
823 debug (UnitTest)
824 {
825 class A
826 {
827 mixin Signal!("scorched", int);
828
829 int signalId1 = -1;
830 int signalId2 = -1;
831
832 void onFirstConnect(int sId)
833 {
834 signalId1 = sId;
835 }
836
837 void onLastDisconnect(int sId)
838 {
839 signalId2 = sId;
840 }
841
842 this()
843 {
844 signalHandler.firstSlotConnected = &onFirstConnect;
845 signalHandler.lastSlotDisconnected = &onLastDisconnect;
846 }
847 }
848
849 class B : A
850 {
851 mixin Signal!("booed", int);
852
853 int bazSum;
854 void baz(int i)
855 {
856 bazSum += i;
857 }
858 }
859
860 class C : A
861 {
862 mixin Signal!("cooked");
863 }
864 }
865
866 unittest
867 {
868 static int fooSum;
869 static int barSum;
870
871 static void foo(int i)
872 {
873 fooSum += i;
874 }
875
876 void bar(long i)
877 {
878 barSum += i;
879 }
880
881 auto a = new A;
882 auto b = new B;
883 auto c = new C;
884 assert(b.scorched.signalId == 0);
885 assert(b.booed.signalId == 1);
886 assert(c.cooked.signalId == 1);
887
888 auto sh = b.signalHandler;
889
890 b.scorched.connect(&foo);
891 assert(sh.connections.length == 1);
892 assert(b.signalId1 == 0);
893 auto scCons = &sh.connections[0];
894
895 assert(scCons.getSlotList!(SlotListId.Func).length == 1);
896 b.scorched.emit(1);
897 assert(fooSum == 1);
898
899 b.scorched.connect(&bar, ConnectionFlags.NoObject);
900 assert(sh.connections.length == 1);
901 assert(scCons.getSlotList!(SlotListId.Strong).length == 1);
902 b.scorched.emit(1);
903 assert (fooSum == 2 && barSum == 1);
904
905 b.scorched.connect(&b.baz);
906 assert(scCons.getSlotList!(SlotListId.Weak).length == 1);
907 b.scorched.emit(1);
908 assert (fooSum == 3 && barSum == 2 && b.bazSum == 1);
909
910 b.scorched.disconnect(&bar);
911 assert(scCons.slotCount == 2);
912 b.scorched.disconnect(&b.baz);
913 assert(scCons.slotCount == 1);
914 b.scorched.disconnect(&foo);
915 assert(scCons.slotCount == 0);
916 assert(b.signalId2 == 0);
917
918 fooSum = 0;
919 void connectFoo()
920 {
921 b.scorched.connect(&foo);
922 b.scorched.disconnect(&connectFoo);
923 }
924
925 b.scorched.connect(&connectFoo, ConnectionFlags.NoObject);
926 b.scorched.emit(1);
927 assert(scCons.getSlotList!(SlotListId.Func).length == 1);
928 assert(scCons.getSlotList!(SlotListId.Strong).length == 0);
929 assert(!fooSum);
930
931 auto r = new B();
932 b.scorched.connect(&r.baz);
933 assert(scCons.getSlotList!(SlotListId.Weak).length == 1);
934 b.scorched.emit(1);
935 assert(r.bazSum == 1);
936 assert(fooSum == 1);
937
938 delete(r);
939 assert(scCons.getSlotList!(SlotListId.Weak).length == 1);
940 b.scorched.emit(1);
941 assert(scCons.getSlotList!(SlotListId.Weak).length == 0);
942 }