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 }