Mercurial > projects > qtd
comparison qt/d2/qt/Signal.d @ 288:f9559a957be9 signals
new signals and slots implementation
author | eldar |
---|---|
date | Sun, 08 Nov 2009 19:28:01 +0000 |
parents | 1f6923c8cba0 |
children | c9d1aac290e9 |
comparison
equal
deleted
inserted
replaced
287:b6984b290e46 | 288:f9559a957be9 |
---|---|
10 * | 10 * |
11 */ | 11 */ |
12 module qt.Signal; | 12 module qt.Signal; |
13 | 13 |
14 public import qt.QGlobal; | 14 public import qt.QGlobal; |
15 public import | 15 import qt.qtd.MetaMarshall; |
16 std.metastrings, | 16 |
17 std.typetuple; | |
18 import core.stdc.stdlib : crealloc = realloc, cfree = free; | 17 import core.stdc.stdlib : crealloc = realloc, cfree = free; |
19 import core.stdc.string : memmove; | 18 import core.stdc.string : memmove; |
20 import | 19 import |
20 core.thread, | |
21 core.exception, | |
22 std.algorithm; | |
23 | |
24 public import | |
25 std.typetuple, | |
21 std.traits, | 26 std.traits, |
22 core.thread, | 27 std.conv, |
23 core.exception; | 28 std.string, |
24 | 29 std.metastrings; |
25 private: // private by default | 30 |
26 | 31 |
27 alias void delegate(Object) DEvent; | 32 // returns name, arguments or tuple of the function depending on type parameter |
28 | 33 enum {_Name, _Tuple, _Args} |
29 extern(C) void rt_attachDisposeEvent(Object o, DEvent e); | 34 string getFunc(int type)(string fullName) |
30 extern(C) void rt_detachDisposeEvent(Object o, DEvent e); | 35 { |
31 extern(C) Object _d_toObject(void* p); | 36 int pos = 0; |
32 | 37 foreach(i, c; fullName) |
33 void realloc(T)(ref T[] a, size_t length) | 38 if (c == '(') |
34 { | 39 static if (type == _Tuple) |
35 a = (cast(T*)crealloc(a.ptr, length * T.sizeof))[0..length]; | 40 return fullName[i..$]; |
36 if (!a.ptr) | 41 else if (type == _Name) |
37 new OutOfMemoryError(__FILE__, __LINE__); | 42 return fullName[0..i]; |
38 } | 43 else if (type == _Args) |
39 | 44 for(int j = fullName.length-1;; j--) |
40 | 45 if(fullName[j] == ')') |
41 void append(T)(ref T[] a, T element) | 46 return fullName[i+1 .. j]; |
42 { | 47 return null; |
43 auto newLen = a.length + 1; | 48 } |
44 a = (cast(T*)crealloc(a.ptr, newLen * T.sizeof))[0..newLen]; | 49 |
45 if (!a.ptr) | 50 /** The beast that takes string representation of function arguments |
46 new OutOfMemoryError(__FILE__, __LINE__); | 51 * and returns an array of default values it doesn't check if arguments |
47 a[newLen - 1] = element; | 52 * without default values follow the arguments with default values for |
48 } | 53 * simplicity. It is done by mixing in an delegate alias. |
49 | 54 */ |
50 void move(T)(ref T[] a, size_t src, size_t dest, size_t length) | 55 string[] defaultValues(string signature) |
51 { | 56 { |
52 if (a.length > 1) | 57 int braces = 0; |
53 memmove(a.ptr + dest, a.ptr + src, length * T.sizeof); | 58 bool inDefaultValue = false; |
54 } | 59 bool inStringLiteral = false; |
55 | 60 string[] res; |
56 // COMPILER BUG: Though this is private cannot name it 'remove' because of conflicts | 61 int startValue = 0; |
57 // with Array.remove | 62 |
58 void erase(T)(ref T[] a, size_t i) | 63 if(strip(signature).length == 0) |
59 { | 64 return res; |
60 auto newLen = a.length - 1; | 65 |
61 move(a, i + 1, i, newLen); | 66 foreach (i,c; signature) |
62 realloc(a, newLen); | 67 { |
63 } | 68 if(!inStringLiteral) |
64 | |
65 version (QtdUnittest) | |
66 { | |
67 unittest | |
68 { | |
69 int[] a; | |
70 realloc(a, 16); | |
71 assert(a.length == 16); | |
72 foreach (i, ref e; a) | |
73 e = i; | |
74 realloc(a, 4096); | |
75 assert(a.length == 4096); | |
76 foreach (i, e; a[0..16]) | |
77 assert(e == i); | |
78 cfree(a.ptr); | |
79 } | |
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 enum SignalEventId | |
98 { | |
99 firstSlotConnected, | |
100 lastSlotDisconnected | |
101 } | |
102 | |
103 public class SignalException : Exception | |
104 { | |
105 this(string msg) | |
106 { | |
107 super(msg); | |
108 } | |
109 } | |
110 | |
111 struct Fn | |
112 { | |
113 void* funcptr; | |
114 | |
115 static typeof(this) opCall(R, A...)(R function(A) fn) | |
116 { | |
117 typeof(this) r; | |
118 r.funcptr = fn; | |
119 return r; | |
120 } | |
121 | |
122 template call(R) | |
123 { | |
124 R call(A...)(A args) | |
125 { | 69 { |
126 alias R function(A) Fn; | 70 if(c == '{' || c =='(') |
127 return (cast(Fn)funcptr)(args); | 71 braces++; |
72 else if(c == '}' || c ==')') | |
73 braces--; | |
128 } | 74 } |
129 } | 75 |
130 | 76 if(c == '\"' || c == '\'') |
131 S get(S)() | |
132 { | |
133 static assert (is(typeof(*S.init) == function)); | |
134 return cast(S)funcptr; | |
135 } | |
136 } | |
137 | |
138 struct Dg | |
139 { | |
140 void* context; | |
141 void* funcptr; | |
142 | |
143 static typeof(this) opCall(R, A...)(R delegate(A) dg) | |
144 { | |
145 typeof(this) r; | |
146 r.context = dg.ptr; | |
147 r.funcptr = dg.funcptr; | |
148 return r; | |
149 } | |
150 | |
151 template call(R) | |
152 { | |
153 R call(A...)(A args) | |
154 { | 77 { |
155 R delegate(A) dg; // BUG: parameter storage classes are ignored | 78 if (inStringLiteral) |
156 dg.ptr = context; | 79 { |
157 dg.funcptr = cast(typeof(dg.funcptr))funcptr; | 80 if(signature[i-1] != '\\') |
158 return dg(args); | 81 inStringLiteral = false; |
159 } | 82 } |
160 } | 83 else |
161 | 84 { |
162 S get(S)() | 85 inStringLiteral = true; |
163 { | |
164 static assert (is(S == delegate)); | |
165 S r; | |
166 r.ptr = context; | |
167 r.funcptr = cast(typeof(r.funcptr))funcptr; | |
168 return r; | |
169 } | |
170 } | |
171 | |
172 struct Slot(R) | |
173 { | |
174 alias R Receiver; | |
175 | |
176 Receiver receiver; | |
177 Dg invoker; | |
178 ConnectionFlags flags; | |
179 | |
180 static if (is(Receiver == Dg)) | |
181 { | |
182 static const isDelegate = true; | |
183 | |
184 bool isDisposed() | |
185 { | |
186 return !receiver.funcptr; | |
187 } | |
188 | |
189 void dispose() | |
190 { | |
191 receiver.funcptr = null; | |
192 receiver.context = null; | |
193 } | |
194 | |
195 Object getObject() | |
196 { | |
197 return flags & ConnectionFlags.NoObject || !receiver.context | |
198 ? null : _d_toObject(receiver.context); | |
199 } | |
200 } | |
201 else | |
202 static const isDelegate = false; | |
203 } | |
204 | |
205 enum SlotListId | |
206 { | |
207 Func, // function pointers | |
208 Weak, // object delegates stored in C heap | |
209 Strong // delegates stored in GC heap | |
210 } | |
211 | |
212 /** | |
213 Used to specify the type of a signal-to-slot connection. | |
214 | |
215 Examples: | |
216 ---- | |
217 class Sender | |
218 { | |
219 mixin Signal!("changed"); | |
220 void change() | |
221 { | |
222 changed.emit; | |
223 } | |
224 } | |
225 | |
226 | |
227 class Receiver | |
228 { | |
229 void alarm() {} | |
230 } | |
231 | |
232 void main() | |
233 { | |
234 auto s = new Sender; | |
235 auto r = new Receiver; | |
236 s.changed.connect(&r.alarm); // now s weakly references r | |
237 | |
238 r = null; | |
239 // collect garbage (assume there is no more reachable pointers | |
240 // to the receiver and it gets finalized) | |
241 ... | |
242 | |
243 s.change; | |
244 // weak reference to the receiving object | |
245 // has been removed from the sender's connection lists. | |
246 | |
247 r = new Receiver; | |
248 s.changed.connect(&r.alarm, ConnectionFlags.Strong); | |
249 | |
250 r = null; | |
251 // collect garbage | |
252 ... | |
253 // the receiving object has not been finalized because s strongly references it. | |
254 | |
255 s.change; // the receiver is called. | |
256 delete r; | |
257 s.change; // the receiver is disconnected from the sender. | |
258 | |
259 static void foo() | |
260 { | |
261 } | |
262 | |
263 s.changed.connect(&foo); | |
264 s.changed.emit; // foo is called. | |
265 s.changed.disconnect(&foo); // must be explicitly disconnected. | |
266 | |
267 void bar() | |
268 { | |
269 } | |
270 | |
271 // ConnectionFlags.NoObject must be specified for delegates | |
272 // to non-static local functions or struct member functions. | |
273 s.changed.connect(&bar, ConnectionFlags.NoObject); | |
274 s.changed.emit; // bar is called. | |
275 s.changed.disconnect(&bar); // must be explicitly disconnected. | |
276 } | |
277 ---- | |
278 */ | |
279 public enum ConnectionFlags : ubyte | |
280 { | |
281 /// | |
282 None, | |
283 /** | |
284 The receiver will be stored as weak reference (implied if ConnectionFlags.NoObject is not specified). | |
285 If the signal receiver is not a function pointer or a delegate referencing a D class instance. | |
286 the sender will not be notified when the receiving object is deleted and emitting the signal | |
287 connected to that receiving object will result in undefined behavior. | |
288 */ | |
289 Weak = 0x0001, | |
290 /** | |
291 The receiver is stored as strong reference (implied if ConnectionFlags.NoObject is specified). | |
292 */ | |
293 Strong = 0x0002, | |
294 /** | |
295 Must be specified if the receiver is not a function pointer or a delegate referencing a D class instance. | |
296 */ | |
297 NoObject = 0x0004 | |
298 | |
299 // Queued = 0x0004, | |
300 // BlockingQueued = 0x0008 | |
301 } | |
302 | |
303 | |
304 struct SlotList(SlotT, bool strong = false) | |
305 { | |
306 alias SlotT SlotType; | |
307 SlotType[] data; | |
308 | |
309 void length(size_t length) | |
310 { | |
311 static if (strong) | |
312 data.length = length; | |
313 else | |
314 realloc(data, length); | |
315 } | |
316 | |
317 SlotType* add(SlotType slot) | |
318 { | |
319 auto oldLen = data.length; | |
320 length = oldLen + 1; | |
321 auto p = &data[oldLen]; | |
322 *p = slot; | |
323 return p; | |
324 } | |
325 | |
326 SlotType* get(int slotId) | |
327 { | |
328 return &data[slotId]; | |
329 } | |
330 | |
331 void remove(int slotId) | |
332 { | |
333 move(data, slotId, slotId + 1, data.length - slotId - 1); | |
334 data = data[0..$ - 1]; | |
335 } | |
336 | |
337 size_t length() | |
338 { | |
339 return data.length; | |
340 } | |
341 | |
342 void free() | |
343 { | |
344 static if (!strong) | |
345 cfree(data.ptr); | |
346 } | |
347 } | |
348 | |
349 public alias void delegate(int signalId, SignalEventId event) SignalEvent; | |
350 | |
351 struct Receivers | |
352 { | |
353 struct Data | |
354 { | |
355 Object object; | |
356 int refs; | |
357 } | |
358 | |
359 Data[] data; | |
360 void add(Object receiver, DEvent disposeEvent) | |
361 { | |
362 foreach (ref d; data) | |
363 { | |
364 if (d.object is receiver) | |
365 { | |
366 d.refs++; | |
367 return; | |
368 } | 86 } |
369 } | 87 } |
370 | 88 |
371 append(data, Data(receiver, 1)); | 89 if (!inStringLiteral && braces == 0) |
372 rt_attachDisposeEvent(receiver, disposeEvent); | |
373 } | |
374 | |
375 void remove(Object receiver, DEvent disposeEvent) | |
376 { | |
377 foreach (i, ref d; data) | |
378 { | 90 { |
379 if (d.object is receiver) | 91 if(c == '=') // found default value |
380 { | 92 { |
381 assert (d.refs); | 93 inDefaultValue = true; |
382 d.refs--; | 94 startValue = i+1; |
383 if (!d.refs) | 95 } |
96 else if(c == ',') // next function argument | |
97 { | |
98 if (inDefaultValue) | |
384 { | 99 { |
385 .erase(data, i); | 100 res ~= signature[startValue..i]; |
386 rt_detachDisposeEvent(receiver, disposeEvent); | 101 inDefaultValue = false; |
387 } | |
388 return; | |
389 } | |
390 } | |
391 | |
392 assert (false); | |
393 } | |
394 | |
395 // remove all refarences for receiver, receiver has been disposed | |
396 void removeAll(Object receiver) | |
397 { | |
398 foreach (i, ref d; data) | |
399 { | |
400 if (d.object is receiver) | |
401 { | |
402 .erase(data, i); | |
403 return; | |
404 } | |
405 } | |
406 } | |
407 | |
408 // remove all references for all receivers, detaching dispose events | |
409 void free(DEvent disposeEvent) | |
410 { | |
411 foreach (i, ref d; data) | |
412 rt_detachDisposeEvent(d.object, disposeEvent); | |
413 cfree(data.ptr); | |
414 data = null; | |
415 } | |
416 } | |
417 | |
418 struct SignalConnections | |
419 { | |
420 bool isInUse; | |
421 | |
422 STuple!( | |
423 SlotList!(Slot!(Fn)), | |
424 SlotList!(Slot!(Dg)), | |
425 SlotList!(Slot!(Dg), true) | |
426 ) slotLists; | |
427 | |
428 STuple!( | |
429 Fn[], | |
430 Dg[] | |
431 ) delayedDisconnects; | |
432 | |
433 void addDelayedDisconnect(Fn r) | |
434 { | |
435 delayedDisconnects.at!(0) ~= r; | |
436 } | |
437 | |
438 void addDelayedDisconnect(Dg r) | |
439 { | |
440 delayedDisconnects.at!(1) ~= r; | |
441 } | |
442 | |
443 SlotListType!(slotListId)* getSlotList(int slotListId)() | |
444 { | |
445 return &slotLists.tupleof[slotListId]; | |
446 } | |
447 | |
448 bool hasSlots() | |
449 { | |
450 foreach(i, e; slotLists.tupleof) | |
451 { | |
452 if (slotLists.tupleof[i].length) | |
453 return true; | |
454 } | |
455 return false; | |
456 } | |
457 | |
458 int slotCount() | |
459 { | |
460 int count; | |
461 foreach(i, e; slotLists.tupleof) | |
462 count += slotLists.at!(i).length; | |
463 return count; | |
464 } | |
465 | |
466 void slotListLengths(int[] lengths) | |
467 { | |
468 foreach(i, e; slotLists.tupleof) | |
469 lengths[i] = slotLists.at!(i).length; | |
470 } | |
471 | |
472 SlotType!(slotListId)* addSlot(int slotListId)(SlotType!(slotListId) slot) | |
473 { | |
474 return getSlotList!(slotListId).add(slot); | |
475 } | |
476 | |
477 void removeSlot(int slotListId)(int slotId) | |
478 { | |
479 slotLists.at!(slotListId).remove(slotId); | |
480 } | |
481 | |
482 void free() | |
483 { | |
484 foreach(i, e; slotLists.tupleof) | |
485 { | |
486 static if (is(typeof(slotLists.at!(i).free))) | |
487 slotLists.at!(i).free; | |
488 } | |
489 } | |
490 | |
491 void onReceiverDisposed(Object receiver) | |
492 { | |
493 foreach (i, e; slotLists.tupleof) | |
494 { | |
495 static if (slotLists.at!(i).SlotType.isDelegate) | |
496 { | |
497 foreach (ref slot; slotLists.at!(i).data) | |
498 { | |
499 if (slot.getObject is receiver) | |
500 slot.dispose; | |
501 } | 102 } |
502 } | 103 } |
503 } | 104 } |
504 } | 105 } |
505 | |
506 template SlotListType(int slotListId) | |
507 { | |
508 alias typeof(slotLists.tupleof)[slotListId] SlotListType; | |
509 } | |
510 | |
511 template SlotType(int slotListId) | |
512 { | |
513 alias SlotListType!(slotListId).SlotType SlotType; | |
514 } | |
515 | |
516 template ReceiverType(int slotListId) | |
517 { | |
518 alias SlotType!(slotListId).Receiver ReceiverType; | |
519 } | |
520 | |
521 static const slotListCount = slotLists.tupleof.length; | |
522 } | |
523 | |
524 | |
525 private Object signalSender_; | |
526 | |
527 /** | |
528 If called from a slot, returns the object | |
529 that is emitting the signal. Otherwise, returns null. | |
530 */ | |
531 public Object signalSender() { | |
532 return signalSender_; | |
533 } | |
534 | |
535 public final class SignalHandler | |
536 { | |
537 SignalConnections[] connections; | |
538 Receivers receivers; | |
539 Object owner; | |
540 int blocked; | |
541 | 106 |
542 SignalEvent signalEvent; | 107 if (inDefaultValue) |
543 | 108 res ~= signature[startValue..$]; |
544 alias SignalConnections.SlotType SlotType; | 109 |
545 alias SignalConnections.ReceiverType ReceiverType; | 110 return res; |
546 | 111 } |
547 public this(Object owner_) { | 112 |
548 owner = owner_; | 113 int defaultValuesLength(string[] defVals) |
549 } | 114 { |
550 | 115 return defVals.length; |
551 private SignalConnections* getConnections(int signalId) | |
552 { | |
553 if (signalId < connections.length) | |
554 return &connections[signalId]; | |
555 return null; | |
556 } | |
557 | |
558 private SlotType!(slotListId)* addSlot(int slotListId)(int signalId, ReceiverType!(slotListId) receiver, | |
559 Dg invoker, ConnectionFlags flags) | |
560 { | |
561 if (signalId >= connections.length) | |
562 connections.length = signalId + 1; | |
563 auto slot = connections[signalId].addSlot!(slotListId)(SlotType!(slotListId)(receiver, invoker)); | |
564 | |
565 static if (slot.isDelegate) | |
566 { | |
567 if (!(flags & ConnectionFlags.NoObject)) | |
568 receivers.add(_d_toObject(receiver.context), &onReceiverDisposed); | |
569 } | |
570 | |
571 if (signalEvent && connections[signalId].slotCount == 1) | |
572 signalEvent(signalId, SignalEventId.firstSlotConnected); | |
573 | |
574 return slot; | |
575 } | |
576 | |
577 void onReceiverDisposed(Object receiver) | |
578 { | |
579 synchronized(this) | |
580 { | |
581 foreach(ref c; connections) | |
582 c.onReceiverDisposed(receiver); | |
583 receivers.removeAll(receiver); | |
584 } | |
585 } | |
586 | |
587 private void removeSlot(int slotListId)(int signalId, int slotId) | |
588 { | |
589 auto slot = connections[signalId].getSlotList!(slotListId).get(slotId); | |
590 static if (slot.isDelegate) | |
591 { | |
592 if (auto obj = slot.getObject) | |
593 receivers.remove(obj, &onReceiverDisposed); | |
594 } | |
595 | |
596 connections[signalId].removeSlot!(slotListId)(slotId); | |
597 | |
598 if (signalEvent && !connections[signalId].slotCount) | |
599 signalEvent(signalId, SignalEventId.lastSlotDisconnected); | |
600 } | |
601 | |
602 size_t slotCount(int signalId) | |
603 { | |
604 synchronized(this) | |
605 { | |
606 auto con = getConnections(signalId); | |
607 if (con) | |
608 return con.slotCount; | |
609 return 0; | |
610 } | |
611 } | |
612 | |
613 void connect(Receiver)(int signalId, Receiver receiver, | |
614 Dg invoker, ConnectionFlags flags) | |
615 { | |
616 synchronized(this) | |
617 { | |
618 static if (is(typeof(receiver.context))) | |
619 { | |
620 Object obj; | |
621 if ((flags & ConnectionFlags.NoObject)) | |
622 { | |
623 // strong by default | |
624 if (flags & ConnectionFlags.Weak) | |
625 addSlot!(SlotListId.Weak)(signalId, receiver, invoker, flags); | |
626 else | |
627 addSlot!(SlotListId.Strong)(signalId, receiver, invoker, flags); | |
628 } | |
629 else | |
630 { | |
631 // weak by default | |
632 if (flags & ConnectionFlags.Strong) | |
633 addSlot!(SlotListId.Strong)(signalId, receiver, invoker, flags); | |
634 else | |
635 addSlot!(SlotListId.Weak)(signalId, receiver, invoker, flags); | |
636 } | |
637 } | |
638 else | |
639 addSlot!(SlotListId.Func)(signalId, receiver, invoker, flags); | |
640 } | |
641 } | |
642 | |
643 void disconnect(Receiver)(int signalId, Receiver receiver) | |
644 { | |
645 synchronized(this) | |
646 { | |
647 auto cons = getConnections(signalId); | |
648 if (!cons) | |
649 return; | |
650 | |
651 // if called from a slot being executed by this signal, delay disconnection | |
652 // until all slots has been called. | |
653 if (cons.isInUse) | |
654 { | |
655 cons.addDelayedDisconnect(receiver); | |
656 return; | |
657 } | |
658 | |
659 TOP: | |
660 foreach (slotListId, e; cons.slotLists.tupleof) | |
661 { | |
662 /// COMPILER BUG: ReceiverType is evaluated to expression instead of type. | |
663 static if (is(typeof(cons.ReceiverType!(slotListId)) == Receiver)) | |
664 { | |
665 auto slotList = cons.getSlotList!(slotListId); | |
666 for (int slotId; slotId < slotList.length;) | |
667 { | |
668 auto slot = slotList.get(slotId); | |
669 static if (slot.isDelegate) | |
670 { | |
671 if (slot.isDisposed) | |
672 { | |
673 removeSlot!(slotListId)(signalId, slotId); | |
674 continue; | |
675 } | |
676 } | |
677 | |
678 if (slot.receiver == receiver) | |
679 { | |
680 removeSlot!(slotListId)(signalId, slotId); | |
681 break TOP; | |
682 } | |
683 | |
684 slotId++; | |
685 } | |
686 } | |
687 } | |
688 } | |
689 } | |
690 | |
691 void emit(A...)(size_t signalId, A args) | |
692 { | |
693 synchronized(this) | |
694 { | |
695 if (signalId >= connections.length || blocked) | |
696 return; | |
697 auto cons = &connections[signalId]; | |
698 | |
699 if (cons.hasSlots) | |
700 { | |
701 { | |
702 cons.isInUse = true; | |
703 signalSender_ = owner; | |
704 scope(exit) | |
705 { | |
706 cons.isInUse = false; | |
707 signalSender_ = null; | |
708 } | |
709 | |
710 // Store the lengths to avoid calling new slots | |
711 // connected in the slots being called. | |
712 // dmd bug: int[cons.slotListCount] fails | |
713 static const c = cons.slotListCount; | |
714 int[c] lengths = void; | |
715 cons.slotListLengths(lengths); | |
716 | |
717 foreach (slotListId, e; cons.slotLists.tupleof) | |
718 { | |
719 auto slotList = cons.getSlotList!(slotListId); | |
720 for (size_t slotId; slotId < lengths[slotListId];) | |
721 { | |
722 auto slot = slotList.get(slotId); | |
723 static if (slot.isDelegate) | |
724 { | |
725 if (slot.isDisposed) | |
726 { | |
727 removeSlot!(slotListId)(signalId, slotId); | |
728 lengths[slotListId]--; | |
729 continue; | |
730 } | |
731 } | |
732 | |
733 slot.invoker.call!(void)(slot.receiver, args); | |
734 ++slotId; | |
735 } | |
736 } | |
737 } | |
738 | |
739 | |
740 // process delayed disconnects if any | |
741 foreach(i, e; cons.delayedDisconnects.tupleof) | |
742 { | |
743 if (cons.delayedDisconnects.at!(i).length) | |
744 { | |
745 foreach (d; cons.delayedDisconnects.at!(i)) | |
746 disconnect(signalId, d); | |
747 cons.delayedDisconnects.at!(i).length = 0; | |
748 } | |
749 } | |
750 } | |
751 } | |
752 } | |
753 | |
754 // Adjusts signal arguments and calls the slot. S - slot signature, A - signal arguments | |
755 private void invokeSlot(S, Receiver, A...)(Receiver r, A args) | |
756 { | |
757 r.get!(S)()(args[0..ParameterTypeTuple!(S).length]); | |
758 } | |
759 | |
760 void blockSignals() | |
761 { | |
762 synchronized(this) | |
763 blocked++; | |
764 } | |
765 | |
766 void unblockSignals() | |
767 { | |
768 synchronized(this) | |
769 { | |
770 if(!blocked) | |
771 throw new SignalException("Signals are not blocked"); | |
772 blocked--; | |
773 } | |
774 } | |
775 | |
776 ~this() | |
777 { | |
778 receivers.free(&onReceiverDisposed); | |
779 foreach(ref c; connections) | |
780 c.free; | |
781 } | |
782 } | |
783 | |
784 public template SignalHandlerOps() | |
785 { | |
786 static assert (is(typeof(this.signalHandler)), | |
787 "SignalHandlerOps is already instantiated in " ~ typeof(this).stringof ~ " or one of its base classes"); | |
788 | |
789 protected: | |
790 SignalHandler signalHandler_; // manages signal-to-slot connections | |
791 | |
792 final SignalHandler signalHandler() | |
793 { | |
794 if (!signalHandler_) | |
795 { | |
796 signalHandler_ = new SignalHandler(this); | |
797 onSignalHandlerCreated(signalHandler_); | |
798 } | |
799 return signalHandler_; | |
800 } | |
801 | |
802 void onSignalHandlerCreated(ref SignalHandler sh) | |
803 { | |
804 } | |
805 | |
806 public: | |
807 final void blockSignals() | |
808 { | |
809 signalHandler.blockSignals(); | |
810 } | |
811 | |
812 final void unblockSignals() | |
813 { | |
814 signalHandler.unblockSignals(); | |
815 } | |
816 | |
817 template connect(string signalName, A...) | |
818 { | |
819 static void connect(T, Func)(T sender, Func func, ConnectionFlags flags = ConnectionFlags.None) | |
820 if (isFnOrDg!(Func)) | |
821 { | |
822 alias findSignal!(T, signalName, Func, A).result sig; | |
823 auto sh = sender.signalHandler(); | |
824 static if (isFn!(Func)) | |
825 alias Fn Callable; | |
826 else | |
827 alias Dg Callable; | |
828 auto invoker = Dg(&sh.invokeSlot!(typeof(func), Callable, sig[2..$])); | |
829 sh.connect(sig[1], Callable(func), invoker, flags); | |
830 } | |
831 } | |
832 | |
833 template disconnect(string signalName, A...) | |
834 { | |
835 static void connect(T, Func)(T sender, Func func, ConnectionFlags flags = ConnectionFlags.None) | |
836 if (isFnOrDg!(Func)) | |
837 { | |
838 alias findSignal!(T, signalName, Func, A).result sig; | |
839 auto sh = sender.signalHandler(); | |
840 static if (isFn!(Func)) | |
841 alias Fn Callable; | |
842 else | |
843 alias Dg Callable; | |
844 sh.disconnect(sig[1], Callable(func)); | |
845 } | |
846 } | |
847 /* | |
848 template slotCount(string signalName, A...) | |
849 { | |
850 debug static void slotCount(T)(T sender) | |
851 { | |
852 alias findSignal!(T, signalName, Func, A).result sig; | |
853 auto sh = sender.signalHandler(); | |
854 return sh.slotCount(sig[1]); | |
855 } | |
856 } | |
857 */ | |
858 } | 116 } |
859 | 117 |
860 /** | 118 /** |
861 New implementation. | 119 New implementation. |
862 */ | 120 */ |
863 | 121 |
864 const string signalPrefix = "__signal"; | 122 |
123 // need this to mark static metamethods-info whether it's generated by presence of default args | |
124 enum DefaultArgs | |
125 { | |
126 None, Start, Continue | |
127 } | |
128 | |
129 // templates for extracting data from static meta-information of signals, slots or properties | |
130 // public alias TypeTuple!("name", index, OwnerClass, DefaultArgs.Start, ArgTypes) __signal | |
131 template MetaEntryName(source...) | |
132 { | |
133 enum MetaEntryName = source[0]; // name of the metaentry is the first element | |
134 } | |
135 | |
136 template MetaEntryOwner(source...) | |
137 { | |
138 alias TupleWrapper!(source[2]).at[0] MetaEntryOwner; // class that owns the property is the third | |
139 // Compiler #BUG 3092 - evaluates MetaEntryOwner as a Tuple with one element | |
140 } | |
141 | |
142 template MetaEntryArgs(source...) | |
143 { | |
144 alias source[4 .. $] MetaEntryArgs; // arguments-tuple starts from the fourth position | |
145 } | |
865 | 146 |
866 template TupleWrapper(A...) { alias A at; } | 147 template TupleWrapper(A...) { alias A at; } |
867 | 148 |
868 template isDg(Dg) | 149 template isDg(Dg) |
869 { | 150 { |
953 else | 234 else |
954 enum SigBySignPred = false; | 235 enum SigBySignPred = false; |
955 } | 236 } |
956 } | 237 } |
957 | 238 |
239 template ByOwner(Owner) | |
240 { | |
241 template ByOwner(source...) | |
242 { | |
243 enum ByOwner = is(MetaEntryOwner!source == Owner); | |
244 } | |
245 } | |
246 | |
958 template staticSymbolName(string prefix, int id) | 247 template staticSymbolName(string prefix, int id) |
959 { | 248 { |
960 const string staticSymbolName = prefix ~ ToString!(id); | 249 const string staticSymbolName = prefix ~ ToString!(id); |
961 } | 250 } |
962 | 251 |
963 template signatureString(string name, A...) | 252 template signatureString(string name, A...) |
964 { | 253 { |
965 const string signatureString = name ~ "(" ~ joinArgs!(A) ~ ")"; | 254 const string signatureString = name ~ "(" ~ joinArgs!(A) ~ ")"; |
966 } | 255 } |
967 | 256 |
257 // recursive search in the static meta-information | |
968 template findSymbolImpl(string prefix, C, int id, alias pred) | 258 template findSymbolImpl(string prefix, C, int id, alias pred) |
969 { | 259 { |
970 static if ( is(typeof(mixin("C." ~ staticSymbolName!(prefix, id)))) ) | 260 static if ( is(typeof(mixin("C." ~ staticSymbolName!(prefix, id)))) ) |
971 { | 261 { |
972 mixin ("alias C." ~ staticSymbolName!(prefix, id) ~ " current;"); | 262 mixin ("alias C." ~ staticSymbolName!(prefix, id) ~ " current;"); |
1004 static if (is(result == void)) | 294 static if (is(result == void)) |
1005 static assert(0, "Signal " ~ name ~ " was not found."); | 295 static assert(0, "Signal " ~ name ~ " was not found."); |
1006 } | 296 } |
1007 } | 297 } |
1008 | 298 |
299 // recursive search in the static meta-information | |
300 template findSymbolsImpl(string prefix, C, int id, alias pred) | |
301 { | |
302 static if ( is(typeof(mixin("C." ~ staticSymbolName!(prefix, id)))) ) | |
303 { | |
304 mixin ("alias C." ~ staticSymbolName!(prefix, id) ~ " current;"); | |
305 static if (pred!current) { | |
306 alias TupleWrapper!current subres; | |
307 // pragma(msg, toStringNow!id ~ " " ~ subres.stringof); | |
308 } else | |
309 alias TypeTuple!() subres; | |
310 alias TypeTuple!(subres, findSymbolsImpl!(prefix, C, id + 1, pred).result) result; | |
311 } | |
312 else | |
313 { | |
314 alias TypeTuple!() result; | |
315 } | |
316 } | |
317 | |
318 template findSymbols(string prefix, C, alias pred) | |
319 { | |
320 alias findSymbolsImpl!(prefix, C, 0, pred).result findSymbols; | |
321 } | |
322 | |
1009 string __toString(long v) | 323 string __toString(long v) |
1010 { | 324 { |
1011 if (v == 0) | 325 if (v == 0) |
1012 return "0"; | 326 return "0"; |
1013 | 327 |
1030 ret = "-" ~ ret; | 344 ret = "-" ~ ret; |
1031 | 345 |
1032 return ret; | 346 return ret; |
1033 } | 347 } |
1034 | 348 |
1035 public string SignalEmitter(A...)(SignalType signalType, string name, int index) | 349 string convertSignalArguments(Args...)() |
350 { | |
351 // void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; | |
352 | |
353 string res = "void*[" ~ __toString(Args.length+1) ~ "] _a = [null"; | |
354 foreach(i, _; Args) | |
355 res ~= ", " ~ "cast(void*) &" ~ convertSignalArgument!(Args[i])("_t" ~ __toString(i)); | |
356 res ~= "];\n"; | |
357 return res; | |
358 } | |
359 | |
360 public string SignalEmitter(A...)(SignalType signalType, string name, string[] defVals, int localIndex) | |
1036 { | 361 { |
1037 string fullArgs, args; | 362 string fullArgs, args; |
363 int defValsLength = defVals.length; | |
364 string argsConversion = ""; | |
365 string argsPtr = "null"; | |
1038 static if (A.length) | 366 static if (A.length) |
1039 { | 367 { |
1040 fullArgs = A[0].stringof ~ " a0"; | 368 while(A.length != defVals.length) |
1041 args = ", a0"; | 369 defVals = "" ~ defVals; |
370 | |
371 fullArgs = A[0].stringof ~ " _t0"; | |
372 if (defVals[0].length) | |
373 fullArgs ~= " = " ~ defVals[0]; | |
374 args = "_t0"; | |
1042 foreach(i, _; A[1..$]) | 375 foreach(i, _; A[1..$]) |
1043 { | 376 { |
1044 fullArgs ~= ", " ~ A[i+1].stringof ~ " a" ~ __toString(i+1); | 377 fullArgs ~= ", " ~ A[i+1].stringof ~ " _t" ~ __toString(i+1); |
1045 args ~= ", a" ~ __toString(i+1); | 378 if (defVals[i+1].length) |
379 fullArgs ~= " = " ~ defVals[i+1]; | |
380 args ~= ", _t" ~ __toString(i+1); | |
1046 } | 381 } |
382 // build up conversion of signal args from D to C++ | |
383 argsPtr = "_a.ptr"; | |
384 argsConversion = convertSignalArguments!(A)(); | |
1047 } | 385 } |
1048 string attribute; | 386 string attribute; |
1049 string sigName = name; | 387 string sigName = name; |
1050 if (signalType == SignalType.BindQtSignal) | 388 if (signalType == SignalType.BindQtSignal) |
1051 name ~= "_emit"; | 389 name ~= "_emit"; |
1052 else | 390 else |
1053 attribute = "protected "; | 391 attribute = "protected "; |
1054 string str = attribute ~ "void " ~ name ~ "(" ~ fullArgs ~ ")" ~ | 392 |
1055 "{ this.signalHandler.emit(" ~ __toString(index) ~ args ~ "); }"; | 393 string indexArgs = __toString(localIndex); |
394 if(defValsLength > 0) | |
395 indexArgs ~= ", " ~ __toString(localIndex+defValsLength); | |
396 string str = attribute ~ "final void " ~ name ~ "(" ~ fullArgs ~ ") {\n" ~ argsConversion ~ "\n" | |
397 ~ " QMetaObject.activate(this, typeof(this).staticMetaObject, " ~ indexArgs ~ ", " ~ argsPtr ~ ");\n" | |
398 ~ "}\n"; // ~ | |
1056 return str; | 399 return str; |
1057 } | 400 } |
1058 | |
1059 /** ---------------- */ | 401 /** ---------------- */ |
1060 | 402 |
1061 | 403 |
1062 /** | 404 const string signalPrefix = "__signal"; |
1063 Examples: | 405 const string slotPrefix = "__slot"; |
1064 ---- | 406 |
1065 struct Args | 407 enum SignalType |
1066 { | 408 { |
1067 bool cancel; | 409 BindQtSignal, |
1068 } | 410 NewSignal, |
1069 | 411 NewSlot |
1070 class C | 412 } |
1071 { | 413 |
1072 private int _x; | 414 template BindQtSignal(string fullName) |
1073 // reference parameters are not supported yet, | 415 { |
1074 // so we pass arguments by pointer. | 416 mixin MetaMethodImpl!(signalPrefix, 0, fullName, SignalType.BindQtSignal); |
1075 mixin Signal!("xChanging", int, Args*); | 417 } |
1076 mixin Signal!("xChanged"); | 418 |
1077 | 419 template Signal(string fullName) |
1078 void x(int v) | 420 { |
1079 { | 421 mixin MetaMethodImpl!(signalPrefix, 0, fullName, SignalType.NewSignal); |
1080 if (v != _x) | 422 } |
423 | |
424 template Slot(string fullName) | |
425 { | |
426 mixin MetaMethodImpl!(slotPrefix, 0, fullName, SignalType.NewSlot); | |
427 } | |
428 | |
429 template SignalImpl(int index, string fullName, SignalType signalType) | |
430 { | |
431 static if (is(typeof(mixin(typeof(this).stringof ~ "." ~ signalPrefix ~ ToString!(index))))) | |
432 mixin SignalImpl!(index + 1, fullName, signalType); | |
433 else | |
434 { | |
435 // pragma(msg, "alias void delegate" ~ getFunc!_Tuple(fullName) ~ " Dg;"); | |
436 mixin("alias void delegate" ~ getFunc!_Tuple(fullName) ~ " Dg;"); | |
437 alias ParameterTypeTuple!(Dg) ArgTypes; | |
438 enum args = getFunc!_Args(fullName); | |
439 enum defVals = defaultValues(args); | |
440 enum defValsLength = defaultValuesLength(defVals); | |
441 | |
442 // pragma (msg, SignalEmitter!(ArgTypes)(SignalType.NewSignal, getFunc!_Name(fullName), defVals, index)); | |
443 mixin InsertMetaSignal!(fullName, index, defValsLength, ArgTypes); | |
444 // pragma (msg, ctfe_meta_signal!(ArgTypes)(fullName, index, defValsLength)); | |
445 } | |
446 } | |
447 template MetaMethodImpl(string metaPrefix, int index, string fullName, SignalType signalType) | |
448 { | |
449 static if (is(typeof(mixin(typeof(this).stringof ~ "." ~ metaPrefix ~ toStringNow!(index))))) | |
450 { | |
451 mixin MetaMethodImpl!(metaPrefix, index + 1, fullName, signalType); | |
452 } | |
453 else | |
454 { | |
455 mixin("alias void delegate" ~ getFunc!_Tuple(fullName) ~ " Dg;"); | |
456 alias ParameterTypeTuple!(Dg) ArgTypes; | |
457 enum args = getFunc!_Args(fullName); | |
458 enum defVals = defaultValues(args); | |
459 enum defValsLength = defaultValuesLength(defVals); | |
460 | |
461 static if (metaPrefix == signalPrefix) | |
1081 { | 462 { |
1082 Args args; | 463 // calculating local index of the signal |
1083 xChanging.emit(v, &args); | 464 static if (typeof(this).stringof == "QObject") |
1084 if (!args.cancel) | 465 enum localIndex = index; |
466 else | |
467 mixin ("enum localIndex = index - 1 - lastSignalIndex_" ~ (typeof(super)).stringof ~ ";"); | |
468 | |
469 static if (signalType == SignalType.NewSignal) | |
1085 { | 470 { |
1086 _x = v; | 471 pragma (msg, SignalEmitter!(ArgTypes)(SignalType.NewSignal, getFunc!_Name(fullName), defVals, localIndex)); |
1087 xChanged.emit; | 472 mixin (SignalEmitter!(ArgTypes)(SignalType.NewSignal, getFunc!_Name(fullName), defVals, localIndex)); |
1088 } | 473 } |
1089 } | 474 } |
1090 } | 475 mixin InsertMetaMethod!(fullName, metaPrefix, index, defValsLength, DefaultArgs.Start, ArgTypes); |
1091 } | 476 // pragma (msg, ctfe_meta_signal!(ArgTypes)(fullName, index, defValsLength)); |
1092 ---- | 477 } |
1093 */ | 478 } |
1094 | 479 template InsertMetaMethod(string fullName, string metaPrefix, int index, int defValsCount, DefaultArgs defArgsInitFlag, ArgTypes...) |
1095 enum SignalType | 480 { |
1096 { | 481 // this identifies if metamethod is was generated by the presence of default args or not |
1097 BindQtSignal, | 482 static if(defValsCount > 0) |
1098 NewSignal | 483 { |
1099 } | 484 static if (defArgsInitFlag == DefaultArgs.Start) |
1100 | 485 enum defValsFlag = DefaultArgs.Start; |
1101 template BindQtSignal(string name, A...) | 486 else |
1102 { | 487 enum defValsFlag = DefaultArgs.Continue; |
1103 mixin SignalImpl!(0, name, SignalType.BindQtSignal, A); | 488 } |
1104 } | 489 else |
1105 | 490 { |
1106 template Signal(string name, A...) | 491 static if (defArgsInitFlag == DefaultArgs.Start) |
1107 { | 492 enum defValsFlag = DefaultArgs.None; |
1108 mixin SignalImpl!(0, name, SignalType.NewSignal, A); | 493 else |
1109 } | 494 enum defValsFlag = DefaultArgs.Continue; |
1110 | 495 } |
1111 template SignalImpl(int index, string name, SignalType signalType, A...) | 496 static if(defValsCount >= 0) |
1112 { | 497 mixin("public alias TypeTuple!(\"" ~ getFunc!_Name(fullName) ~ "\", index, typeof(this), defValsFlag, ArgTypes) " ~ metaPrefix ~ toStringNow!(index) ~ ";"); |
1113 static if (is(typeof(mixin(typeof(this).stringof ~ ".__signal" ~ ToString!(index))))) | 498 static if(defValsCount > 0) |
1114 mixin SignalImpl!(index + 1, name, signalType, A); | 499 mixin InsertMetaMethod!(fullName, metaPrefix, index+1, defValsCount-1, DefaultArgs.Continue, ArgTypes[0..$-1]); |
1115 else | 500 } |
1116 { | 501 |
1117 // mixed-in once | 502 |
1118 static if (!is(typeof(this.signalHandler))) | 503 string signature_impl(T...)(string name) |
1119 mixin SignalHandlerOps; | 504 { |
1120 | 505 string res = name ~ "("; |
1121 mixin (SignalEmitter!(A)(signalType, name, index)); | 506 foreach(i, _; T) |
1122 mixin("public alias TypeTuple!(\"" ~ name ~ "\", index, A) __signal" ~ ToString!(index) ~ ";"); | 507 { |
1123 } | 508 if(i > 0) |
1124 } | 509 res ~= ","; |
1125 | 510 res ~= T[i].stringof; |
1126 extern(C) alias void function(void*) SlotConnector; | 511 } |
1127 | 512 res ~= ")"; |
1128 debug (UnitTest) | 513 return res; |
1129 { | 514 } |
1130 class A | 515 |
1131 { | 516 template signature(string name, T...) |
1132 mixin Signal!("scorched", int); | 517 { |
1133 | 518 enum signature = signature_impl!(T)(name); |
1134 int signalId1 = -1; | 519 } |
1135 int signalId2 = -1; | 520 |
1136 | 521 template lastSignalIndex(T) |
1137 void onFirstConnect(int sId) | 522 { |
1138 { | 523 static if (T.stringof == "QObject") |
1139 signalId1 = sId; | 524 enum lastSignalIndex = lastSignalIndexImpl!(T, 0); |
1140 } | 525 else |
1141 | 526 mixin ("enum lastSignalIndex = lastSignalIndexImpl!(T, " ~ "T.lastSignalIndex_" ~ (BaseClassesTuple!(T)[0]).stringof ~ ");"); |
1142 void onLastDisconnect(int sId) | 527 } |
1143 { | 528 |
1144 signalId2 = sId; | 529 template lastSignalIndexImpl(T, int index) |
1145 } | 530 { |
1146 | 531 static if (is(typeof(mixin("T." ~ signalPrefix ~ toStringNow!(index))))) |
1147 this() | 532 enum lastSignalIndexImpl = lastSignalIndexImpl!(T, index + 1); |
1148 { | 533 else |
1149 signalHandler.firstSlotConnected = &onFirstConnect; | 534 enum lastSignalIndexImpl = index - 1; |
1150 signalHandler.lastSlotDisconnected = &onLastDisconnect; | 535 } |
1151 } | |
1152 } | |
1153 | |
1154 class B : A | |
1155 { | |
1156 mixin Signal!("booed", int); | |
1157 | |
1158 int bazSum; | |
1159 void baz(int i) | |
1160 { | |
1161 bazSum += i; | |
1162 } | |
1163 } | |
1164 | |
1165 class C : A | |
1166 { | |
1167 mixin Signal!("cooked"); | |
1168 } | |
1169 } | |
1170 | |
1171 unittest | |
1172 { | |
1173 static int fooSum; | |
1174 static int barSum; | |
1175 | |
1176 static void foo(int i) | |
1177 { | |
1178 fooSum += i; | |
1179 } | |
1180 | |
1181 void bar(long i) | |
1182 { | |
1183 barSum += i; | |
1184 } | |
1185 | |
1186 auto a = new A; | |
1187 auto b = new B; | |
1188 auto c = new C; | |
1189 assert(b.scorched.signalId == 0); | |
1190 assert(b.booed.signalId == 1); | |
1191 assert(c.cooked.signalId == 1); | |
1192 | |
1193 auto sh = b.signalHandler; | |
1194 | |
1195 b.scorched.connect(&foo); | |
1196 assert(sh.connections.length == 1); | |
1197 assert(b.signalId1 == 0); | |
1198 auto scCons = &sh.connections[0]; | |
1199 | |
1200 assert(scCons.getSlotList!(SlotListId.Func).length == 1); | |
1201 b.scorched.emit(1); | |
1202 assert(fooSum == 1); | |
1203 | |
1204 b.scorched.connect(&bar, ConnectionFlags.NoObject); | |
1205 assert(sh.connections.length == 1); | |
1206 assert(scCons.getSlotList!(SlotListId.Strong).length == 1); | |
1207 b.scorched.emit(1); | |
1208 assert (fooSum == 2 && barSum == 1); | |
1209 | |
1210 b.scorched.connect(&b.baz); | |
1211 assert(scCons.getSlotList!(SlotListId.Weak).length == 1); | |
1212 b.scorched.emit(1); | |
1213 assert (fooSum == 3 && barSum == 2 && b.bazSum == 1); | |
1214 | |
1215 b.scorched.disconnect(&bar); | |
1216 assert(scCons.slotCount == 2); | |
1217 b.scorched.disconnect(&b.baz); | |
1218 assert(scCons.slotCount == 1); | |
1219 b.scorched.disconnect(&foo); | |
1220 assert(scCons.slotCount == 0); | |
1221 assert(b.signalId2 == 0); | |
1222 | |
1223 fooSum = 0; | |
1224 void connectFoo() | |
1225 { | |
1226 b.scorched.connect(&foo); | |
1227 b.scorched.disconnect(&connectFoo); | |
1228 } | |
1229 | |
1230 b.scorched.connect(&connectFoo, ConnectionFlags.NoObject); | |
1231 b.scorched.emit(1); | |
1232 assert(scCons.getSlotList!(SlotListId.Func).length == 1); | |
1233 assert(scCons.getSlotList!(SlotListId.Strong).length == 0); | |
1234 assert(!fooSum); | |
1235 | |
1236 auto r = new B(); | |
1237 b.scorched.connect(&r.baz); | |
1238 assert(scCons.getSlotList!(SlotListId.Weak).length == 1); | |
1239 b.scorched.emit(1); | |
1240 assert(r.bazSum == 1); | |
1241 assert(fooSum == 1); | |
1242 | |
1243 delete(r); | |
1244 assert(scCons.getSlotList!(SlotListId.Weak).length == 1); | |
1245 b.scorched.emit(1); | |
1246 assert(scCons.getSlotList!(SlotListId.Weak).length == 0); | |
1247 } |