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