1 module rc.handler;
2 
3 import rc.allocator;
4 
5 version(rcdebugprint)
6 {
7     pragma(msg,"!!use rc debug print");
8     import std.stdio;
9 }
10 
11 version(unittest)
12 {
13     import std.range;
14     import std.array;
15     import std.format;
16     import std.algorithm;
17 }
18 
19 ///
20 struct RCObject(T)
21 {
22     ///
23     T obj;
24     ///
25     alias obj this;
26 
27     ///
28     this(this) { incRef(); }
29 
30     ///
31     this( T o ) { obj = o; incRef(); }
32 
33     /// no increment ref
34     package static auto make( T o )
35     {
36         RCObject!T ret;
37         ret.obj = o;
38         return ret;
39     }
40 
41     version(rcdebugprint)
42     {
43         /// no increment ref
44         package static auto make( string n, T o )
45         {
46             RCObject!T ret;
47             ret.name = n;
48             ret.obj = o;
49             return ret;
50         }
51 
52         this( string n, T o ) { name = n; obj = o; incRef(); }
53         string name = "__obj__";
54     }
55 
56     /// nothrow need for dispose
57     ~this() nothrow
58     {
59         if( obj is null ) return;
60         try
61         {
62             version(rcdebugprint)
63                 writefln( "  ** %12s dtor", name );
64             decRef();
65         }
66         catch(Exception e)
67         {
68             import std.experimental.logger;
69             try errorf( "ERROR: ", e );
70             catch(Exception e) {}
71         }
72     }
73 
74     ///
75     void incRef(string file=__FILE__,size_t line=__LINE__) // file and line for debug
76     {
77         version(rcdebugprint)
78             writef( "  ** +++ ref %12s:  ", name );
79         if( obj is null )
80         {
81             version(rcdebugprint)
82                 writefln( "is null (%s:%d)", file, line );
83             return;
84         }
85         RC.incRef(obj);
86         version(rcdebugprint)
87             writefln( "ok [%d] (%s:%d)", refCount, file, line );
88     }
89 
90     /// dispose object if refCount == 0
91     void decRef(string file=__FILE__,size_t line=__LINE__) // file and line for debug
92     {
93         version(rcdebugprint)
94             writef( "  ** --- ref %12s: ", name );
95         if( obj is null )
96         {
97             version(rcdebugprint)
98                 writefln( "is null (%s:%d)", file, line ); // debug
99             return;
100         }
101         assert( refCount > 0, "not null object have 0 refs" );
102 
103         obj = RC.decRef(obj);
104 
105         version(rcdebugprint)
106         {
107             if( obj is null )
108                 writefln( " ok no refs (%s:%d)", file, line );
109             else
110                 writefln( " ok [%d] (%s:%d)", refCount, file, line );
111         }
112     }
113 
114     /// return 0 if object is null
115     size_t refCount() @property const
116     {
117         if( obj is null ) return 0;
118         return RC.refCount(obj);
119     }
120 
121     ///
122     void opAssign(X=this)( auto ref RCObject!T r )
123     {
124         version(rcdebugprint)
125             writefln( "  opAssign %12s <- %12s", name, r.name );
126         decRef();
127         obj = r.obj;
128         version(rcdebugprint)
129             name = "<" ~ r.name ~ ">";
130         incRef();
131     }
132 
133     ///
134     void opAssign(X=this)( auto ref T r )
135     {
136         version(rcdebugprint)
137             writefln( "  opAssign %12s <- %12s", name, r.name );
138         decRef();
139         obj = r;
140         version(rcdebugprint)
141             name = "<" ~ r.name ~ ">";
142         incRef();
143     }
144 }
145 
146 ///
147 struct RCArray(T)
148 {
149     /// if a slice this link to original array
150     private T[] orig;
151     ///
152     T[] work;
153 
154     ///
155     private void init( T[] origin, T[] slice )
156     {
157         if( slice !is null )
158             assert( slice.ptr >= origin.ptr &&
159                     slice.ptr < origin.ptr + origin.length,
160                     "slice is not in original" );
161 
162         orig = origin;
163         incRef();
164 
165         work = slice is null ? orig : slice;
166 
167         static if( isRCType!T )
168             foreach( ref w; work ) w.incRef;
169     }
170 
171     ///
172     alias work this;
173 
174     ///
175     this(this) { incRef(); }
176 
177     ///
178     this( T[] orig, T[] slice=null ) { init( orig, slice ); }
179 
180     /// no increment ref
181     package static auto make( T[] o )
182     {
183         RCArray!T ret;
184         ret.orig = o;
185         ret.work = o;
186         return ret;
187     }
188 
189     version(rcdebugprint)
190     {
191         /// no increment ref
192         package static auto make( string n, T[] o )
193         {
194             RCArray!T ret;
195             ret.name = n;
196             ret.orig = o;
197             ret.work = o;
198             return ret;
199         }
200 
201         this( string n, T[] orig, T[] slice=null ) // debug
202         {
203             name = n;
204             init( orig, slice );
205         }
206 
207         auto opSlice( size_t i, size_t j )
208         { return RCArray!T( name~".p", orig, work[i..j] ); }
209     }
210     else
211     {
212         ///
213         auto opSlice( size_t i, size_t j )
214         { return RCArray!T( orig, work[i..j] ); }
215     }
216 
217     ///
218     void opAssign(X=this)( auto ref RCArray!T arr )
219     {
220         decRef();
221         init( arr.orig, arr.work );
222         // incRef in init
223     }
224 
225     ///
226     void incRef(string file=__FILE__,size_t line=__LINE__) // file and line for debug
227     {
228         version(rcdebugprint)
229             writef( "  [] +++ ref %12s:  ", name );
230         if( orig is null )
231         {
232             version(rcdebugprint)
233                 writefln( "is null (%s:%d)", file, line );
234             return;
235         }
236         RC.incRef(orig);
237         version(rcdebugprint)
238             writefln( "ok [%d] (%s:%d)", refCount, file, line );
239     }
240 
241     /// dispose array if `refCount == 0`
242     void decRef(string file=__FILE__,size_t line=__LINE__) // file and line for debug
243     {
244         version(rcdebugprint)
245             writef( "  [] --- ref %12s: ", name );
246         if( orig is null )
247         {
248             version(rcdebugprint)
249                 writefln( "is null (%s:%d)", file, line );
250             return;
251         }
252 
253         assert( refCount > 0, "not null object have 0 refs" );
254 
255         orig = RC.decRef(orig);
256 
257         version(rcdebugprint)
258         {
259             if( orig is null )
260                 writefln( " ok no refs (%s:%d)", file, line );
261             else
262                 writefln( " ok [%d] (%s:%d)", refCount, file, line );
263         }
264     }
265 
266     ///
267     size_t refCount() @property const
268     {
269         if( orig is null ) return 0;
270         return RC.refCount(orig);
271     }
272 
273     version(rcdebugprint)
274     {
275         private string _name = "__arr__";
276         string name() const @property { return _name; }
277         void name( string n ) @property { _name = n; }
278     }
279 
280     ~this()
281     {
282         version(rcdebugprint)
283             writefln( "[] %s dtor start", name );
284 
285         if( refCount )
286         {
287             /+ logic:
288                 if `orig` saved only in this object
289                 it means what only `work` set must have `refCount` > 0
290                 otherwise it means what one ore more elements are saved
291                 outside of `orig` and on dispose(orig) they must be saved
292              +/
293             if( refCount == 1 )
294             {
295                 static if( isRCType!T )
296                     foreach( ref w; orig )
297                         if( w.refCount )
298                             // `work` set decriments futher
299                             w.incRef;
300             }
301 
302             static if( isRCType!T )
303                 foreach( ref w; work ) w.decRef;
304 
305             // if `orig` saved only in this object
306             // additional decriment for all performs
307             // if they isRCType because RCObject and
308             // RCArray have decriment in destructor
309             decRef;
310         }
311     }
312 }
313 
314 /// true if T is RCObject or RCArray
315 template isRCType(T)
316 {
317     static if( is( T E == RCObject!X, X ) || is( T E == RCArray!X, X ) )
318         enum isRCType = true;
319     else
320         enum isRCType = false;
321 }
322 
323 ///
324 auto rcMake(T,A...)( A args )
325 { return RCObject!(T).make( RC.make!T(args) ); }
326 
327 ///
328 unittest
329 {
330     static string[] log;
331 
332     static class Par
333     {
334         this( uint i ) { log ~= "+par"; }
335         ~this() { log ~= "-par"; }
336     }
337 
338     static class Ch : Par
339     {
340         this() { super(1); log ~= "+ch"; }
341         ~this() { log ~= "-ch"; }
342     }
343 
344     assert( log.length == 0 );
345 
346     auto inc = 0;
347     {
348         RCObject!Ch c;
349         {
350             auto a = rcMake!Ch();
351             assert( equal( log, ["+par","+ch"] ) );
352             assert( a.refCount == 1 );
353             auto b = a;
354             assert( equal( log, ["+par","+ch"] ) );
355             assert( a.refCount == 2 );
356             assert( b.refCount == 2 );
357             c = a;
358             assert( a.refCount == 3 );
359             assert( b.refCount == 3 );
360             assert( c.refCount == 3 );
361             b = rcMake!Ch();
362             assert( a.refCount == 2 );
363             assert( b.refCount == 1 );
364             assert( c.refCount == 2 );
365             assert( equal( log, ["+par","+ch","+par","+ch"] ) );
366             b = rcMake!Ch();
367             assert( a.refCount == 2 );
368             assert( b.refCount == 1 );
369             assert( c.refCount == 2 );
370             assert( equal( log, ["+par","+ch","+par","+ch",
371                                  "+par","+ch", // new Ch stored in b
372                                  "-ch","-par" // old Ch in b
373                                 ] ) );
374         }
375         assert( c.refCount == 1 );
376     }
377     assert( equal( log, ["+par","+ch","+par","+ch",
378                          "+par","+ch","-ch","-par",
379                          "-ch","-par","-ch","-par"] ) );
380 }
381 
382 unittest
383 {
384     static string[] log;
385 
386     static class A
387     {
388         this() { log ~= "+"; }
389         ~this() { log ~= "-"; }
390     }
391 
392     static class B
393     {
394         A a;
395         this() { this.a = RC.make!A(); }
396         this( A a )
397         {
398             this.a = a;
399             RC.incRef(a);
400         }
401 
402         ~this() { RC.decRef(a); }
403     }
404 
405     {
406         assert( log.length == 0 );
407         RCObject!B b;
408         {
409             auto a = rcMake!A();
410             auto x = rcMake!B(a);
411             assert( equal( log, ["+"] ) );
412             assert( RC.refCount(x.a) == 2 );
413             assert( x.refCount == 1 );
414             b = x;
415             assert( a.refCount == 2 );
416             assert( RC.refCount(x.a) == 2 );
417             assert( x.refCount == 2 );
418             x = rcMake!B();
419             assert( b.refCount == 1 );
420             assert( x.refCount == 1 );
421             assert( a.refCount == 2 );
422             assert( a.obj is b.a );
423             assert( RC.refCount(b.a) == 2 );
424             assert( RC.refCount(x.a) == 1 );
425         }
426         assert( RC.refCount(b.a) == 1 );
427         assert( b.refCount == 1 );
428         assert( equal( log, ["+","+","-"] ) );
429     }
430     assert( equal( log, ["+","+","-","-"] ) );
431 }
432 
433 ///
434 auto rcMakeArray(T,A...)( A args )
435 { return RCArray!(T).make( RC.makeArray!T(args) ); }
436 
437 unittest
438 {
439     static string[] log;
440     static string[] exp;
441     static void change(string s)( int[] i... )
442     { exp ~= i.map!(a=>format("%s%d",s,a)).array; }
443     static void add( int[] i... ) { change!"+"(i); }
444     static void rem( int[] i... ) { change!"-"(i); }
445 
446     static class A
447     {
448         int no;
449         this( int i ) { no=i; log ~= format("+%d",i); }
450         ~this() { log ~= format("-%d",no); }
451     }
452 
453     alias RCA = RCObject!A;
454 
455     {
456         RCA obj1;
457         RCA obj2;
458 
459         assert( equal( log, exp ) );
460         {
461             RCArray!RCA tmp;
462             {
463                 auto arr = rcMakeArray!RCA(6);
464                 assert( equal( log, exp ) );
465                 foreach( int i, ref a; arr )
466                 {
467                     a = rcMake!A(i);
468                     add(i);
469                 }
470                 assert( equal( log, exp ) );
471 
472                 foreach( ref a; arr )
473                     assert( a.refCount == 1 );
474 
475                 obj1 = arr[1];
476                 obj2 = arr[2];
477 
478                 assert( equal( log, exp ) );
479 
480                 assert( obj1.refCount == 2 );
481                 assert( obj2.refCount == 2 );
482                 assert( arr[1].refCount == 2 );
483                 assert( arr[2].refCount == 2 );
484 
485                 arr[2] = rcMake!A(11);
486                 add(11);
487                 assert( equal( log, exp ) );
488                 assert( obj2.refCount == 1 );
489                 assert( arr[2].refCount == 1 );
490                 assert( arr[2].obj != obj2.obj );
491 
492                 arr[3] = rcMake!A(88);
493                 add(88); rem(3);
494                 assert( equal( log, exp ) );
495                 assert( arr[3].refCount == 1 );
496 
497                 tmp = arr[3..5];
498                 assert( equal( log, exp ) );
499                 assert( arr[0].refCount == 1 );
500                 assert( arr[1].refCount == 2 );
501                 assert( arr[2].refCount == 1 );
502                 assert( arr[2].obj.no == 11 );
503                 assert( arr[3].refCount == 2 );
504                 assert( arr[3].obj.no == 88 );
505                 assert( arr[4].refCount == 2 );
506                 assert( arr[5].refCount == 1 );
507             }
508             rem( 0, 11, 5 );
509             assert( equal( log, exp ) );
510         }
511         rem( 88, 4 );
512         assert( equal( log, exp ) );
513         assert( obj1.refCount == 1 );
514         assert( obj1.obj.no == 1 );
515         assert( obj2.refCount == 1 );
516         assert( obj2.obj.no == 2 );
517     }
518     rem( 2, 1 );
519     assert( equal( log, exp ) );
520 }
521 
522 //
523 unittest
524 {
525     static string[] log; // what happens
526     static string[] exp; // expected
527 
528     // write to expected
529     static void change(string s)( int[] i... )
530     { exp ~= i.map!(a=>format("%s%d",s,a)).array; }
531 
532     // ditto
533     static void add( int[] i... ) { change!"+"(i); }
534     // ditto
535     static void rem( int[] i... ) { change!"-"(i); }
536 
537     // writes to log construction and destruction events
538     static class A
539     {
540         int no;
541         this( int i ) { no=i; log ~= format("+%d",i); }
542         ~this() { log ~= format("-%d",no); }
543     }
544 
545     alias RCA = RCObject!A;
546 
547     {
548         RCA obj;
549         {
550             RCArray!RCA tmp1;
551             {
552                 RCArray!RCA tmp2;
553                 {
554                     assert( equal( log, exp ) );
555                     auto arr = rcMakeArray!RCA(6);
556                     assert( equal( log, exp ) );
557                     foreach( int i, ref a; arr )
558                     {
559                         a = rcMake!A(i);
560                         add(i);
561                     }
562                     assert( equal( log, exp ) );
563                     assert( arr[0].refCount == 1 );
564                     assert( arr[1].refCount == 1 );
565                     assert( arr[2].refCount == 1 );
566                     assert( arr[3].refCount == 1 );
567                     assert( arr[4].refCount == 1 );
568                     assert( arr[5].refCount == 1 );
569 
570                     tmp1 = arr[1..4];
571                     assert( equal( log, exp ) );
572                     assert( arr[0].refCount == 1 );
573                     assert( arr[1].refCount == 2 );
574                     assert( arr[2].refCount == 2 );
575                     assert( arr[3].refCount == 2 );
576                     assert( arr[4].refCount == 1 );
577                     assert( arr[5].refCount == 1 );
578 
579                     tmp2 = arr[3..5];
580                     assert( equal( log, exp ) );
581                     assert( arr[0].refCount == 1 );
582                     assert( arr[1].refCount == 2 );
583                     assert( arr[2].refCount == 2 );
584                     assert( arr[3].refCount == 3 );
585                     assert( arr[4].refCount == 2 );
586                     assert( arr[5].refCount == 1 );
587 
588                     obj = tmp2[0];
589                     assert( equal( log, exp ) );
590                     assert( arr[0].refCount == 1 );
591                     assert( arr[1].refCount == 2 );
592                     assert( arr[2].refCount == 2 );
593                     assert( arr[3].refCount == 4 );
594                     assert( arr[4].refCount == 2 );
595                     assert( arr[5].refCount == 1 );
596                 }
597                 rem(0,5);
598                 assert( equal( log, exp ) );
599                 assert( tmp1[0].refCount == 1 );
600                 assert( tmp1[1].refCount == 1 );
601                 assert( tmp1[2].refCount == 3 );
602 
603                 assert( obj.refCount == 3 );
604 
605                 assert( tmp2[0].refCount == 3 );
606                 assert( tmp2[0].obj.no == 3 );
607                 assert( tmp2[1].refCount == 1 );
608                 assert( tmp2[1].obj.no == 4 );
609             }
610             rem(4);
611             assert( equal( log, exp ) );
612             assert( tmp1[0].refCount == 1 );
613             assert( tmp1[1].refCount == 1 );
614             assert( tmp1[2].refCount == 2 );
615             assert( obj.refCount == 2 );
616         }
617         rem(1,2);
618         assert( equal( log, exp ) );
619         assert( obj.refCount == 1 );
620     }
621     rem(3);
622     assert( equal( log, exp ) );
623 }
624 
625 version(rcdebugprint)
626 {
627     auto rcMakeNamed(T,A...)( string name, A args )
628     { return RCObject!(T).make( name, RC.make!T(args) ); }
629 
630     auto rcMakeNamedArray(T,A...)( string name, A args )
631     { return RCArray!(T).make( name, RC.makeArray!T(args) ); }
632 }