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 }