1 // A simple mediator implementation. 2 module subscribed.mediator; 3 4 import std.traits : isCallable; 5 import std.array : replace; 6 import std.meta; 7 8 import subscribed.event; 9 10 /** 11 * A simple mediator implementation. 12 * More precisely, an event collection with a unified interface and beforeEach/afterEach hooks. 13 * 14 * Params: 15 * params - A list alternating between index types and callable types. 16 */ 17 struct Mediator(params...) 18 { 19 static assert(params.length % 2 == 0, "The parameter count must be even"); 20 21 /// The type of the (indexing) channel names. 22 alias IType = typeof(params[0]); 23 24 private 25 { 26 struct Channel(IType name_, T) if (isCallable!T) 27 { 28 alias Type = T; 29 enum name = name_; 30 } 31 32 template channels(params...) 33 { 34 static if (params.length == 0) 35 { 36 alias channels = AliasSeq!(); 37 } 38 else 39 { 40 static assert( 41 is(typeof(params[0]) == IType) && isCallable!(params[1]), 42 "Parameters must alternate between index types and callable types" 43 ); 44 45 alias channels = AliasSeq!(Channel!(params[0], params[1]), channels!(params[2..$])); 46 } 47 } 48 49 mixin template bindChannel(c) 50 { 51 private 52 { 53 alias EventType = Event!(c.Type); 54 EventType event; 55 } 56 57 void on(IType channel : c.name)(EventType.ListenerType[] listeners...) 58 { 59 event.append(listeners); 60 } 61 62 void off(IType channel : c.name)(EventType.ListenerType[] listeners...) 63 { 64 event.remove(listeners); 65 } 66 67 void emit(IType channel : c.name)(EventType.ParamTypes params) 68 { 69 foreach (listener; beforeEach.listeners) 70 if (!listener(channel)) 71 return; 72 73 event.call(params); 74 afterEach(channel); 75 } 76 } 77 } 78 79 /// The hook to be executed before any transition. If false is returned, the no transition occurs. 80 Event!(bool delegate(IType)) beforeEach; 81 82 /// The hook to be executed after a successful transition. 83 Event!(void delegate(IType)) afterEach; 84 85 version (D_Ddoc) 86 { 87 alias EventType = Event!(void function()); 88 89 /** 90 * A function for appending listeners to the channel event. 91 * 92 * Params: 93 * channel = The channel whose event to subscribe to. 94 * listeners = The listeners to append. 95 * 96 * See_Also: 97 * subscribed.event.Event.append 98 */ 99 void on(IType channel)(EventType.ListenerType[] listeners...); 100 101 /** 102 * A function for removing listeners from the channel event. 103 * 104 * Params: 105 * channel = The channel whose event to remove from. 106 * listeners = The listeners to remove. 107 * 108 * See_Also: 109 * subscribed.event.Event.append 110 */ 111 void off(IType channel)(EventType.ListenerType[] listeners...); 112 113 /** 114 * Calls all the registered listeners for the channel in order. 115 * 116 * Params: 117 * channel = The channel to emit a message to. 118 * params = The param tuple to call the listeners with. 119 * 120 * Returns: 121 * An array of results from the listeners. 122 * If $(DDOC_PSYMBOL EventType.ReturnType) is void, then this function also returns void. 123 * 124 * See_Also: 125 * subscribed.event.Event.call 126 */ 127 void emit(string channel)(EventType.ParamTypes params); 128 } 129 130 static foreach (channel; channels!params) 131 mixin bindChannel!channel; 132 } 133 134 /// The mediator events can be strings. 135 unittest 136 { 137 Mediator!( 138 "start", void delegate(), 139 "stop", void delegate() 140 ) mediator; 141 142 mediator.emit!"start"(); 143 } 144 145 /// The mediator events can be enum members. 146 unittest 147 { 148 enum Event { start, stop } 149 150 Mediator!( 151 Event.start, void delegate(), 152 Event.stop, void delegate() 153 ) mediator; 154 155 mediator.emit!(Event.start)(); 156 } 157 158 /// The mediator events cannot be of mixed type. 159 unittest 160 { 161 immutable canCompile = __traits(compiles, Mediator!( 162 "start", void delegate(), 163 3, void delegate() 164 )); 165 166 assert(!canCompile, "Can compile mediators with mixed index types"); 167 } 168 169 /// The mediator can subscribe, unsubscribe and broadcast events. 170 unittest 171 { 172 Mediator!( 173 "inc", void delegate(), 174 "dec", void delegate(), 175 "reset counter", void delegate() 176 ) mediator; 177 178 int counter; 179 180 void increment() 181 { 182 counter++; 183 } 184 185 void decrement() 186 { 187 counter--; 188 } 189 190 void reset() 191 { 192 counter = 0; 193 } 194 195 mediator.on!"inc"(&increment); 196 mediator.on!"dec"(&decrement); 197 mediator.on!"reset counter"(&reset); 198 199 assert(counter == 0, "Mediator functions are called before any action is performed"); 200 mediator.emit!"inc"(); 201 assert(counter == 1, "The mediator does not call one of it's functions"); 202 mediator.emit!"dec"(); 203 assert(counter == 0, "The mediator does not call one of it's functions"); 204 205 assert(counter == 0, "Mediator functions are called before any action is performed"); 206 mediator.emit!"inc"(); 207 assert(counter == 1, "The mediator does not call one of it's functions"); 208 mediator.emit!"reset counter"(); 209 assert(counter == 0, "The mediator does not call one of it's functions"); 210 211 mediator.beforeEach ~= string => false; 212 213 assert(counter == 0, "The beforeEach hook does not work"); 214 mediator.emit!"inc"(); 215 assert(counter == 0, "The beforeEach hook does not work"); 216 mediator.emit!"dec"(); 217 assert(counter == 0, "The beforeEach hook does not work"); 218 219 mediator.beforeEach.clear(); 220 221 mediator.off!"inc"(&increment); 222 mediator.off!"dec"(&decrement); 223 224 assert(counter == 0, "The mediator called one of it's functions while unregistering them"); 225 mediator.emit!"inc"(); 226 assert(counter == 0, "The mediator did not remove a listener"); 227 mediator.emit!"dec"(); 228 assert(counter == 0, "The mediator did not remove a listener"); 229 }