1 /*
2  * Copyright (c) 2017-2018 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: 2017-2018 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 }