1 // Copyright (c) 2013, 2014 Heapsource.com and Contributors - http://www.heapsource.com
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a copy
4 // of this software and associated documentation files (the "Software"), to deal
5 // in the Software without restriction, including without limitation the rights
6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 // copies of the Software, and to permit persons to whom the Software is
8 // furnished to do so, subject to the following conditions:
9 //
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
12 //
13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 // THE SOFTWARE.
20 //
21 
22 module events;
23 
24 import std.algorithm;
25 import std.container;
26 import std..string;
27 import std.range;
28 import core.thread : Fiber;
29 
30 enum EventOperation {
31     Unknown,
32     Added,
33     Removed
34 }
35 
36 abstract class Event(TReturn, Args...) {
37     alias TReturn delegate(Args) delegateType;
38     protected:
39         abstract void add(delegateType item);
40 
41     public: 
42 
43         void addSync(delegateType item) {
44             this.add(item);
45         }
46 
47         void addAsync(delegateType item) {
48             auto fibered = delegate TReturn(Args args) {
49                 static if (is( TReturn == void )) {
50                     // it's void returning, don't do anything
51                     Fiber fiber = new Fiber( {
52                         item(args);             
53                     });
54                     fiber.call;
55                 } else {
56                     // execute saving the last result
57                     TReturn v;
58                     Fiber fiber = new Fiber( {
59                         v = item(args);             
60                     });
61                     fiber.call;
62                     return v;
63                 }
64             };
65             this.add(fibered);
66         }
67 }
68 
69 class Action(TReturn, Args...) : Event!(TReturn, Args) {
70     alias TReturn delegate(Args) delegateType;
71     alias void delegate(delegateType) handlerType;
72 
73     private: 
74     handlerType _handler;
75 
76     protected:
77     override void add(delegateType item) {
78         _handler(item);
79     }
80 
81     public:
82     this(handlerType handler) {
83         _handler = handler;
84     }
85     auto opBinary(string op)(delegateType rhs) {
86         static if (op == "^") {
87             static assert(0, "Operator ^ is only valid for events, use ^= instead");
88         }
89         else static if (op == "^^") {
90             static assert(0, "Operator ^^ is only valid for events, use ^^= instead");
91         }
92         else static assert(0, "Operator "~op~" not implemented");
93         return this;
94     }
95     auto opOpAssign(string op)(delegateType rhs) {
96         static if (op == "^") {
97             this.add(rhs);
98         }
99         else static if (op == "^^") {
100             this.addAsync(rhs);
101         }
102         else static assert(0, "Operator "~op~" not implemented");
103         return this;
104     }
105 }
106 
107 class EventList(TReturn, Args...) : Event!(TReturn, Args) {
108         alias void delegate(Trigger trigger, bool activated) activationDelegate;
109     private:
110         delegateType[] _list;
111         Trigger _trigger;
112 
113     protected:
114         final override void add(delegateType item) {
115             auto oldCount = normalizedCount;
116             onAdd(item, oldCount);
117         }
118     public:
119 
120         final void remove(delegateType item) {
121             auto oldCount = normalizedCount;
122             onRemove(item, oldCount);
123         }
124         @property bool active() {
125             return normalizedCount != 0;
126         }
127 
128         final class Trigger {
129             package:
130 
131             // protect constructor, use EventList.own instead
132             this() {
133 
134             }
135 
136             public:
137 
138             void delegate(EventOperation operation, delegateType item) changed;
139 
140             activationDelegate activation;
141 
142             auto opCall(Args args) {
143                 return execute(args);
144             }
145 
146             auto execute(Args args) {
147                 static if (is( TReturn == void )) {
148                     // it's void returning, don't do anything
149                     foreach(d;_list) {
150                         this.outer.onExecute(d, args);
151                     }
152                 } else {
153                     // execute saving the last result
154                     TReturn v;
155                     foreach(d;_list) {
156                         v = this.outer.onExecute(d, args);
157                     }
158                     return v;
159                 }
160             }
161 
162             @property size_t count() {
163                 return _list.length;
164             }
165 
166             void reset() {
167                 foreach(d;_list) {
168                     remove(d);
169                 }
170             }
171 
172         }
173 
174         auto own() {
175             return this.own(null);
176         }
177 
178         auto own(activationDelegate activation) {
179             if(_trigger !is null) {
180                 throw new Exception("Event already owned");
181             }
182             _trigger = new Trigger;
183             _trigger.activation = activation;
184             return _trigger;
185         }
186 
187         auto opOpAssign(string op)(delegateType rhs) {
188             static if (op == "^") {
189                 static assert(0, "Operator ^= is only valid for actions, use ^ instead");
190             }
191             else static if (op == "^^") {
192                 static assert(0, "Operator ^^= is only valid for actions, use ^^ instead");
193             }
194             else static assert(0, "Operator "~op~" not implemented");
195             return this;
196         }
197 
198         auto opBinary(string op)(delegateType rhs) {
199             static if (op == "^") {
200                 this.add(rhs);
201             }
202             else static if (op == "^^") {
203                 this.addAsync(rhs);
204             }
205             else static assert(0, "Operator "~op~" not implemented");
206             return this;
207         }
208 
209 
210     protected:
211 
212         TReturn onExecute(delegateType item, Args args) {
213             return item(args);
214         }
215 
216         void onAdd(delegateType item, size_t oldCount) {
217             _list ~= item;
218             this.onChanged(EventOperation.Added, item, oldCount);
219         }
220 
221         void onRemove(delegateType item, size_t oldCount) {
222             import std.algorithm : countUntil, remove;
223             auto i = _list.countUntil(item);
224             if(i > -1) {
225                 _list = _list.remove(i);
226             }
227             this.onChanged(EventOperation.Removed, item, oldCount);
228         }
229         
230         @property size_t normalizedCount() {
231             return _trigger !is null ? _trigger.count : 0;
232         }
233         
234         void onChanged(EventOperation operation, delegateType item, size_t oldCount) {
235             if(_trigger !is null) {
236                 if(_trigger.changed) {
237                     _trigger.changed(operation, item);
238                 }
239                 auto subscriptionCount = normalizedCount;
240                 if(_trigger.activation !is null && ((oldCount == 0 && subscriptionCount == 1) || (oldCount == 1 && subscriptionCount == 0))) {
241                     _trigger.activation(_trigger, this.active); 
242                 }
243             }
244         }
245 
246 }
247 
248 enum StrictTrigger {
249     Sync = "Sync", // use the current fiber to execute all the handler
250     Async = "Async" // create a new fiber for every handler
251 }
252 
253 class StrictEventList(StrictTrigger style, TReturn, Args...) : EventList!(TReturn, Args) {
254     public:
255         auto opBinary(string op)(delegateType rhs) {
256             static if (op == "^") {
257                 static if(style == StrictTrigger.Async) {
258                     static assert(0, "Operator ^ is disallowed in strictly Async events, use ^^ instead for an async subscription");
259                 }
260                 else {
261                     super.opBinary!op(rhs);
262                 }
263             }
264             else static if (op == "^^") {
265                 static if(style == StrictTrigger.Sync) {
266                     static assert(0, "Operator ^^ is disallowed in strictly Sync events, use ^ instead for a sync subscription");
267                 }
268                 else {
269                     super.opBinary!op(rhs);
270                 }
271             }
272             else static assert(0, "Operator "~op~" not implemented");
273             return this;
274         }
275 
276         static if(style == StrictTrigger.Sync) {
277             @disable
278             final override void addAsync(delegateType item) {
279                 assert(0, "addAsync is disallowed in strictly Sync events, use addSync instead for a sync subscription");
280             }
281         }
282 
283         static if(style == StrictTrigger.Async) {
284             @disable
285             final override void addSync(delegateType item) {
286                 assert(0, "addSync is disallowed in strictly Async events, use addAsync instead for a async subscription");
287             }
288         }
289 }
290 
291 class StrictAction(StrictTrigger style, TReturn, Args...) : Action!(TReturn, Args) {
292     public:
293         this(handlerType handler) {
294             super(handler);
295         }
296 
297         auto opOpAssign(string op)(delegateType rhs) {
298             static if (op == "^") {
299                 static if(style == StrictTrigger.Async) {
300                     static assert(0, "Operator ^= is disallowed in strictly Async actions, use ^^= instead for an async subscription");
301                 }
302                 else {
303                     super.opOpAssign!op(rhs);
304                 }
305             }
306             else static if (op == "^^") {
307                 static if(style == StrictTrigger.Sync) {
308                     static assert(0, "Operator ^^= is disallowed in strictly Sync actions, use ^= instead for a sync subscription");
309                 }
310                 else {
311                     super.opOpAssign!op(rhs);
312                 }
313             }
314             else static assert(0, "Operator "~op~" not implemented");
315             return this;
316         }
317 
318         static if(style == StrictTrigger.Sync) {
319             @disable
320             final override void addAsync(delegateType item) {
321                 assert(0, "addAsync is disallowed in strictly Sync actions, use addSync instead for a sync subscription");
322             }
323         }
324 
325         static if(style == StrictTrigger.Async) {
326             @disable
327             final override void addSync(delegateType item) {
328                 assert(0, "addSync is disallowed in strictly Async actions, use addAsync instead for a async subscription");
329             }
330         }
331 }