1 /* 2 * Copyright (c) 2017-2019 sel-project 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in all 12 * copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 * SOFTWARE. 21 * 22 */ 23 /** 24 * Copyright: Copyright (c) 2017-2019 sel-project 25 * License: MIT 26 * Authors: Kripth 27 * Source: $(HTTP github.com/sel-project/selery/source/selery/event/event.d, selery/event/event.d) 28 */ 29 module selery.event.event; 30 31 import std.algorithm : sort, canFind; 32 import std.conv : to; 33 import std..string : indexOf; 34 import std.typetuple : TypeTuple; 35 import std.traits : isAbstractClass, BaseClassesTuple, InterfacesTuple, Parameters; 36 37 import selery.util.tuple : Tuple; 38 39 alias class_t = size_t; 40 41 private enum dictionary = " abcdefghijklmonpqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.".dup; 42 43 private enum hash(T) = hashImpl(T.mangleof); 44 45 //TODO find out whether this is slow during CT 46 private class_t hashImpl(string mangle) { 47 size_t result = 1; 48 foreach(c ; mangle) { 49 result ^= (result >> 8) ^ ~(size_t.max / dictionary.indexOf(c)); 50 } 51 return result; 52 } 53 54 /* 55 * Storage for event's delegates with the event casted 56 * to a generic void pointer. 57 */ 58 private alias Delegate = Tuple!(void delegate(void*), "del", size_t, "count"); 59 60 /* 61 * Storage for callable events (already casted to the right 62 * type) ready to be ordered and called. 63 */ 64 private alias Callable = Tuple!(void delegate(), "call", size_t, "count"); 65 66 /* 67 * Count variable shared between every event listener to 68 * maintain a global registration order. 69 */ 70 private size_t count = 0; 71 72 private alias Implementations(T) = TypeTuple!(T, BaseClassesTuple!T[0..$-1], InterfacesTuple!T); 73 74 /** 75 * Generic event listener. 76 */ 77 class EventListener(O:Event, Children...) if(areValidChildren!(O, Children)) { 78 79 protected Delegate[][class_t] listeners; 80 81 /** 82 * Adds an event through a delegate. 83 * Returns: an id that can be used to unregister the event 84 */ 85 public @trusted size_t addEventListener(T)(void delegate(T) listener) if(is(T == class) && is(T : O) || is(T == interface)) { 86 this.listeners[hash!T] ~= Delegate(cast(void delegate(void*))listener, count); 87 return count++; 88 } 89 90 /// ditto 91 public @safe opOpAssign(string op : "+", T)(void delegate(T) listener) { 92 return this.addEventListener(listener); 93 } 94 95 public @safe void setListener(E...)(EventListener!(O, E) listener) { 96 foreach(hash, listeners; listener.listeners) { 97 foreach(del ; listeners) { 98 this.listeners[hash] ~= Delegate(del.del, del.count); 99 } 100 } 101 } 102 103 /** 104 * Removes an event listener using its delegate pointer. 105 * Returns: true if one or more event have been removed, false otherwise 106 * Example: 107 * --- 108 * // add 109 * example.addEventListener(&event); 110 * 111 * // remove 112 * assert(example.removeEventListener(&event)); 113 * --- 114 */ 115 public @trusted bool removeEventListener(T)(void delegate(T) listener) { 116 bool removed = false; 117 auto ptr = hash!T in this.listeners; 118 if(ptr) { 119 foreach(i, del; *ptr) { 120 if(cast(void delegate(T))del.del == listener) { 121 removed = true; 122 if((*ptr).length == 1) { 123 this.listeners.remove(hash!T); 124 break; 125 } else { 126 *ptr = (*ptr)[0..i] ~ (*ptr)[i+1..$]; 127 } 128 } 129 } 130 } 131 return removed; 132 } 133 134 /// ditto 135 public @safe bool opOpAssign(string op : "-", T)(void delegate(T) listener) { 136 return this.removeEventListener(listener); 137 } 138 139 /** 140 * Removes an event listener using its assigned id. 141 * Returns: true if the event has been removed, false otherwise 142 * Example: 143 * --- 144 * // add 145 * auto id = example.addEventListener(&event); 146 * 147 * // remove 148 * assert(example.removeEventListener(id)); 149 * --- 150 */ 151 public @safe bool removeEventListener(size_t count) { 152 foreach(i, listeners; this.listeners) { 153 foreach(j, del; listeners) { 154 if(del.count == count) { 155 if(listeners.length == 1) { 156 this.listeners.remove(i); 157 } else { 158 this.listeners[i] = listeners[0..j] ~ listeners[j+1..$]; 159 } 160 return true; 161 } 162 } 163 } 164 return false; 165 } 166 167 /// ditto 168 public @safe bool opOpAssign(string op : "-")(size_t count) { 169 return this.removeEventListener(count); 170 } 171 172 /** 173 * Calls an event. 174 * Events are always called in the order they are registered, even 175 * in the inheritance. 176 */ 177 public void callEvent(T:O)(ref T event) if(is(T == class) && !isAbstractClass!T) { 178 Callable[] callables = this.callablesOf(event); 179 if(callables.length) { 180 sort!"a.count < b.count"(callables); 181 foreach(callable ; callables) { 182 callable.call(); 183 static if(is(T : Cancellable)) { 184 if(event.cancelled) break; 185 } 186 } 187 } 188 } 189 190 protected Callable[] callablesOf(T:O)(ref T event) if(is(T == class) && !isAbstractClass!T) { 191 Callable[] callables; 192 foreach_reverse(E ; Implementations!T) { 193 auto ptr = hash!E in this.listeners; 194 if(ptr) { 195 foreach(i, del; *ptr) { 196 callables ~= this.createCallable!E(event, del.del, del.count); 197 } 198 } 199 } 200 static if(staticDerivateIndexOf!(T, Children) >= 0) { 201 callables ~= mixin("event." ~ Children[staticDerivateIndexOf!(T, Children)+1] ~ ".callablesOf(event)"); 202 } 203 return callables; 204 } 205 206 private Callable createCallable(E, T)(ref T event, void delegate(void*) del, size_t count) { 207 return Callable((){(cast(void delegate(E))del)(event);}, count); 208 } 209 210 /** 211 * Calls an event only if it exists. 212 * This should be used when a event is used only to notify 213 * the plugin of it. 214 * Returns: the instance of the event or null if the event hasn't been called 215 */ 216 public T callEventIfExists(T:O)(lazy Parameters!(T.__ctor) args) if(is(T == class) && !isAbstractClass!T) { 217 T callImpl() { 218 T event = new T(args); 219 this.callEvent(event); 220 return event; 221 } 222 foreach_reverse(E ; Implementations!T) { 223 if(hash!E in this.listeners) return callImpl(); 224 } 225 static if(staticDerivateIndexOf!(T, Children) >= 0) { 226 alias C = Children[staticDerivateIndexOf!(T, Children)]; 227 static assert(is(typeof(args[0]) : EventListener!O), T.stringof ~ ".__ctor[0] must extend " ~ (EventListener!O).stringof); 228 foreach_reverse(E ; Implementations!T) { 229 if(hash!E in args[0].listeners) return callImpl(); 230 } 231 } 232 return null; 233 } 234 235 /** 236 * Calls a cancellable event using callEventIfExists. 237 * Returns: true if the event has been called and cancelled, false otherwise 238 */ 239 public bool callCancellableIfExists(T:O)(lazy Parameters!(T.__ctor) args) if(is(T == class) && !isAbstractClass!T && is(T : Cancellable)) { 240 T event = this.callEventIfExists!T(args); 241 return event !is null && event.cancelled; 242 } 243 244 } 245 246 private bool areValidChildren(T, C...)() { 247 static if(C.length % 2 != 0) return false; 248 foreach(immutable i, E; C) { 249 static if(i % 2 == 0) { 250 static if(!is(E : T) || !is(E == interface)) return false; 251 } else { 252 static if(!__traits(hasMember, C[i-1], E)) return false; 253 } 254 } 255 return true; 256 } 257 258 alias staticDerivateIndexOf(T, E...) = staticDerivateIndexOfImpl!(0, T, E); 259 260 private template staticDerivateIndexOfImpl(size_t index, T, E...) { 261 static if(E.length == 0) { 262 enum ptrdiff_t staticDerivateIndexOfImpl = -1; 263 } else static if(is(E[0] == interface) && is(T : E[0])) { 264 enum ptrdiff_t staticDerivateIndexOfImpl = index; 265 } else { 266 alias staticDerivateIndexOfImpl = staticDerivateIndexOfImpl!(index+1, T, E[1..$]); 267 } 268 } 269 270 /** 271 * Base interface of the event. Every valid event instance (that is not an interface) 272 * must implement this interface. 273 */ 274 interface Event {} 275 276 /** 277 * Indicates that the event is cancellable and its propagation 278 * can be stopped by plugins. 279 */ 280 interface Cancellable { 281 282 /** 283 * Cancels the event. 284 * A cancelled event is not propagated further to the next listeners, 285 * if there's any. 286 * Example: 287 * --- 288 * example += (ExampleEvent event){ log("1"); }; 289 * example += (ExampleEvent event){ log("2"); event.cancel(); }; 290 * example += (ExampleEvent event){ log("3"); }; 291 * assert(event.callCancellableIfExists!ExampleEvent()); 292 * --- 293 * The example will print 294 * --- 295 * 1 296 * 2 297 * --- 298 */ 299 public pure nothrow @safe @nogc void cancel(); 300 301 /** 302 * Indicates whether the event has been cancelled. 303 * A cancelled event cannot be uncancelled. 304 */ 305 public pure nothrow @property @safe @nogc bool cancelled(); 306 307 /// ditto 308 alias canceled = cancelled; 309 310 public static mixin template Implementation() { 311 312 private bool _cancelled; 313 314 public override pure nothrow @safe @nogc void cancel() { 315 this._cancelled = true; 316 } 317 318 public override pure nothrow @property @safe @nogc bool cancelled() { 319 return this._cancelled; 320 } 321 322 } 323 324 public static mixin template FinalImplementation() { 325 326 public final override pure nothrow @safe @nogc void cancel() { 327 super.cancel(); 328 } 329 330 public final override pure nothrow @property @safe @nogc bool cancelled() { 331 return super.cancelled(); 332 } 333 334 } 335 336 } 337 338 /// ditto 339 alias Cancelable = Cancellable; 340 341 class CancellableOf { 342 343 static CancellableOf instance; 344 345 static this() { 346 instance = new CancellableOf(); 347 } 348 349 void createCancellable(T:Cancellable)(T event) { 350 event.cancel(); 351 } 352 353 }