1 /+ 2 The MIT License (MIT) 3 4 Copyright (c) <2013> <Oleg Butko (deviator), Anton Akzhigitov (Akzwar)> 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy 7 of this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights 9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 copies of the Software, and to permit persons to whom the Software is 11 furnished to do so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in 14 all copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 THE SOFTWARE. 23 +/ 24 25 module des.util.signal; 26 27 class SignalException: Exception 28 { @safe pure nothrow this( string msg ){ super( msg ); } } 29 30 struct Signal(Args...) 31 { 32 /++ делегат +/ 33 alias void delegate(Args) slottype; 34 35 /++ "список дел" +/ 36 slottype[] slots; 37 void clear() { slots.length = 0; } 38 39 /++ добавляет в список делегат +/ 40 void connect( slottype f ) 41 { 42 if( f is null ) throw new SignalException( "signal get null delegate" ); 43 slots ~= f; 44 } 45 46 /++ вызывает все делегаты в прямом порядке +/ 47 void opCall( Args args ) 48 { foreach( slot; slots ) slot( args ); } 49 50 /++ вызывает все делегаты в обратном порядке +/ 51 void callReverse( Args args ) 52 { foreach_reverse( slot; slots ) slot( args ); } 53 } 54 55 alias Signal!() EmptySignal; 56 57 unittest 58 { 59 uint[] array; 60 EmptySignal stest; 61 stest.connect({ array ~= 0; }); 62 stest.connect({ array ~= 3; }); 63 assert( array.length == 0 ); 64 stest(); 65 assert( array.length == 2 ); 66 assert( array == [ 0, 3 ] ); 67 stest.callReverse(); 68 assert( array.length == 4 ); 69 assert( array == [ 0, 3, 3, 0 ] ); 70 71 string[] sarr; 72 Signal!string strsignal; 73 with( strsignal ) 74 { 75 connect( ( str ){ sarr ~= str; } ); 76 connect( ( str ){ sarr ~= str ~ str; } ); 77 } 78 79 assert( sarr.length == 0 ); 80 strsignal( "ok" ); 81 assert( sarr == [ "ok", "okok" ] ); 82 } 83 84 struct NamedSignal( TName, Args... ) 85 { 86 alias void delegate(Args) slottype; 87 slottype[TName] slots; 88 void clear() { slots.destroy(); } 89 90 /++ добавляет в список делегат с определённым именем +/ 91 void connect( TName name, slottype f ) 92 { 93 if( f is null ) throw new SignalException( "signal get null delegate" ); 94 slots[name] = f; 95 } 96 97 /++ вызывает делегат под именем +/ 98 bool opCall( TName name, Args args ) 99 { 100 auto fun = name in slots; 101 if( fun !is null ) 102 { 103 (*fun)( args ); 104 return true; 105 } 106 return false; 107 } 108 109 /++ позволяет вызвать несколько именнованых делегатов +/ 110 TName[] opCall( TName[] nlist, Args args ) 111 { 112 TName[] ret; 113 foreach( name; nlist ) 114 if( this.opCall( name, args ) ) 115 ret ~= name; 116 return ret; 117 } 118 } 119 120 unittest 121 { 122 size_t[] arr; 123 124 alias NamedSignal!(string, size_t) SU_sig; 125 126 SU_sig susig; 127 128 susig.connect( "add", (a){ arr ~= a; } ); 129 susig.connect( "remove", (a){ arr = arr[0 .. a] ~ arr[a+1 .. $]; } ); 130 131 susig( "add", 10 ); 132 susig( "add", 15 ); 133 134 assert( arr == [ 10, 15 ] ); 135 136 susig( "add", 20 ); 137 auto ret = susig( "add", 25 ); 138 139 assert( arr == [ 10, 15, 20, 25 ] ); 140 assert( ret == true ); 141 142 ret = susig( "remove", 1 ); 143 assert( arr == [ 10, 20, 25 ] ); 144 assert( ret == true ); 145 146 ret = susig( "get", 25 ); 147 assert( arr == [ 10, 20, 25 ] ); 148 assert( ret == false ); 149 150 auto ret_names = susig( ["add", "get"], 35 ); 151 assert( ret_names == ["add"] ); 152 assert( arr == [ 10, 20, 25, 35 ] ); 153 } 154 155 /++ 156 Расширяет концепцию Signal, выполняет в прямом порядке 157 делегаты из списка begin, затем выполняет в прямом порядке контент, 158 затем в обратном делегаты из списка end. 159 160 See_Also: Signal 161 +/ 162 struct SignalBox(Args...) 163 { 164 /++ делегат +/ 165 alias void delegate(Args) slottype; 166 167 slottype[] begin; 168 slottype[] end; 169 slottype[] list; 170 void clear() 171 { 172 begin.length = 0; 173 list.length = 0; 174 end.length = 0; 175 } 176 177 /++ добавляет пару делегатов +/ 178 void addPair( slottype b, slottype e ) 179 { 180 if( b is null ) throw new SignalException( "signalbox get null open delegate" ); 181 if( e is null ) throw new SignalException( "signalbox get null close delegate" ); 182 begin ~= b; 183 end ~= e; 184 } 185 186 /++ добавляет делегат открытия +/ 187 void addBegin( slottype f ) 188 { 189 if( f is null ) throw new SignalException( "signalbox get null open delegate" ); 190 begin ~= f; 191 } 192 193 /++ добавляет делегат закрытия +/ 194 void addEnd( slottype f ) 195 { 196 if( f is null ) throw new SignalException( "signalbox get null close delegate" ); 197 end ~= f; 198 } 199 200 /++ добавляет делегат открытия +/ 201 void addBeginF( slottype f ) 202 { 203 if( f is null ) throw new SignalException( "signalbox get null open delegate" ); 204 begin = f ~ begin; 205 } 206 207 /++ добавляет делегат закрытия +/ 208 void addEndF( slottype f ) 209 { 210 if( f is null ) throw new SignalException( "signalbox get null close delegate" ); 211 end = f ~ end; 212 } 213 214 /++ добавляет контент +/ 215 void connect( slottype f ) 216 { 217 if( f is null ) throw new SignalException( "signalbox get null content delegate" ); 218 list ~= f; 219 } 220 221 /++ производит последовательный вызов всех begin, затем контента, затем end в обратном порядке +/ 222 void opCall( Args args ) 223 { 224 call_begin( args ); 225 call_list( args ); 226 call_end( args ); 227 } 228 229 void call_begin( Args args ) { foreach( f; begin ) f( args ); } 230 void call_list( Args args ) { foreach( f; list ) f( args ); } 231 void call_end( Args args ) { foreach_reverse( f; end ) f( args ); } 232 } 233 234 alias SignalBox!() SignalBoxNoArgs; 235 236 unittest 237 { 238 string[] arr; 239 SignalBoxNoArgs stest; 240 stest.addPair({ arr ~= "open"; },{ arr ~= "close"; }); 241 stest.connect({ arr ~= "content"; }); 242 assert( arr.length == 0 ); 243 stest(); 244 assert( arr.length == 3 ); 245 assert( arr == [ "open", "content", "close" ] ); 246 auto except_test = false; 247 try 248 stest.connect( null ); 249 catch( SignalException e ) 250 except_test = true; 251 assert( except_test ); 252 } 253 254 /++ 255 Условный вызов всех слотов. 256 257 Поведение аналогичное Signal происходит в случае, если 258 все делегаты из списка условий возвращают значение, принятое за верное. 259 Иначе выполняется список altslots. 260 +/ 261 struct ConditionSignal(Args...) 262 { 263 /++ тип условия 264 265 если trueval совпадает с возвращаемым значением делегата 266 условие считается выполненым 267 +/ 268 struct condition 269 { 270 /++ делегат +/ 271 bool delegate(Args) fun; 272 /++ значение, принятое за верное +/ 273 bool trueval=false; 274 } 275 276 /++ делегат +/ 277 alias void delegate(Args) slottype; 278 279 condition[] conds; 280 slottype[] always; 281 slottype[] slots; 282 slottype[] altslots; 283 284 void clear() 285 { 286 conds.length = 0; 287 always.length = 0; 288 slots.length = 0; 289 altslots.length = 0; 290 } 291 292 /++ добавляет условие в список 293 Params: 294 f = делегат проверки 295 trueval = "верное" значение 296 +/ 297 void addCondition( bool delegate(Args) f, bool trueval=false ) 298 { 299 if( f is null ) throw new SignalException( "get null condition delegate" ~ __FUNCTION__ ); 300 conds ~= condition( f, trueval ); 301 } 302 303 /++ добавляет обязательный вызов 304 +/ 305 void connectAlways( slottype f ) 306 { 307 if( f is null ) throw new SignalException( "get null delegate" ~ __FUNCTION__ ); 308 always ~= f; 309 } 310 311 /++ добавляет слот в список slots 312 +/ 313 void connect( slottype f ) 314 { 315 if( f is null ) throw new SignalException( "get null delegate" ~ __FUNCTION__ ); 316 slots ~= f; 317 } 318 319 /++ добавляет слот в список altslots 320 +/ 321 void connectAlt( slottype f ) 322 { 323 if( f is null ) throw new SignalException( "get null delegate" ~ __FUNCTION__ ); 324 altslots ~= f; 325 } 326 327 /++ вызывает последовательно сначала условия, потом слоты 328 329 при невыполнении условия прекращает проверку 330 выполняет вызов слотов из списка altslots 331 +/ 332 bool opCall( Args args ) 333 { 334 bool ok = true; 335 336 foreach( a; always ) a( args ); 337 338 foreach( c; conds ) 339 if( c.fun( args ) != c.trueval ) 340 { 341 ok = false; 342 break; 343 } 344 345 if( ok ) foreach( f; slots ) f( args ); 346 else foreach( f; altslots ) f( args ); 347 348 return ok; 349 } 350 } 351 352 unittest 353 { 354 bool cond = true; 355 356 int[] arr; 357 358 ConditionSignal!() stest; 359 stest.addCondition( { return cond; }, true ); 360 stest.connect({ arr ~= 0; }); 361 stest.connectAlt({ arr ~= 1; }); 362 363 stest(); 364 assert( arr == [ 0 ] ); 365 cond = false; 366 stest(); 367 assert( arr == [ 0, 1 ] ); 368 }