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