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 }