root/trunk/luigi/signalobj.d

Revision 46, 13.6 kB (checked in by baxissimo, 8 years ago)

Added top level add_blah_callback calls in gui.d to hook the system-level input routines. May rethink this a bit still. That may be wanted in some cases (reshape, or recording all mouse motion for example) , but probably more useful would be routines that get called as a _fallback_ when no gui widget intercepts the event.

Fixed disconnect in signalobj.d

Added disconnect routines to adapter.d

Fixed dxut to work without Derelict.

Line 
1 /**
2  * signalobj.d - a few utilities for creating signal objects for more flexible
3  *   signals and slots.
4  *
5  * Written by Bill Baxter, November 9, 2006.
6  * This code is released to the public domain
7  */
8 module luigi.signalobj;
9
10 // This code triggered an ICE in DMD 0.173.  But now it's fixed in 0.174!!  cool!
11 import std.stdio : writefln;
12 import std.signals;
13 import std.traits;
14 import std.c.string : memcpy;
15
16 //----------------------------------------------------------------------------
17
18 /** A simple object that wraps a std.signal.  This allows creating signals at
19  *  global scope, returning them from functions, etc.
20  *  In terms of what can be connected, this has the same restrictions as
21  *  the std.signal.Signal mixin, namely the slot delegate signature must
22  *  match the signal's signature exactly.
23  */
24 class SignalObj(T...)
25 {
26     alias void delegate(T) slot_t;
27
28     // Methods provided by Signal mixin:
29     //   connect(slot_t);
30     //   disconnect(slot_t);
31     //   emit(T);
32     mixin Signal!(T);
33 }
34
35
36 /**
37  * A wrapper for a delegate.  This lets one use delegate literals
38  * as a slot in some situations where they would cause access errors.
39  */
40 class DelegateSlot(T...)
41 {
42     alias T args_t;
43     alias void delegate(T) func_t;
44
45     this( void delegate(T) dg) { fn = dg; }
46
47     void slot(T args) {
48         fn(args);
49     }
50     func_t fn;
51 }
52
53 /** A simple opaque datatype which serves as a key for FlexSignal.fdisconnect.
54  *  The primary use is internal, for keeping a mapping between user slots
55  *  and their wrappers.  However, they can also be used as a key to a delegate
56  *  literal for later disconnecting.
57  */
58 struct slot_key {
59     alias void delegate() slot_t;
60     byte[slot_t.sizeof] data=0;
61 }
62 slot_key make_slot_key(T)(T fn) {
63     alias void delegate() slot_t;
64     slot_key f_key;
65     static assert(T.sizeof <= slot_t.sizeof);
66     memcpy(&f_key, &fn, T.sizeof);
67     return f_key;
68 }
69
70 /** FlexSignal is a more flexible version of a signal object wrapper. 
71  *
72  *  Using the connect/disconnect methods it is possible to connect the
73  *  signal to most any callable entity with a signature that is 'compatible'.
74  *  with the signal's signature.
75  *
76  *  Specifically one can connect
77  *  - functions and delegate literals.
78  *  - slots that return values (the return value is simply ignored by the signal)
79  *  - slots that take fewer parameters than the signal supplies
80  *    (trailing paramters from signal simply aren't passed to the slot).
81  *  - delegates with fewer arguments
82  *  than the signal supplies are allowed, and any argument which can
83  *  be implicitly converted by D is allowed as well.
84  */
85 class FlexSignal(T...)
86 {
87     alias void delegate(T) slot_t;
88
89     private mixin Signal!(T) s;
90     slot_t[slot_key] thunkMap;
91
92     /** Emit the signal with the given arguments.
93      */
94     void emit(T v) {
95         s.emit(v);
96     }
97
98     /** Connect a slot to the signal using a class delegate exactly
99      *  matching the signature of the signal.
100      *  Returns: the slot passed in.
101      */
102     slot_t connect_exact(slot_t f) {
103         s.connect(f);
104         return f;
105     }
106
107     /** Disconnect a slot from the signal using a class delegate exactly
108      *  matching the signature of the signal.
109      *  Returns: the slot passed in.
110      */
111     slot_t disconnect_exact(slot_t f) {
112         s.disconnect(f);
113         return f;
114     }
115
116     /** A flexible version of connect.  Works for delegates to classes
117      * or structs, plain functions, delegate literals.  Also works for
118      * things with or without return values, and with fewer
119      * arguments than the signal supplies.
120      *
121      * So for example, an (int,char[]) FlexSignal can be connected to an
122      * int funciton that returns a float.
123      *
124      * Also it can convert compatible argument types, any thing that can
125      * be implicitly converted at runtime is allowed.
126      *
127      * So for example, the (int, char[]) FlexSignal can be connected to a
128      * method taking a (double,char[]) because int is automatically
129      * promoted to double.
130      *
131      * Returns: a slot key which can be used to disconnect the item
132      * later.  (You can also use the original slot to disconnect if you
133      * have it.)
134      */
135     slot_key connect(DT,int arg_offset=0)(DT f)
136     {
137         // make the key
138         static assert(DT.sizeof <= slot_t.sizeof);
139         slot_key f_key = make_slot_key(f);
140        
141         //slot_t f_wrap = SlotAdapter!(slot_t).adapt(f);
142         // wrap is safer for now.
143         slot_t f_wrap;
144         f_wrap = SlotAdapter!(slot_t).wrap!(typeof(f),arg_offset)(f);
145
146         s.connect(f_wrap);
147
148         thunkMap[f_key]=f_wrap;
149         return f_key;
150     }
151
152     /** Provides an alternate signal for connect() using the ~= operator.
153         This can allow for somewhat cleaner syntax when using delegate literals.
154         (One set of parentheses is eliminated)
155         For example:
156             theSignal ~= (Widget w, bool onoff) {
157                 writefln(onoff?"checked!":"unchecked!");};
158         Versus
159             theSignal.connect( (Widget w, bool onoff) {
160                 writefln(onoff?"checked!":"unchecked!");});
161         The downside is that it is not possible to specify template
162         arguments for ~= explicitly if needed.
163      */
164     alias connect opCatAssign;
165
166     /** Some simple wrappers for the most comment skipped argument
167      *  versions of connect.  These work well with implicit instatiation.
168      */
169     slot_key connect1st(DT)(DT f) { return connect!(DT,0)(f);  }
170     slot_key connect2nd(DT)(DT f) { return connect!(DT,1)(f);  }
171     slot_key connect3rd(DT)(DT f) { return connect!(DT,2)(f);  }
172     slot_key connect4th(DT)(DT f) { return connect!(DT,3)(f);  }
173     slot_key connect5th(DT)(DT f) { return connect!(DT,4)(f);  }
174
175     // this doesn't quite work yet because of DMD bug #540
176     // Name also has to be different from 'connect' for what also looks
177     // to be a compiler bug.  They should be able to co-exist.
178     // If it did work you'd be able to do something like sig.connecti!(2)(dg).
179     template connecti(int arg_offset) {
180         slot_key connecti(DT)(DT f)
181         {
182             return connect!(DT,arg_offset)(f);
183         }
184     }
185
186     /** Disconnect a slot of any type */
187     void disconnect(DT)(DT f) {
188         static if( is(DT==slot_t) ) {
189             s.disconnect(f);
190         }
191         else {
192             static if( is(DT==slot_key) ) {
193                 alias f f_key;
194             }
195             else {
196                 // make the key from the delegate
197                 static assert(DT.sizeof <= slot_t.sizeof);
198                 slot_key f_key = make_slot_key(f);
199             }
200
201             if (f_key in thunkMap) {
202                 s.disconnect(thunkMap[f_key]);
203             } else {
204                 debug writefln("FlexSignal.fdisconnect: Slot not found");
205             }
206         }
207     }
208 }
209
210 /**
211  SlotAdapter is a template that takes a signal's 'slot_t' delegate type.
212  It contains two functions, wrap() and adapt().
213
214  wrap() returns a slot_t delegate to object that wraps a passed in
215  delegate or function which may have a signature that differs from
216  'slot_t'.  As long as the callable entity passed in has argument
217  types compatible with 'slot_t' then the operation should succeed.  In
218  particular exact matching with slot_t's parameter types is not
219  necessary, any type implicitly convertable from the slot_t's argument
220  type is ok.
221
222  adapt() is the same as wrap(), except it will return the original
223  delegate without creating a wrapper if that delegate's matches the
224  slot_t exactly.
225
226  Usage:
227    auto wrapped = SlotAdapter!(Signals_SlotType).wrap(target_slot)
228    auto adapted = SlotAdapter!(Signals_SlotType).adapt(target_slot)
229 */
230 template SlotAdapter(slot_t)
231 {
232     alias ReturnType!(slot_t) slot_ret_t;
233     alias ParameterTypeTuple!(slot_t) slot_arg_t;
234    
235     static assert( is(slot_ret_t==void), "Expected native slot type to return void."  );
236    
237     slot_t wrap(WrapSlotT,int arg_offset=0)(WrapSlotT fn) {
238         alias WrapSlotT wrap_slot_t;
239         alias ReturnType!(fn) wrap_ret_t;
240         alias ParameterTypeTuple!(fn) wrap_arg_t;
241
242         // This has to be a _class_ for std.signal's use, currently
243         class Inner {
244             wrap_slot_t thunked_slot;
245             void slot(slot_arg_t arg)
246             {
247                 thunked_slot(arg[arg_offset..wrap_arg_t.length+arg_offset]);
248             }
249         }
250         Inner inner = new Inner;
251         inner.thunked_slot = fn;
252         return &inner.slot;
253     }
254
255     slot_t adapt(WrapSlotT)(WrapSlotT fn, int arg_offset=0)
256     {
257         static if (is(slot_t==WrapSlotT)) {
258             // This *does* need to be wrapped if this is
259             // a delegate to a struct method, but there's no way to ask D
260             // for this information.  Well anyway, disallowing struct
261             // is listed as a bug currently.
262             //    http://www.digitalmars.com/d/phobos/std_signals.html
263             // so there's hope it will change.
264             // Use wrap instead of adapt if this is a concern.
265             return fn;
266         }
267         else {
268             return wrap(fn,arg_offset);
269         }
270     }
271 }
272
273 // END LIBRARY CODE
274
275 //============================================================================
276 // Tests below -- not automated: requires eyeball to verify
277
278 unittest {
279 // Object with lots of different slots
280 class Slotty
281 {
282     char[] name;
283
284     this(char[] name_) {
285         name = name_;
286     }
287     ~this() {
288         debug (fsignal) writefln("-- %s.dtor: Kaw, see ya later, Chief!", name);
289     }
290
291     void slot0()
292     { writefln("-- %s.slot0: got ()", name); }
293     void slot1(int i)
294     { writefln("-- %s.slot1: got (%s)", name, i); }
295     void slot2(int i, char[]s)
296     { writefln("-- %s.slot2: got (%s,\"%s\")", name,i,s); }
297     void slot3(int i, char[]s, float f)
298     { writefln("-- %s.slot3: got (%s, \"%s\", %s)", name,i,s,f); }
299
300     void slot2l(long i, char[]s) {
301         writefln("-- %s.slot2l: got ((long) %s, \"%s\")", name,i,s);
302     }
303     void slot2b(byte i, char[]s) {
304         writefln("-- %s.slot2b: got ((byte) %s, \"%s\")", name,i,s);
305     }
306     void slot3l(int i, char[]s, long f)
307     { writefln("-- %s.slot3l: got (%s, \"%s\", (long) %s", name,i,s,f); }
308
309     // ----------ones with returns--------------
310     int slot0r() {
311         writefln("-- %s.slot0r: got () ...  Returning!",name);
312         return 0;
313     }
314     int slot1r(int i) {
315         writefln("-- %s.slot1r: got (%s)", name, i," ... Returning!");
316         return 1;
317     }
318     int slot2r(int i, char[]s) {
319         writefln("-- %s.slot2r: got (%s, \"%s\")", name,i,s, " ... Returning!");
320         return 2;
321     }
322
323     int slot2rl(long i, char[]s) {
324         writefln("-- %s.slot3rl: got (%s, \"%s\")", name,i,s, " ... Returning!");
325         return 2;
326     }
327
328     int slot3r(int i, char[]s, float f)
329     {
330         writefln("-- %s.slot3r: got (%s, \"%s\", %s)", name,i,s,f, " ... Returning!");
331         return 3;
332     }
333
334 }
335
336 void func0()
337 { writefln("-- func0: got()"); }
338
339 void func1(int i)
340 { writefln("-- func1: got (%s)", i); }
341
342 void func2(int i, char[]s)
343 { writefln("-- func2: got (%s, \"%s\")",i,s); }
344
345 void func3(int i, char[]s, float f)
346 { writefln("-- func3: got (%s, \"%s\", %s)",i,s,f); }
347
348 double func3ldr(long i, char[]s, double d)
349 {
350     writefln("-- func3ldr: got ((long) %s, \"%s\", (double) %s) ... Returning!",i,s,d);
351     return d;
352 }
353
354 void func1s(char [] s)
355 { writefln("-- func1s: got(\"%s\")", s); }
356
357
358 void test() {
359     auto s1 = new Slotty("Slotty1");
360     auto s2 = new Slotty("Slotty2");
361
362    
363     auto sig1 = new SignalObj!(int);
364     sig1.connect(&s1.slot1);
365     //sig1.connect(&s1.slot0); // error mismatch
366
367     writefln(">>> SignalObject EMIT");
368     sig1.emit(3);
369
370
371     writefln("\n>> TEST SlotAdapter...");
372
373     // error as you can't implicitly convert float to long
374     //auto test0 =  SlotAdapter!(fsig.slot_t).wrap(&s1.slot3l);
375     //test0(9,"tee nine", 9.9);
376
377     auto fsig = new FlexSignal!(int,char[],float);
378
379
380     alias SlotAdapter!(fsig.slot_t) SAdapt;
381     auto test1 =  SAdapt.wrap(&s1.slot2r);
382     test1(9,"nine", 9.9);
383     auto test2 =  SAdapt.wrap(&s1.slot2);
384     test2(9,"nine", 9.9);
385     auto test3 =  SAdapt.wrap(&s1.slot3);
386     test3(9,"nine", 9.9);
387
388
389     fsig.connect(&s1.slot3);
390     fsig.connect(&s1.slot2);
391     fsig.connect(&s1.slot1);
392     fsig.connect(&s1.slot0);
393     fsig.connect(&s2.slot3);
394     fsig.connect(&s2.slot2);
395     fsig.connect(&s2.slot1);
396     fsig.connect(&s2.slot0);
397
398     fsig.connect(&func0);
399     fsig.connect(&func1);
400     fsig.connect(&func2);
401     fsig.connect(&func3);
402     fsig.connect(&func3ldr);
403
404     // Connect, but skip the first arg of the signal
405     // I.e. just subscribe to the string part of the message.
406     // This IFTI limitation is annoying!
407     // fsig.connecti!(1)(&func1s);
408     //fsig.connect!(typeof(&func1s),1)(&func1s);
409     fsig.connect2nd(&func1s);
410
411     slot_key literal =
412         fsig.connect( (int i){ writefln("@@@ Hello from delegate literal! int=",i); });
413
414     writefln("\n>>> FlexSignal EMIT (3,\"A three\",9.9)");
415     fsig.emit(3,"A three!",9.9);
416
417     fsig.disconnect(&s2.slot0);
418     fsig.disconnect(&s1.slot3);
419
420     fsig.disconnect(&func0);
421     fsig.disconnect(&func1);
422     fsig.disconnect(&func2);
423
424     fsig.disconnect(literal);
425
426     writefln("\n>>> FlexSignal EMIT (4,\"A four!\",8.8)");
427     fsig.emit(4,"A four!",8.8);
428
429     debug (fsignal) writefln("Program end");
430 }
431 test();
432
433 } // end unittest
434
435 //----------------------------------------------------------------------------
436 // needed to run tests
437 version (unittestmain) {
438     void main(){}
439 }
Note: See TracBrowser for help on using the browser.