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