comparison d1/qt/Signal.d @ 344:96a75b1e5b26

project structure changes
author Max Samukha <maxter@spambox.com>
date Fri, 14 May 2010 12:14:37 +0300
parents qt/d1/qt/Signal.d@1f6923c8cba0
children
comparison
equal deleted inserted replaced
343:552647ec0f82 344:96a75b1e5b26
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 public import qt.QGlobal;
15 import tango.core.Exception;
16 import tango.core.Traits;
17 import tango.core.Thread;
18 public import tango.core.Tuple;
19 import tango.stdc.stdlib : crealloc = realloc, cfree = free;
20 import tango.stdc.string : memmove;
21
22 private: // private by default
23
24 alias void delegate(Object) DEvent;
25
26 extern(C) void rt_attachDisposeEvent(Object o, DEvent e);
27 extern(C) void rt_detachDisposeEvent(Object o, DEvent e);
28 extern(C) Object _d_toObject(void* p);
29
30 void realloc(T)(ref T[] a, size_t length)
31 {
32 a = (cast(T*)crealloc(a.ptr, length * T.sizeof))[0..length];
33 if (!a.ptr)
34 new OutOfMemoryException(__FILE__, __LINE__);
35 }
36
37 void append(T)(ref T[] a, T element)
38 {
39 auto newLen = a.length + 1;
40 a = (cast(T*)crealloc(a.ptr, newLen * T.sizeof))[0..newLen];
41 if (!a.ptr)
42 new OutOfMemoryException(__FILE__, __LINE__);
43 a[newLen - 1] = element;
44 }
45
46 void move(T)(ref T[] a, size_t src, size_t dest, size_t length)
47 {
48 if (a.length > 1)
49 memmove(a.ptr + dest, a.ptr + src, length * T.sizeof);
50 }
51
52 // COMPILER BUG: Though this is private cannot name it 'remove' because of conflicts
53 // with Array.remove
54 void erase(T)(ref T[] a, size_t i)
55 {
56 auto newLen = a.length - 1;
57 move(a, i + 1, i, newLen);
58 realloc(a, newLen);
59 }
60
61 version (QtdUnittest)
62 {
63 unittest
64 {
65 int[] a;
66 realloc(a, 16);
67 assert(a.length == 16);
68 foreach (i, ref e; a)
69 e = i;
70 realloc(a, 4096);
71 assert(a.length == 4096);
72 foreach (i, e; a[0..16])
73 assert(e == i);
74 cfree(a.ptr);
75 }
76 }
77
78 // TODO: This one should be replaced with an appropriate library function
79 char[] __toString(long v)
80 {
81 if (v == 0)
82 return "0";
83
84 char[] ret;
85
86 bool neg;
87 if (v < 0)
88 {
89 neg = true;
90 v = -v;
91 }
92
93 while (v != 0)
94 {
95 ret = cast(char)(v % 10 + '0') ~ ret;
96 v = cast(long)(v / 10);
97 }
98
99 if (neg)
100 ret = "-" ~ ret;
101
102 return ret;
103 }
104
105 template ToString(long i)
106 {
107 const string ToString = __toString(i);
108 }
109
110 //TODO: should be in the standard library
111 struct STuple(A...)
112 {
113 static string genSTuple()
114 {
115 string r = "";
116 foreach (i, e; A)
117 r ~= A[i].stringof ~ " _" ~ ToString!(i) ~ ";";
118 return r;
119 }
120
121 mixin (genSTuple);
122 template at(size_t i) { mixin("alias _" ~ ToString!(i) ~ " at;"); };
123 }
124
125 enum SignalEventId
126 {
127 firstSlotConnected,
128 lastSlotDisconnected
129 }
130
131 public class SignalException : Exception
132 {
133 this(char[] msg)
134 {
135 super(msg);
136 }
137 }
138
139 struct Fn
140 {
141 void* funcptr;
142
143 static typeof(*this) opCall(R, A...)(R function(A) fn)
144 {
145 typeof(*this) r;
146 r.funcptr = fn;
147 return r;
148 }
149
150 template call(R)
151 {
152 R call(A...)(A args)
153 {
154 alias R function(A) Fn;
155 return (cast(Fn)funcptr)(args);
156 }
157 }
158
159 S get(S)()
160 {
161 static assert (is(typeof(*S.init) == function));
162 return cast(S)funcptr;
163 }
164 }
165
166 struct Dg
167 {
168 void* context;
169 void* funcptr;
170
171 static typeof(*this) opCall(R, A...)(R delegate(A) dg)
172 {
173 typeof(*this) r;
174 r.context = dg.ptr;
175 r.funcptr = dg.funcptr;
176 return r;
177 }
178
179 template call(R)
180 {
181 R call(A...)(A args)
182 {
183 R delegate(A) dg; // BUG: parameter storage classes are ignored
184 dg.ptr = context;
185 dg.funcptr = cast(typeof(dg.funcptr))funcptr;
186 return dg(args);
187 }
188 }
189
190 S get(S)()
191 {
192 static assert (is(S == delegate));
193 S r;
194 r.ptr = context;
195 r.funcptr = cast(typeof(r.funcptr))funcptr;
196 return r;
197 }
198 }
199
200 struct Slot(R)
201 {
202 alias R Receiver;
203
204 Receiver receiver;
205 Dg invoker;
206 ConnectionFlags flags;
207
208 static if (is(Receiver == Dg))
209 {
210 static const isDelegate = true;
211
212 bool isDisposed()
213 {
214 return !receiver.funcptr;
215 }
216
217 void dispose()
218 {
219 receiver.funcptr = null;
220 receiver.context = null;
221 }
222
223 Object getObject()
224 {
225 return flags & ConnectionFlags.NoObject || !receiver.context
226 ? null : _d_toObject(receiver.context);
227 }
228 }
229 else
230 static const isDelegate = false;
231 }
232
233 enum SlotListId
234 {
235 Func, // function pointers
236 Weak, // object delegates stored in C heap
237 Strong // delegates stored in GC heap
238 }
239
240 /**
241 Used to specify the type of a signal-to-slot connection.
242
243 Examples:
244 ----
245 class Sender
246 {
247 mixin Signal!("changed");
248 void change()
249 {
250 changed.emit;
251 }
252 }
253
254
255 class Receiver
256 {
257 void alarm() {}
258 }
259
260 void main()
261 {
262 auto s = new Sender;
263 auto r = new Receiver;
264 s.changed.connect(&r.alarm); // now s weakly references r
265
266 r = null;
267 // collect garbage (assume there is no more reachable pointers
268 // to the receiver and it gets finalized)
269 ...
270
271 s.change;
272 // weak reference to the receiving object
273 // has been removed from the sender's connection lists.
274
275 r = new Receiver;
276 s.changed.connect(&r.alarm, ConnectionFlags.Strong);
277
278 r = null;
279 // collect garbage
280 ...
281 // the receiving object has not been finalized because s strongly references it.
282
283 s.change; // the receiver is called.
284 delete r;
285 s.change; // the receiver is disconnected from the sender.
286
287 static void foo()
288 {
289 }
290
291 s.changed.connect(&foo);
292 s.changed.emit; // foo is called.
293 s.changed.disconnect(&foo); // must be explicitly disconnected.
294
295 void bar()
296 {
297 }
298
299 // ConnectionFlags.NoObject must be specified for delegates
300 // to non-static local functions or struct member functions.
301 s.changed.connect(&bar, ConnectionFlags.NoObject);
302 s.changed.emit; // bar is called.
303 s.changed.disconnect(&bar); // must be explicitly disconnected.
304 }
305 ----
306 */
307 public enum ConnectionFlags : ubyte
308 {
309 ///
310 None,
311 /**
312 The receiver will be stored as weak reference (implied if ConnectionFlags.NoObject is not specified).
313 If the signal receiver is not a function pointer or a delegate referencing a D class instance.
314 the sender will not be notified when the receiving object is deleted and emitting the signal
315 connected to that receiving object will result in undefined behavior.
316 */
317 Weak = 0x0001,
318 /**
319 The receiver is stored as strong reference (implied if ConnectionFlags.NoObject is specified).
320 */
321 Strong = 0x0002,
322 /**
323 Must be specified if the receiver is not a function pointer or a delegate referencing a D class instance.
324 */
325 NoObject = 0x0004
326
327 // Queued = 0x0004,
328 // BlockingQueued = 0x0008
329 }
330
331
332 struct SlotList(SlotT, bool strong = false)
333 {
334 alias SlotT SlotType;
335 SlotType[] data;
336
337 void length(size_t length)
338 {
339 static if (strong)
340 data.length = length;
341 else
342 realloc(data, length);
343 }
344
345 SlotType* add(SlotType slot)
346 {
347 auto oldLen = data.length;
348 length = oldLen + 1;
349 auto p = &data[oldLen];
350 *p = slot;
351 return p;
352 }
353
354 SlotType* get(int slotId)
355 {
356 return &data[slotId];
357 }
358
359 void remove(int slotId)
360 {
361 move(data, slotId, slotId + 1, data.length - slotId - 1);
362 data = data[0..$ - 1];
363 }
364
365 size_t length()
366 {
367 return data.length;
368 }
369
370 void free()
371 {
372 static if (!strong)
373 cfree(data.ptr);
374 }
375 }
376
377 public alias void delegate(int signalId, SignalEventId event) SignalEvent;
378
379 struct Receivers
380 {
381 struct Data
382 {
383 Object object;
384 int refs;
385 }
386
387 Data[] data;
388 void add(Object receiver, DEvent disposeEvent)
389 {
390 foreach (ref d; data)
391 {
392 if (d.object is receiver)
393 {
394 d.refs++;
395 return;
396 }
397 }
398
399 append(data, Data(receiver, 1));
400 rt_attachDisposeEvent(receiver, disposeEvent);
401 }
402
403 void remove(Object receiver, DEvent disposeEvent)
404 {
405 foreach (i, ref d; data)
406 {
407 if (d.object is receiver)
408 {
409 assert (d.refs);
410 d.refs--;
411 if (!d.refs)
412 {
413 .erase(data, i);
414 rt_detachDisposeEvent(receiver, disposeEvent);
415 }
416 return;
417 }
418 }
419
420 assert (false);
421 }
422
423 // remove all refarences for receiver, receiver has been disposed
424 void removeAll(Object receiver)
425 {
426 foreach (i, ref d; data)
427 {
428 if (d.object is receiver)
429 {
430 .erase(data, i);
431 return;
432 }
433 }
434 }
435
436 // remove all references for all receivers, detaching dispose events
437 void free(DEvent disposeEvent)
438 {
439 foreach (i, ref d; data)
440 rt_detachDisposeEvent(d.object, disposeEvent);
441 cfree(data.ptr);
442 data = null;
443 }
444 }
445
446 struct SignalConnections
447 {
448 bool isInUse;
449
450 STuple!(
451 SlotList!(Slot!(Fn)),
452 SlotList!(Slot!(Dg)),
453 SlotList!(Slot!(Dg), true)
454 ) slotLists;
455
456 STuple!(
457 Fn[],
458 Dg[]
459 ) delayedDisconnects;
460
461 void addDelayedDisconnect(Fn r)
462 {
463 delayedDisconnects.at!(0) ~= r;
464 }
465
466 void addDelayedDisconnect(Dg r)
467 {
468 delayedDisconnects.at!(1) ~= r;
469 }
470
471 SlotListType!(slotListId)* getSlotList(int slotListId)()
472 {
473 return &slotLists.tupleof[slotListId];
474 }
475
476 bool hasSlots()
477 {
478 foreach(i, e; slotLists.tupleof)
479 {
480 if (slotLists.tupleof[i].length)
481 return true;
482 }
483 return false;
484 }
485
486 int slotCount()
487 {
488 int count;
489 foreach(i, e; slotLists.tupleof)
490 count += slotLists.at!(i).length;
491 return count;
492 }
493
494 void slotListLengths(int[] lengths)
495 {
496 foreach(i, e; slotLists.tupleof)
497 lengths[i] = slotLists.at!(i).length;
498 }
499
500 SlotType!(slotListId)* addSlot(int slotListId)(SlotType!(slotListId) slot)
501 {
502 return getSlotList!(slotListId).add(slot);
503 }
504
505 void removeSlot(int slotListId)(int slotId)
506 {
507 slotLists.at!(slotListId).remove(slotId);
508 }
509
510 void free()
511 {
512 foreach(i, e; slotLists.tupleof)
513 {
514 static if (is(typeof(slotLists.at!(i).free)))
515 slotLists.at!(i).free;
516 }
517 }
518
519 void onReceiverDisposed(Object receiver)
520 {
521 foreach (i, e; slotLists.tupleof)
522 {
523 static if (slotLists.at!(i).SlotType.isDelegate)
524 {
525 foreach (ref slot; slotLists.at!(i).data)
526 {
527 if (slot.getObject is receiver)
528 slot.dispose;
529 }
530 }
531 }
532 }
533
534 template SlotListType(int slotListId)
535 {
536 alias typeof(slotLists.tupleof)[slotListId] SlotListType;
537 }
538
539 template SlotType(int slotListId)
540 {
541 alias SlotListType!(slotListId).SlotType SlotType;
542 }
543
544 template ReceiverType(int slotListId)
545 {
546 alias SlotType!(slotListId).Receiver ReceiverType;
547 }
548
549 static const slotListCount = slotLists.tupleof.length;
550 }
551
552
553 private ThreadLocal!(Object) signalSender_;
554 static this()
555 {
556 signalSender_ = new ThreadLocal!(Object);
557 }
558
559 /**
560 If called from a slot, returns the object
561 that is emitting the signal. Otherwise, returns null.
562 */
563 public Object signalSender() {
564 return signalSender_.val;
565 }
566
567 public final class SignalHandler
568 {
569 SignalConnections[] connections;
570 Receivers receivers;
571 Object owner;
572 int blocked;
573
574 SignalEvent signalEvent;
575
576 alias SignalConnections.SlotType SlotType;
577 alias SignalConnections.ReceiverType ReceiverType;
578
579 public this(Object owner_) {
580 owner = owner_;
581 }
582
583 private SignalConnections* getConnections(int signalId)
584 {
585 if (signalId < connections.length)
586 return &connections[signalId];
587 return null;
588 }
589
590 private SlotType!(slotListId)* addSlot(int slotListId)(int signalId, ReceiverType!(slotListId) receiver,
591 Dg invoker, ConnectionFlags flags)
592 {
593 if (signalId >= connections.length)
594 connections.length = signalId + 1;
595 auto slot = connections[signalId].addSlot!(slotListId)(SlotType!(slotListId)(receiver, invoker, flags));
596
597 static if (slot.isDelegate)
598 {
599 if (!(flags & ConnectionFlags.NoObject))
600 receivers.add(_d_toObject(receiver.context), &onReceiverDisposed);
601 }
602
603 if (signalEvent && connections[signalId].slotCount == 1)
604 signalEvent(signalId, SignalEventId.firstSlotConnected);
605
606 return slot;
607 }
608
609 void onReceiverDisposed(Object receiver)
610 {
611 synchronized(this)
612 {
613 foreach(ref c; connections)
614 c.onReceiverDisposed(receiver);
615 receivers.removeAll(receiver);
616 }
617 }
618
619 private void removeSlot(int slotListId)(int signalId, int slotId)
620 {
621 auto slot = connections[signalId].getSlotList!(slotListId).get(slotId);
622 static if (slot.isDelegate)
623 {
624 if (auto obj = slot.getObject)
625 receivers.remove(obj, &onReceiverDisposed);
626 }
627
628 connections[signalId].removeSlot!(slotListId)(slotId);
629
630 if (signalEvent && !connections[signalId].slotCount)
631 signalEvent(signalId, SignalEventId.lastSlotDisconnected);
632 }
633
634
635 size_t slotCount(int signalId)
636 {
637 synchronized(this)
638 {
639 auto con = getConnections(signalId);
640 if (con)
641 return con.slotCount;
642 return 0;
643 }
644 }
645
646 void connect(Receiver)(int signalId, Receiver receiver,
647 Dg invoker, ConnectionFlags flags)
648 {
649 synchronized(this)
650 {
651 static if (is(typeof(receiver.context)))
652 {
653 Object obj;
654 if ((flags & ConnectionFlags.NoObject))
655 {
656 // strong by default
657 if (flags & ConnectionFlags.Weak)
658 addSlot!(SlotListId.Weak)(signalId, receiver, invoker, flags);
659 else
660 addSlot!(SlotListId.Strong)(signalId, receiver, invoker, flags);
661 }
662 else
663 {
664 // weak by default
665 if (flags & ConnectionFlags.Strong)
666 addSlot!(SlotListId.Strong)(signalId, receiver, invoker, flags);
667 else
668 addSlot!(SlotListId.Weak)(signalId, receiver, invoker, flags);
669 }
670 }
671 else
672 {
673 flags |= ConnectionFlags.NoObject;
674 addSlot!(SlotListId.Func)(signalId, receiver, invoker, flags);
675 }
676 }
677 }
678
679 void disconnect(Receiver)(int signalId, Receiver receiver)
680 {
681 synchronized(this)
682 {
683 auto cons = getConnections(signalId);
684 if (!cons)
685 return;
686
687 // if called from a slot being executed by this signal, delay disconnection
688 // until all slots has been called.
689 if (cons.isInUse)
690 {
691 cons.addDelayedDisconnect(receiver);
692 return;
693 }
694
695 TOP:
696 foreach (slotListId, e; cons.slotLists.tupleof)
697 {
698 /// COMPILER BUG: ReceiverType is evaluated to expression instead of type.
699 static if (is(typeof(cons.ReceiverType!(slotListId)) == Receiver))
700 {
701 auto slotList = cons.getSlotList!(slotListId);
702 for (int slotId; slotId < slotList.length;)
703 {
704 auto slot = slotList.get(slotId);
705 static if (slot.isDelegate)
706 {
707 if (slot.isDisposed)
708 {
709 removeSlot!(slotListId)(signalId, slotId);
710 continue;
711 }
712 }
713
714 if (slot.receiver == receiver)
715 {
716 removeSlot!(slotListId)(signalId, slotId);
717 break TOP;
718 }
719
720 slotId++;
721 }
722 }
723 }
724 }
725 }
726
727 void emit(A...)(size_t signalId, A args)
728 {
729 synchronized(this)
730 {
731 if (signalId >= connections.length || blocked)
732 return;
733 auto cons = &connections[signalId];
734
735 if (cons.hasSlots)
736 {
737 {
738 cons.isInUse = true;
739 signalSender_.val = owner;
740 scope(exit)
741 {
742 cons.isInUse = false;
743 signalSender_.val = null;
744 }
745
746 // Store the lengths to avoid calling new slots
747 // connected in the slots being called.
748 // dmd bug: int[cons.slotListCount] fails
749 static const c = cons.slotListCount;
750 int[c] lengths = void;
751 cons.slotListLengths(lengths);
752
753 foreach (slotListId, e; cons.slotLists.tupleof)
754 {
755 auto slotList = cons.getSlotList!(slotListId);
756 for (size_t slotId; slotId < lengths[slotListId];)
757 {
758 auto slot = slotList.get(slotId);
759 static if (slot.isDelegate)
760 {
761 if (slot.isDisposed)
762 {
763 removeSlot!(slotListId)(signalId, slotId);
764 lengths[slotListId]--;
765 continue;
766 }
767 }
768
769 slot.invoker.call!(void)(slot.receiver, args);
770 ++slotId;
771 }
772 }
773 }
774
775
776 // process delayed disconnects if any
777 foreach(i, e; cons.delayedDisconnects.tupleof)
778 {
779 if (cons.delayedDisconnects.at!(i).length)
780 {
781 foreach (d; cons.delayedDisconnects.at!(i))
782 disconnect(signalId, d);
783 cons.delayedDisconnects.at!(i).length = 0;
784 }
785 }
786 }
787 }
788 }
789
790 // Adjusts signal arguments and calls the slot. S - slot signature, A - signal arguments
791 private void invokeSlot(S, Receiver, A...)(Receiver r, A args)
792 {
793 r.get!(S)()(args[0..ParameterTupleOf!(S).length]);
794 }
795
796 void blockSignals()
797 {
798 synchronized(this)
799 blocked++;
800 }
801
802 void unblockSignals()
803 {
804 synchronized(this)
805 {
806 if(!blocked)
807 throw new SignalException("Signals are not blocked");
808 blocked--;
809 }
810 }
811
812 ~this()
813 {
814 receivers.free(&onReceiverDisposed);
815 foreach(ref c; connections)
816 c.free;
817 }
818 }
819
820 public template SignalHandlerOps()
821 {
822 static assert (is(typeof(this.signalHandler)),
823 "SignalHandlerOps is already instantiated in " ~ typeof(this).stringof ~ " or one of its base classes");
824
825 protected:
826 SignalHandler signalHandler_; // manages signal-to-slot connections
827
828 final SignalHandler signalHandler()
829 {
830 if (!signalHandler_)
831 {
832 signalHandler_ = new SignalHandler(this);
833 onSignalHandlerCreated(signalHandler_);
834 }
835 return signalHandler_;
836 }
837
838 void onSignalHandlerCreated(ref SignalHandler sh)
839 {
840 }
841
842 public:
843 final void blockSignals()
844 {
845 signalHandler.blockSignals();
846 }
847
848 final void unblockSignals()
849 {
850 signalHandler.unblockSignals();
851 }
852
853 template connect(string signalName, A...)
854 {
855 static void connect(T, Func)(T sender, Func func, ConnectionFlags flags = ConnectionFlags.None)
856 {
857 static if (isFnOrDg!(Func)) // D1 has no constraints
858 {
859 alias findSignal!(T, signalName, Func, A).result sig;
860 auto sh = sender.signalHandler();
861 static if (isFn!(Func))
862 alias Fn Callable;
863 else
864 alias Dg Callable;
865 auto invoker = Dg(&sh.invokeSlot!(typeof(func), Callable, sig[2..$]));
866 sh.connect(sig[1], Callable(func), invoker, flags);
867 }
868 else
869 {
870 static assert(false, "The slot must be a function or delegate type.");
871 }
872 }
873 }
874
875 template disconnect(string signalName, A...)
876 {
877 static void connect(T, Func)(T sender, Func func, ConnectionFlags flags = ConnectionFlags.None)
878 {
879 static if (isFnOrDg!(Func)) // D1 has no constraints
880 {
881 alias findSignal!(T, signalName, Func, A).result sig;
882 auto sh = sender.signalHandler();
883 static if (isFn!(Func))
884 alias Fn Callable;
885 else
886 alias Dg Callable;
887 sh.disconnect(sig[1], Callable(func));
888 }
889 else
890 {
891 static assert(false, "The slot must be a function or delegate type.");
892 }
893 }
894 }
895 /*
896 template slotCount(string signalName, A...)
897 {
898 debug static void slotCount(T)(T sender)
899 {
900 alias findSignal!(T, signalName, Func, A).result sig;
901 auto sh = sender.signalHandler();
902 return sh.slotCount(sig[1]);
903 }
904 }
905 */
906 }
907
908 /**
909 New implementation.
910 */
911
912 const string signalPrefix = "__signal";
913
914 template TupleWrapper(A...) { alias A at; }
915
916 template isDg(Dg)
917 {
918 enum { isDg = is(Dg == delegate) }
919 }
920
921 template isFn(Fn)
922 {
923 enum { isFn = is(typeof(*Fn.init) == function) }
924 }
925
926 template isFnOrDg(Dg)
927 {
928 enum { isFnOrDg = isFn!(Dg) || isDg!(Dg) }
929 }
930
931 string joinArgs(A...)()
932 {
933 string res = "";
934 static if(A.length)
935 {
936 res = A[0].stringof;
937 foreach(k; A[1..$])
938 res ~= "," ~ k.stringof;
939 }
940 return res;
941 }
942
943 template SlotPred(T1, T2)
944 {
945 enum { SlotPred = is(T1 : T2) }
946 }
947
948 template CheckSlot(alias Needle, alias Source)
949 {
950 static if(Needle.at.length <= Source.at.length)
951 enum { CheckSlot = CheckArgs!(Needle, Source, SlotPred, 0).value }
952 else
953 enum { CheckSlot = false }
954 }
955
956 template SignalPred(T1, T2)
957 {
958 enum { SignalPred = is(T1 == T2) }
959 }
960
961 template CheckSignal(alias Needle, alias Source)
962 {
963 static if(Needle.at.length == Source.at.length)
964 enum { CheckSignal = CheckArgs!(Needle, Source, SignalPred, 0).value }
965 else
966 enum { CheckSignal = false }
967 }
968
969 template CheckArgs(alias Needle, alias Source, alias pred, int i)
970 {
971 static if (i < Needle.at.length)
972 {
973 static if (pred!(Needle.at[i], Source.at[i]))
974 enum { value = CheckArgs!(Needle, Source, pred, i + 1).value }
975 else
976 enum { value = false }
977 }
978 else
979 {
980 enum { value = true }
981 }
982 }
983
984 template SigByNamePred(string name, SlotArgs...)
985 {
986 template SigByNamePred(source...)
987 {
988 static if (source[0] == name) // only instantiate CheckSlot if names match
989 enum { SigByNamePred = CheckSlot!(TupleWrapper!(SlotArgs), TupleWrapper!(source[2 .. $])) }
990 else
991 enum { SigByNamePred = false }
992 }
993 }
994
995 template SigBySignPred(string name, SigArgs...)
996 {
997 template SigBySignPred(source...)
998 {
999 static if (source[0] == name) // only instantiate CheckSignal if names match
1000 enum { SigBySignPred = CheckSignal!(TupleWrapper!(SigArgs), TupleWrapper!(source[2 .. $])) }
1001 else
1002 enum { SigBySignPred = false }
1003 }
1004 }
1005
1006 template staticSymbolName(string prefix, int id)
1007 {
1008 const string staticSymbolName = prefix ~ ToString!(id);
1009 }
1010
1011 template signatureString(string name, A...)
1012 {
1013 const string signatureString = name ~ "(" ~ joinArgs!(A) ~ ")";
1014 }
1015
1016 template findSymbolImpl(string prefix, C, int id, alias pred)
1017 {
1018 static if ( is(typeof(mixin("C." ~ staticSymbolName!(prefix, id)))) )
1019 {
1020 mixin ("alias C." ~ staticSymbolName!(prefix, id) ~ " current;");
1021 static if (pred!(current))
1022 alias current result;
1023 else
1024 alias findSymbolImpl!(prefix, C, id + 1, pred).result result;
1025 }
1026 else
1027 {
1028 alias void result;
1029 }
1030 }
1031
1032 template findSymbol(string prefix, C, alias pred)
1033 {
1034 alias findSymbolImpl!(prefix, C, 0, pred).result findSymbol;
1035 }
1036
1037 template findSignal(C, string name, Receiver, SigArgs...)
1038 {
1039 alias TupleWrapper!(ParameterTupleOf!(Receiver)) SlotArgsWr;
1040 static if (SigArgs.length > 0)
1041 {
1042 alias findSymbol!(signalPrefix, C, SigBySignPred!(name, SigArgs)) result;
1043 static if (is(result == void))
1044 static assert(0, "Signal " ~ name ~ "(" ~ joinArgs!(SigArgs)() ~ ") was not found.");
1045 else
1046 static if (!CheckSlot!(SlotArgsWr, TupleWrapper!(result[2 .. $])))
1047 static assert(0, "Signature of slot is incompatible with signal " ~ name ~ ".");
1048 }
1049 else
1050 {
1051 alias findSymbol!(signalPrefix, C, SigByNamePred!(name, SlotArgsWr.at)) result;
1052 static if (is(result == void))
1053 static assert(0, "Signal " ~ name ~ " was not found.");
1054 }
1055 }
1056
1057
1058 public string SignalEmitter(A...)(SignalType signalType, string name, int index)
1059 {
1060 string fullArgs, args;
1061 static if (A.length)
1062 {
1063 fullArgs = A[0].stringof ~ " a0";
1064 args = ", a0";
1065 foreach(i, _; A[1..$])
1066 {
1067 fullArgs ~= ", " ~ A[i+1].stringof ~ " a" ~ __toString(i+1);
1068 args ~= ", a" ~ __toString(i+1);
1069 }
1070 }
1071 string attribute;
1072 string sigName = name;
1073 if (signalType == SignalType.BindQtSignal)
1074 name ~= "_emit";
1075 else
1076 attribute = "protected ";
1077 string str = attribute ~ "void " ~ name ~ "(" ~ fullArgs ~ ")" ~
1078 "{ this.signalHandler.emit(" ~ __toString(index) ~ args ~ "); }";
1079 return str;
1080 }
1081
1082 /**
1083 Examples:
1084 ----
1085 struct Args
1086 {
1087 bool cancel;
1088 }
1089
1090 class C
1091 {
1092 private int _x;
1093 // reference parameters are not supported yet,
1094 // so we pass arguments by pointer.
1095 mixin Signal!("xChanging", int, Args*);
1096 mixin Signal!("xChanged");
1097
1098 void x(int v)
1099 {
1100 if (v != _x)
1101 {
1102 Args args;
1103 xChanging.emit(v, &args);
1104 if (!args.cancel)
1105 {
1106 _x = v;
1107 xChanged.emit;
1108 }
1109 }
1110 }
1111 }
1112 ----
1113 */
1114
1115 enum SignalType
1116 {
1117 BindQtSignal,
1118 NewSignal
1119 }
1120
1121 template BindQtSignal(string name, A...)
1122 {
1123 mixin SignalImpl!(0, name, SignalType.BindQtSignal, A);
1124 }
1125
1126 template Signal(string name, A...)
1127 {
1128 mixin SignalImpl!(0, name, SignalType.NewSignal, A);
1129 }
1130
1131 template SignalImpl(int index, string name, SignalType signalType, A...)
1132 {
1133 static if (is(typeof(mixin(typeof(this).stringof ~ ".__signal" ~ ToString!(index)))))
1134 mixin SignalImpl!(index + 1, name, signalType, A);
1135 else
1136 {
1137 // mixed-in once
1138 static if (!is(typeof(this.signalHandler)))
1139 mixin SignalHandlerOps;
1140
1141 mixin (SignalEmitter!(A)(signalType, name, index));
1142 mixin("public alias Tuple!(\"" ~ name ~ "\", index, A) __signal" ~ ToString!(index) ~ ";");
1143 }
1144 }
1145
1146 extern(C) alias void function(void*) SlotConnector;
1147
1148 debug (UnitTest)
1149 {
1150 class A
1151 {
1152 mixin Signal!("scorched", int);
1153
1154 int signalId1 = -1;
1155 int signalId2 = -1;
1156
1157 void onFirstConnect(int sId)
1158 {
1159 signalId1 = sId;
1160 }
1161
1162 void onLastDisconnect(int sId)
1163 {
1164 signalId2 = sId;
1165 }
1166
1167 this()
1168 {
1169 signalHandler.firstSlotConnected = &onFirstConnect;
1170 signalHandler.lastSlotDisconnected = &onLastDisconnect;
1171 }
1172 }
1173
1174 class B : A
1175 {
1176 mixin Signal!("booed", int);
1177
1178 int bazSum;
1179 void baz(int i)
1180 {
1181 bazSum += i;
1182 }
1183 }
1184
1185 class C : A
1186 {
1187 mixin Signal!("cooked");
1188 }
1189 }
1190
1191 unittest
1192 {
1193 static int fooSum;
1194 static int barSum;
1195
1196 static void foo(int i)
1197 {
1198 fooSum += i;
1199 }
1200
1201 void bar(long i)
1202 {
1203 barSum += i;
1204 }
1205
1206 auto a = new A;
1207 auto b = new B;
1208 auto c = new C;
1209 assert(b.scorched.signalId == 0);
1210 assert(b.booed.signalId == 1);
1211 assert(c.cooked.signalId == 1);
1212
1213 auto sh = b.signalHandler;
1214
1215 b.scorched.connect(&foo);
1216 assert(sh.connections.length == 1);
1217 assert(b.signalId1 == 0);
1218 auto scCons = &sh.connections[0];
1219
1220 assert(scCons.getSlotList!(SlotListId.Func).length == 1);
1221 b.scorched.emit(1);
1222 assert(fooSum == 1);
1223
1224 b.scorched.connect(&bar, ConnectionFlags.NoObject);
1225 assert(sh.connections.length == 1);
1226 assert(scCons.getSlotList!(SlotListId.Strong).length == 1);
1227 b.scorched.emit(1);
1228 assert (fooSum == 2 && barSum == 1);
1229
1230 b.scorched.connect(&b.baz);
1231 assert(scCons.getSlotList!(SlotListId.Weak).length == 1);
1232 b.scorched.emit(1);
1233 assert (fooSum == 3 && barSum == 2 && b.bazSum == 1);
1234
1235 b.scorched.disconnect(&bar);
1236 assert(scCons.slotCount == 2);
1237 b.scorched.disconnect(&b.baz);
1238 assert(scCons.slotCount == 1);
1239 b.scorched.disconnect(&foo);
1240 assert(scCons.slotCount == 0);
1241 assert(b.signalId2 == 0);
1242
1243 fooSum = 0;
1244 void connectFoo()
1245 {
1246 b.scorched.connect(&foo);
1247 b.scorched.disconnect(&connectFoo);
1248 }
1249
1250 b.scorched.connect(&connectFoo, ConnectionFlags.NoObject);
1251 b.scorched.emit(1);
1252 assert(scCons.getSlotList!(SlotListId.Func).length == 1);
1253 assert(scCons.getSlotList!(SlotListId.Strong).length == 0);
1254 assert(!fooSum);
1255
1256 auto r = new B();
1257 b.scorched.connect(&r.baz);
1258 assert(scCons.getSlotList!(SlotListId.Weak).length == 1);
1259 b.scorched.emit(1);
1260 assert(r.bazSum == 1);
1261 assert(fooSum == 1);
1262
1263 delete(r);
1264 assert(scCons.getSlotList!(SlotListId.Weak).length == 1);
1265 b.scorched.emit(1);
1266 assert(scCons.getSlotList!(SlotListId.Weak).length == 0);
1267 }