1 module carbon.event;
2 
3 import std.algorithm,
4        std.variant,
5        std.traits,
6        std.signals,
7        std.functional;
8 
9 version(unittest) import std.stdio;
10 
11 /**
12 
13 */
14 struct FiredContext
15 {
16     Variant sender;
17     string file;
18     size_t line;
19     string funcName;
20     string prettyFuncName;
21 }
22 
23 
24 /**
25 
26 */
27 interface StrongConnectedSlotTag {}
28 
29 
30 private
31 final class SignalImpl(T...)
32 {
33     void connect(string name, Class)(Class obj)
34     if(hasMember!(Class, name) && (is(Class == class) || is(Class == interface)))
35     {
36         MixedInSignal.connect(mixin(`&obj.` ~ name));
37     }
38 
39 
40     void disconnect(string name, Class)(Class obj)
41     if(hasMember!(Class, name) && (is(Class == class) || is(Class == interface)))
42     {
43         MixedInSignal.disconnect(mixin(`&obj.` ~ name));
44     }
45 
46 
47     SlotTag strongConnect(Callable)(Callable func)
48     if(is(typeof((T args) { func(args); })))
49     {
50         static final class SlotImpl : SlotTag
51         {
52             override
53             void dg(T args){ _f(args); }
54 
55             override
56             bool opEquals(Object rhs)
57             {
58                 if(auto o = cast(SlotImpl)rhs)
59                     return this._f == o._f;
60                 else
61                     return false;
62             }
63 
64           private:
65             Callable _f;
66         }
67 
68         auto slot = new SlotImpl;
69         slot._f = func;
70         _slotSet[slot] = true;
71 
72         this.connect!"dg"(slot);
73         return slot;
74     }
75 
76 
77     void disconnect(ref SlotTag tag)
78     {
79         _slotSet.remove(tag);
80         destroy(tag);
81         tag = null;
82     }
83 
84 
85     void strongDisconnect(ref SlotTag tag)
86     {
87         this.disconnect(tag);
88         tag = null;
89     }
90 
91 
92     void emitImpl(T args)
93     {
94         MixedInSignal.emit(args);
95     }
96 
97 
98   private
99   {
100     mixin Signal!(T) MixedInSignal;
101   }
102 
103 
104     interface SlotTag : StrongConnectedSlotTag
105     {
106         void dg(T);
107     }
108 
109   private:
110     bool[SlotTag] _slotSet;
111 }
112 
113 
114 
115 /**
116 
117 */
118 final class EventManager(T...)
119 {
120     this()
121     {
122         _noarg = new SignalImpl!();
123         _simple = new SignalImpl!T;
124         _withContext = new SignalImpl!(FiredContext, T);
125     }
126 
127 
128     void disable()
129     {
130         _disabled = true;
131     }
132 
133 
134     void enable()
135     {
136         _disabled = false;
137     }
138 
139 
140     void connect(string name, Class)(Class obj)
141     if(hasMember!(Class, name) && (is(Class == class) || is(Class == interface)))
142     {
143       static if(is(typeof((FiredContext ctx, T args){ mixin(`obj.` ~ name ~ `(ctx, args);`); })))
144         _withContext.connect!name(obj);
145       else static if(is(typeof((T args){ mixin(`obj.` ~ name ~ `(args);`); })))
146         _simple.connect!name(obj);
147       else
148         _noarg.connect!name(obj);
149     }
150 
151 
152     void disconnect(string name, Class)(Class obj)
153     if(hasMember!(Class, name) && (is(Class == class) || is(Class == interface)))
154     {
155       static if(is(typeof((FiredContext ctx, T args){ mixin(`obj.` ~ name ~ `(ctx, args);`); })))
156         _withContext.disconnect!name(obj);
157       else static if(is(typeof((T args){ mixin(`obj.` ~ name ~ `(args);`); })))
158         _simple.disconnect!name(obj);
159       else
160         _noarg.disconnect!name(obj);
161     }
162 
163 
164     StrongConnectedSlotTag strongConnect(void function() func)
165     {
166         return _noarg.strongConnect(func);
167     }
168 
169 
170     StrongConnectedSlotTag strongConnect(void function(T) func)
171     {
172         return _simple.strongConnect(func);
173     }
174 
175 
176     StrongConnectedSlotTag strongConnect(void function(FiredContext, T) func)
177     {
178         return _withContext.strongConnect(func);
179     }
180 
181 
182     StrongConnectedSlotTag strongConnect(void delegate() func)
183     {
184         return _noarg.strongConnect(func);
185     }
186 
187 
188     StrongConnectedSlotTag strongConnect(void delegate(T) func)
189     {
190         return _simple.strongConnect(func);
191     }
192 
193 
194     StrongConnectedSlotTag strongConnect(void delegate(FiredContext, T) func)
195     {
196         return _withContext.strongConnect(func);
197     }
198 
199 
200     StrongConnectedSlotTag strongConnect(Callable)(Callable func)
201     {
202       static if(is(typeof((FiredContext ctx, T args){ func(ctx, args); })))
203         return _withContext.strongConnect(func);
204       else static if(is(typeof((T args){ func(args); })))
205         return _simple.strongConnect(func);
206       else
207         return _noarg.strongConnect(func);
208     }
209 
210 
211     void strongDisconnect(ref StrongConnectedSlotTag slotTag)
212     {
213         if(auto s1 = cast(_withContext.SlotTag)slotTag){
214             _withContext.strongDisconnect(s1);
215             slotTag = null;
216         }
217         else if(auto s2 = cast(_simple.SlotTag)slotTag){
218             _simple.strongDisconnect(s2);
219             slotTag = null;
220         }
221         else if(auto s3 = cast(_noarg.SlotTag)slotTag){
222             _noarg.strongDisconnect(s3);
223             slotTag = null;
224         }
225     }
226 
227 
228     void disconnect(ref StrongConnectedSlotTag slotTag)
229     {
230         this.strongDisconnect(slotTag);
231         slotTag = null;
232     }
233 
234 
235     void emit()(T args, string file = __FILE__, size_t line = __LINE__,
236                 string func = __FUNCTION__, string preFunc = __PRETTY_FUNCTION__)
237     {
238         emit(null, args, file, line, func, preFunc);
239     }
240 
241 
242     void emit(S)(S sender, T args, string file = __FILE__, size_t line = __LINE__,
243                                  string func = __FUNCTION__, string preFunc = __PRETTY_FUNCTION__)
244     {
245         FiredContext ctx;
246         ctx.sender = sender;
247         ctx.file = file;
248         ctx.line = line;
249         ctx.funcName = func;
250         ctx.prettyFuncName = preFunc;
251 
252         emit(ctx, args);
253     }
254 
255 
256     void emit()(FiredContext ctx, T args)
257     {
258         if(!_disabled){
259             _noarg.emit();
260             _simple.emit(args);
261             _withContext.emit(ctx, args);
262         }
263     }
264 
265 
266   private:
267     bool _disabled;
268     SignalImpl!() _noarg;
269     SignalImpl!T _simple;
270     SignalImpl!(FiredContext, T) _withContext;
271 }
272 
273 
274 ///
275 unittest
276 {
277     auto event = new EventManager!int();
278 
279     int sum;
280     auto tag1 = event.strongConnect(delegate(a){ sum += a; });
281 
282     event.emit(12);
283     assert(sum == 12);
284 
285     auto tag2 = event.strongConnect(() { sum += 2; });
286 
287     event.emit(4);
288     assert(sum == 18);  // add 2 + 4
289 
290     event.disconnect(tag1);
291     event.emit(12);
292     assert(sum == 20);  // only add 2
293 
294     event.disconnect(tag2);
295     event.emit(5);
296     assert(sum == 20);
297 }
298 
299 
300 unittest
301 {
302     scope(failure) {writefln("Unittest failure :%s(%s)", __FILE__, __LINE__); stdout.flush();}
303     scope(success) {writefln("Unittest success :%s(%s)", __FILE__, __LINE__); stdout.flush();}
304 
305     auto event = new EventManager!bool();
306 
307     bool bCalled = false;
308     auto tag = event.strongConnect(delegate(FiredContext ctx, bool b){
309         assert(b);
310         assert(ctx.sender == null);
311         bCalled = true;
312     });
313 
314     event.emit(true);
315     assert(bCalled);
316 
317     bCalled = false;
318     event.disable();
319     event.emit(true);
320     assert(!bCalled);
321 
322     event.enable();
323     event.emit(true);
324     assert(bCalled);
325 
326     bCalled = false;
327     event.disconnect(tag);
328     event.emit(true);
329     assert(!bCalled);
330     assert(tag is null);
331 }
332 
333 
334 /**
335 
336 */
337 class SeqEventManager(size_t N, T...)
338 {
339     this()
340     {
341         foreach(i; 0 .. N)
342             _signals[i] = new EventManager!T;
343     }
344 
345 
346     EventManager!T opIndex(size_t i)
347     in{
348         assert(i < N);
349     }
350     body{
351         return _signals[i];
352     }
353 
354 
355     void disable()
356     {
357         _disable = true;
358     }
359 
360 
361     void enable()
362     {
363         _disable = false;
364     }
365 
366 
367     void emit()(auto ref T args, string file = __FILE__, size_t line = __LINE__,
368                                      string func = __FUNCTION__, string preFunc = __PRETTY_FUNCTION__)
369     {
370         emit(null, forward!args, file, line, func, preFunc);
371     }
372 
373 
374     void emit(S)(S sender, auto ref T args, string file = __FILE__, size_t line = __LINE__,
375                                      string func = __FUNCTION__, string preFunc = __PRETTY_FUNCTION__)
376     {
377         FiredContext ctx;
378         ctx.sender = sender;
379         ctx.file = file;
380         ctx.line = line;
381         ctx.funcName = func;
382         ctx.prettyFuncName = preFunc;
383 
384         emit(ctx, forward!args);
385     }
386 
387 
388     void emit()(FiredContext ctx, auto ref T args)
389     {
390         if(!_disable){
391             foreach(i, ref e; _signals)
392                 e.emit(ctx, forward!args);
393         }
394     }
395 
396 
397   private:
398     EventManager!T[N] _signals;
399     bool _disable;
400 }
401 
402 ///
403 unittest
404 {
405     scope(failure) {writefln("Unittest failure :%s(%s)", __FILE__, __LINE__); stdout.flush();}
406     scope(success) {writefln("Unittest success :%s(%s)", __FILE__, __LINE__); stdout.flush();}
407 
408     auto event = new SeqEventManager!(3, bool);
409 
410     size_t cnt;
411     size_t[3] ns;
412     event[0].strongConnect(delegate(b){
413         assert(b);
414         ns[0] = cnt;
415         ++cnt;
416     });
417 
418     event[1].strongConnect(delegate(b){
419         assert(b);
420         ns[1] = cnt;
421         ++cnt;
422     });
423 
424     event[2].strongConnect(delegate(b){
425         assert(b);
426         ns[2] = cnt;
427         ++cnt;
428     });
429 
430     event.emit(true);
431     assert(cnt == 3);
432     assert(ns[] == [0, 1, 2]);
433 }