1 /// An event structure representing a one-to-many function/delegate relationship. 2 module subscribed.event; 3 4 import std.traits : isCallable, ParameterTypeTuple, ReturnTypeTpl = ReturnType; 5 import std.experimental.allocator; 6 7 import subscribed.slist; 8 9 /** 10 * An event structure representing a one-to-many function/delegate relationship. 11 * It mimics a function by overriding the call operator. 12 * It also implements the bidirectional range interface. 13 * 14 * Params: 15 * T = The listener type this event contains. 16 */ 17 struct Event(T) if (isCallable!T) 18 { 19 /// The listeners' type. 20 alias ListenerType = T; 21 22 version (D_Ddoc) 23 { 24 /// The event's return type. 25 alias ReturnType = void; 26 } 27 else 28 { 29 static if (is(ReturnTypeTpl!T == void)) 30 alias ReturnType = void; 31 else 32 alias ReturnType = ReturnTypeTpl!T[]; 33 } 34 35 /// The event's argument type tuple. 36 alias ParamTypes = ParameterTypeTuple!T; 37 38 private 39 { 40 SList!T _listeners; 41 int _size; 42 } 43 44 ~this() 45 { 46 _listeners.destroy(); 47 } 48 49 /// The number of listeners. 50 size_t size() const 51 { 52 return _size; 53 } 54 55 /// A range array of all the listeners. 56 auto listeners() 57 { 58 return _listeners; 59 } 60 61 /** 62 * A boolean property indicating whether there are listeners. 63 * Part of the bidirectional range interface. 64 */ 65 bool empty() const 66 { 67 return _listeners.empty; 68 } 69 70 /** 71 * Get the first listener or throw an error if there are no listeners. 72 * Part of the bidirectional range interface. 73 */ 74 T front() const 75 { 76 return _listeners.front; 77 } 78 79 /** 80 * Remove the first listener or throw an error if there are no listeners. 81 * Part of the bidirectional range interface. 82 */ 83 void popFront() 84 { 85 _listeners.popFront(); 86 _size -= 1; 87 } 88 89 /** 90 * Prepend listeners to the listener collection. 91 * 92 * Params: 93 * listeners = The listeners to insert. 94 */ 95 void prepend(T[] listeners...) 96 { 97 foreach (listener; listeners) 98 { 99 _size++; 100 _listeners.insertFront(listener); 101 } 102 } 103 104 /** 105 * Get the last listener or throw an error if there are no listeners. 106 * Part of the bidirectional range interface. 107 */ 108 T back() const 109 { 110 return _listeners.back; 111 } 112 113 /** 114 * Remove the last listener or throw an error if there are no listeners. 115 * Part of the bidirectional range interface. 116 */ 117 void popBack() 118 { 119 _listeners.popBack(); 120 _size -= 1; 121 } 122 123 /** 124 * Appends a listener to the listener collection. 125 * 126 * Params: 127 * listeners = The listeners to insert. 128 */ 129 void append(T[] listeners...) 130 { 131 foreach (listener; listeners) 132 { 133 _size++; 134 _listeners.insertBack(listener); 135 } 136 } 137 138 /** 139 * Removed all occurrences of the given listeners. 140 * 141 * Params: 142 * listeners = The listeners to remove. 143 */ 144 void remove(T[] listeners...) 145 { 146 foreach (listener; listeners) 147 _size -= _listeners.removeAll(listener); 148 } 149 150 /** 151 * Clears all listeners. 152 */ 153 void clear() 154 { 155 _listeners.clear(); 156 _size = 0; 157 } 158 159 /** 160 * Calls all the registered listeners in order. 161 * 162 * Params: 163 * params = The param tuple to call the listener with. 164 */ 165 void call(ParamTypes params) 166 { 167 foreach (listener; _listeners) 168 listener(params); 169 } 170 171 /// Aliases $(DDOC_PSYMBOL call). 172 void opCall(ParamTypes params) 173 { 174 return call(params); 175 } 176 177 /** 178 * Copies the event to allow multiple range-like iteration. 179 * Part of the bidirectional range interface. 180 */ 181 auto save() 182 { 183 return this; 184 } 185 186 /// 187 auto opSlice() 188 { 189 return this; 190 } 191 192 /// Aliases $(DDOC_PSYMBOL append). 193 void opOpAssign(string s: "~")(T listener) 194 { 195 append(listener); 196 } 197 198 /// Aliases $(DDOC_PSYMBOL remove). 199 void opOpAssign(string s: "-")(T listener) 200 { 201 remove(listener); 202 } 203 } 204 205 version (unittest) 206 { 207 int add(int a, int b) 208 { 209 return a + b; 210 } 211 212 int multiply(int a, int b) 213 { 214 return a * b; 215 } 216 217 void doNothing() {} 218 } 219 220 /// Events return all their listener outputs in dynamic arrays. 221 unittest 222 { 223 import std.array : array; 224 Event!(int function(int, int)) event; 225 event.append(&add, &multiply); 226 assert(event.front()(5, 5) == 10, "The event listeners are iterated in the same order they were added in"); 227 event.popFront(); 228 assert(event.front()(5, 5) == 25, "The event listeners are iterated in the same order they were added in"); 229 } 230 231 /// Adding and removing listeners is straightforward. 232 unittest 233 { 234 Event!(int function(int, int)) event; 235 event.append(&add, &multiply); 236 assert(event.size == 2, "The eveEvent!(void function())Event!(void function())nt listener count does not increase upon addition"); 237 event.remove(&add, &multiply); 238 assert(event.size == 0, "The event listener count does not decrease upon removal"); 239 } 240 241 /// You can add the same listener multiple times. When removing it however, all matching listeners get removed. 242 unittest 243 { 244 Event!(void function()) event; 245 246 event ~= &doNothing; 247 event.prepend(&doNothing); 248 event.append(&doNothing); 249 assert(event.size == 3, "The event listener does not add identical listeners"); 250 event.remove(&doNothing); 251 assert(event.empty, "The event listener does not remove identical listeners"); 252 } 253 254 /// The event is a bidirectional range of it's listeners. 255 unittest 256 { 257 import std.range : isBidirectionalRange; 258 assert(isBidirectionalRange!(Event!(void function())), "The bidirectional range interface is not implemented"); 259 260 Event!(void function()) event; 261 event ~= &doNothing; 262 const checkpoint = event; 263 264 foreach (listener; event) {} 265 266 assert(!event.empty, "The range is cleared after foreach iteration"); 267 268 for (; !event.empty; event.popFront()) {} 269 270 assert(event.empty, "The range is not cleared after manual iteration"); 271 assert(!checkpoint.empty, "The range checkpoint is cleared after iteration"); 272 } 273 274 /// Range mutation primitives work. 275 unittest 276 { 277 import std.exception : assertThrown; 278 279 Event!(void function()) event; 280 281 event ~= &doNothing; 282 assert(event.front == &doNothing, "Returns the last remaining listener"); 283 event.popFront(); 284 assertThrown(event.back, "Getting the back of an empty event does not throw"); 285 assertThrown(event.popBack(), "Popping an empty event does not throw"); 286 } 287 288 /// In case you want to remove all listeners. 289 unittest 290 { 291 Event!(void function()) event; 292 293 event ~= &doNothing; 294 assert(!event.empty, "The event has no listeners, one listener expected"); 295 event.clear(); 296 assert(event.empty, "The event is not empty"); 297 }