1 /** 2 Utility for emitting/subscribing to events. 3 */ 4 module dcord.util.emitter; 5 6 import vibe.core.concurrency, 7 vibe.core.core; 8 9 import std.stdio, 10 std.algorithm, 11 std.array, 12 std.variant, 13 std.datetime, 14 core.time; 15 16 import dcord.util.errors; 17 18 enum EmitterOrder { 19 BEFORE = 1, 20 UNSPECIFIED = 2, 21 AFTER = 3, 22 } 23 24 immutable EmitterOrder[] EmitterOrderAll = [ 25 EmitterOrder.BEFORE, 26 EmitterOrder.UNSPECIFIED, 27 EmitterOrder.AFTER, 28 ]; 29 30 /** 31 Special exception used to stop the emission of an event. 32 */ 33 class EmitterStop: Exception { mixin ExceptionMixin; } 34 35 /** 36 Interface implementing a single call method for emitting an event. 37 */ 38 interface BoundEmitter { 39 void call(string, Variant); 40 } 41 42 /** 43 Base event listener implementation. 44 */ 45 class BaseEventListener: BoundEmitter { 46 EmitterOrder order; 47 Emitter e; 48 string name; 49 50 /** 51 Unbinds this listener forever. 52 */ 53 void unbind() { 54 this.e.listeners[this.order][this.name] = this.e.listeners[this.order][this.name].filter!( 55 (li) => li != this).array; 56 } 57 58 void call(string name, Variant arg) { 59 60 } 61 } 62 63 /** 64 Listener for a specific event. 65 */ 66 class EventListener: BaseEventListener { 67 void delegate(Variant arg) func; 68 69 this(Emitter e, string name, EmitterOrder order, void delegate(Variant) f) { 70 this.e = e; 71 this.name = name; 72 this.order = order; 73 this.func = f; 74 } 75 76 override void call(string name, Variant arg) { 77 this.func(arg); 78 } 79 } 80 81 /** 82 Array of EventListeners 83 */ 84 alias EventListenerArray = EventListener[]; 85 86 /** 87 Listener for all events. 88 */ 89 class AllEventListener: BaseEventListener { 90 void delegate(string, Variant) func; 91 92 this(Emitter e, EmitterOrder order, void delegate(string, Variant) f) { 93 this.e = e; 94 this.order = order; 95 this.func = f; 96 } 97 98 override void call(string name, Variant arg) { 99 this.func(name, arg); 100 } 101 } 102 103 /** 104 Event emitter which allows the emission and subscription of events. 105 */ 106 class Emitter { 107 BoundEmitter[][string][EmitterOrder] listeners; 108 109 /** 110 Listen to an event by string, ignoring the actual event in the callback. 111 */ 112 EventListener on(string event, void delegate() f, EmitterOrder order=EmitterOrder.UNSPECIFIED) { 113 auto li = new EventListener(this, event, order, (arg) { 114 try { f(); } catch (EmitterStop) { return; } 115 }); 116 117 this.listeners[order][event] ~= li; 118 return li; 119 } 120 121 /** 122 Listen to an event based on its type. 123 */ 124 EventListener listen(T)(void delegate(T) f, EmitterOrder order=EmitterOrder.UNSPECIFIED) { 125 auto li = new EventListener(this, T.stringof, order, (arg) { 126 try { f(arg.get!T); } catch (EmitterStop) { return; } 127 }); 128 129 this.listeners[order][T.stringof] ~= li; 130 return li; 131 } 132 133 /** 134 Listen to an event based on its name. 135 */ 136 EventListener listenRaw(string event, void delegate(Variant) f, EmitterOrder order=EmitterOrder.UNSPECIFIED) { 137 auto li = new EventListener(this, event, order, f); 138 this.listeners[order][event] ~= li; 139 return li; 140 } 141 142 /** 143 Listen to all events. 144 */ 145 AllEventListener listenAll(void delegate(string, Variant) f, EmitterOrder order=EmitterOrder.UNSPECIFIED) { 146 auto li = new AllEventListener(this, order, f); 147 this.listeners[order][""] ~= li; 148 return li; 149 } 150 151 /** 152 Emit an event. 153 */ 154 void emit(T)(T obj) { 155 this.emitByName!T(T.stringof, obj, false); 156 this.emitByName!T(T.stringof, obj, true); 157 } 158 159 /// Emit an event by name. 160 private void emitByName(T)(string name, T obj, bool all) { 161 Variant v; 162 163 if (all) name = ""; 164 165 foreach (order; EmitterOrderAll) { 166 if (!(order in this.listeners)) continue; 167 if (!(name in this.listeners[order])) continue; 168 if (!v.hasValue()) v = Variant(obj); 169 170 foreach (func; this.listeners[order][name]) { 171 runTask({ 172 try { 173 func.call(name, v); 174 } catch (Exception e) { 175 writeln(e.toString); 176 } 177 }); 178 } 179 } 180 } 181 }