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