Mercurial > projects > qtd
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 } |