1 /// Reference counting module
2 module gfx.core.rc;
3 
4 import std.typecons : Flag, No, Yes;
5 
6 /// A resource that can be disposed
7 interface Disposable
8 {
9     /// Dispose the underlying resource
10     void dispose();
11 }
12 
13 /// A atomic reference counted resource.
14 /// Objects implementing this interface can be safely manipulated as shared.
15 interface AtomicRefCounted : Disposable
16 {
17     /// Atomically loads the number of active references.
18     final @property size_t refCount() const {
19         return (cast(shared(AtomicRefCounted))this).refCountShared;
20     }
21     /// ditto
22     shared @property size_t refCountShared() const;
23 
24     /// Atomically increment the reference count.
25     /// Returns: a reference to this
26     final AtomicRefCounted retain() {
27         return cast(AtomicRefCounted)(cast(shared(AtomicRefCounted))this).retainShared();
28     }
29     /// ditto
30     shared(AtomicRefCounted) retainShared() shared;
31 
32     /// Atomically decrement the reference count.
33     /// If refCount reaches zero, and disposeOnZero is set,
34     /// the object is locked with its own mutex, and dispose is called.
35     /// In most cases, the calling code should set disposeOnZero, unless it
36     /// is intended to release the object to give it away.
37     /// (such as at the end of a builder function)
38     /// Returns: null if the object was disposed during this call, a reference
39     /// to this otherwise
40     final AtomicRefCounted release(in Flag!"disposeOnZero" disposeOnZero = Yes.disposeOnZero)
41     in {
42         assert(
43             refCount > 0,
44             "inconsistent ref count for "~(cast(Object)this).classinfo.name
45         );
46     }
47     body {
48         return cast(AtomicRefCounted)(cast(shared(AtomicRefCounted))this).releaseShared(disposeOnZero);
49     }
50     /// ditto
51     shared(AtomicRefCounted) releaseShared(in Flag!"disposeOnZero" disposeOnZero = Yes.disposeOnZero) shared
52     in {
53         assert(
54             refCountShared > 0,
55             "inconsistent ref count for "~(cast(Object)this).classinfo.name
56         );
57     }
58 
59     /// Get a reference to this if the refCount >= 1.
60     /// This increases the refCount by 1. rcLock should be used to keep
61     /// weak reference and ensures that the resource is not disposed.
62     /// The operation is atomic.
63     final AtomicRefCounted rcLock()
64     out(res) {
65         assert(
66             (res && refCount >= 2) || (!res && !refCount),
67             "inconsistent ref count for "~(cast(Object)this).classinfo.name
68         );
69     }
70     body {
71         return cast(AtomicRefCounted)(cast(shared(AtomicRefCounted))this).rcLockShared();
72     }
73     /// ditto
74     shared shared(AtomicRefCounted) rcLockShared();
75     // out(res) {
76     //     assert(
77     //         (res && refCountShared >= 2) || (!res && !refCountShared),
78     //         "inconsistent ref count for "~(cast(Object)this).classinfo.name
79     //     );
80     // }
81     // this contract compiles with dmd but create a failure on ldc2:
82     // cannot implicitely convert shared(T) to const(AtomicRefCounted)
83 
84     override void dispose()
85     in { assert(refCount == 0); } // override to add this contract
86 }
87 
88 /// compile time check that T can be ref counted atomically.
89 enum isAtomicRefCounted(T) = is(T : shared(AtomicRefCounted)) || is(T : AtomicRefCounted);
90 
91 
92 /// A string that can be mixed-in a class declaration to implement AtomicRefCounted.
93 /// dispose is not implemented of course, but is called by release while the object is locked.
94 /// Classes implementing it are free to do it in a non-thread safe manner as long
95 /// as dispose does not manipulate external state.
96 enum atomicRcCode = sharedAtomicMethods ~ q{
97     private size_t _refCount=0;
98 };
99 
100 /// Counts the number of references of a single object.
101 /// Useful for shared-agnostic generic code.
102 size_t countObj(T)(T obj) if (isAtomicRefCounted!T) {
103     static if (is(T == shared)) {
104         return obj.refCountShared;
105     }
106     else {
107         return obj.refCount;
108     }
109 }
110 
111 /// Retains a single object.
112 /// Useful for shared-agnostic generic code.
113 T retainObj(T)(T obj) if (isAtomicRefCounted!T) {
114     static if (is(T == shared)) {
115         return cast(T)obj.retainShared();
116     }
117     else {
118         return cast(T)obj.retain();
119     }
120 }
121 
122 /// Releases a single object.
123 /// Useful for shared-agnostic generic code.
124 T releaseObj(T)(T obj, in Flag!"disposeOnZero" disposeOnZero = Yes.disposeOnZero)
125 if (isAtomicRefCounted!T)
126 {
127     static if (is(T == shared)) {
128         return cast(T)obj.releaseShared(disposeOnZero);
129     }
130     else {
131         return cast(T)obj.release(disposeOnZero);
132     }
133 }
134 
135 /// Locks a single object.
136 /// Useful for shared-agnostic generic code.
137 T lockObj(T)(T obj) if (isAtomicRefCounted!T) {
138     static if (is(T == shared)) {
139         return obj.rcLockShared();
140     }
141     else {
142         return cast(T)obj.rcLock();
143     }
144 }
145 
146 /// Dispose GC allocated array of resources
147 void disposeArray(T)(ref T[] arr) if (is(T : Disposable) && !isAtomicRefCounted!T)
148 {
149     import std.algorithm : each;
150     arr.each!(el => el.dispose());
151     arr = null;
152 }
153 
154 /// Dispose GC allocated associative array of resources
155 void disposeArray(T, K)(ref T[K] arr) if (is(T : Disposable) && !isAtomicRefCounted!T)
156 {
157     import std.algorithm : each;
158     arr.each!((k, el) { el.dispose(); });
159     arr = null;
160 }
161 
162 /// Retain GC allocated array of ref-counted resources
163 void retainArray(T)(ref T[] arr) if (isAtomicRefCounted!T)
164 {
165     import std.algorithm : each;
166     arr.each!(el => retainObj(el));
167 }
168 
169 /// Retain GC allocated associative array of ref-counted resources
170 void retainArray(T, K)(ref T[K] arr) if (isAtomicRefCounted!T)
171 {
172     import std.algorithm : each;
173     arr.each!((k, el) => retainObj(el));
174 }
175 
176 /// Release GC allocated array of ref-counted resources
177 void releaseArray(T)(ref T[] arr) if (isAtomicRefCounted!T)
178 {
179     import std.algorithm : each;
180     arr.each!(el => releaseObj(el));
181     arr = null;
182 }
183 /// Release GC allocated associative array of ref-counted resources
184 void releaseArray(T, K)(ref T[K] arr) if (isAtomicRefCounted!T)
185 {
186     import std.algorithm : each;
187     arr.each!((k, el) => releaseObj(el));
188     arr.clear();
189     arr = null;
190 }
191 
192 /// Reinitialises a GC allocated array of struct.
193 /// Useful if the struct release resource in its destructor.
194 void reinitArray(T)(ref T[] arr) if (is(T == struct))
195 {
196     foreach(ref t; arr) {
197         t = T.init;
198     }
199     arr = null;
200 }
201 /// Reinitialises a GC allocated associative array of struct.
202 /// Useful if the struct release resource in its destructor.
203 void reinitArray(T, K)(ref T[K] arr) if (is(T == struct))
204 {
205     foreach(k, ref t; arr) {
206         t = T.init;
207     }
208     arr.clear();
209     arr = null;
210 }
211 
212 /// Helper that build a new instance of T and returns it within a Rc!T
213 template makeRc(T) if (isAtomicRefCounted!T)
214 {
215     Rc!T makeRc(Args...)(Args args)
216     {
217         return Rc!T(new T(args));
218     }
219 }
220 
221 /// Helper that places an instance of T within a Rc!T
222 template rc(T) if (isAtomicRefCounted!T)
223 {
224     Rc!T rc(T obj)
225     {
226         if (obj) {
227             return Rc!T(obj);
228         }
229         else {
230             return (Rc!T).init;
231         }
232     }
233 }
234 
235 /// Produces an Rc!T holding a null object
236 @property Rc!T nullRc(T)() if (isAtomicRefCounted!T) {
237     return Rc!T.init;
238 }
239 
240 /// Helper struct that manages the reference count of an object using RAII.
241 struct Rc(T) if (isAtomicRefCounted!T)
242 {
243     private T _obj;
244 
245     /// Build a Rc instance with the provided resource
246     this(T obj)
247     {
248         _obj = obj;
249         if (obj) {
250             retainObj(_obj);
251         }
252     }
253 
254     /// Postblit adds a reference to the held reference.
255     this(this)
256     {
257         if (_obj) retainObj(_obj);
258     }
259 
260     /// Removes a reference to the held reference.
261     ~this()
262     {
263         if(_obj) {
264             releaseObj(_obj, Yes.disposeOnZero);
265             _obj = null;
266         }
267     }
268 
269     /// Assign another resource. Release the previously held ref and retain the new one.
270     void opAssign(T obj)
271     {
272         if(_obj) releaseObj(_obj, Yes.disposeOnZero);
273         _obj = obj;
274         if(_obj) retainObj(_obj);
275     }
276 
277     /// Check whether this Rc is assigned to a resource.
278     bool opCast(T : bool)() const
279     {
280         return loaded;
281     }
282 
283     /// Check whether this Rc is assigned to a resource.
284     @property bool loaded() const
285     {
286         return _obj !is null;
287     }
288 
289     /// Reset the resource.
290     void unload()
291     {
292         if(_obj) {
293             releaseObj(_obj);
294             _obj = null;
295         }
296     }
297 
298     /// Decrease the ref count and return the object without calling dispose
299     T giveAway()
300     in {
301         assert(_obj, "giving away invalid object");
302     }
303     out(obj) {
304         assert(obj);
305     }
306     body {
307         auto obj = releaseObj(_obj, No.disposeOnZero);
308         _obj = null;
309         return obj;
310     }
311 
312     /// Access to the held resource.
313     @property inout(T) obj() inout { return _obj; }
314 
315     alias obj this;
316 }
317 
318 /// Helper struct that keeps a weak reference to a Resource.
319 struct Weak(T) if (is(T : Disposable))
320 {
321     private T _obj;
322 
323     /// Build a Weak instance.
324     this(T obj)
325     {
326         _obj = obj;
327     }
328 
329     /// Reset the internal reference.
330     void reset()
331     {
332         _obj = null;
333     }
334 
335     /// Check whether the resource has been disposed.
336     @property bool disposed() const
337     {
338         return !_obj || (_obj.refCount == 0);
339     }
340 
341     /// Return a Rc that contain the underlying resource if it has not been disposed.
342     Rc!T lock()
343     {
344         Rc!T rc;
345         rc._obj = _obj ? lockObj(_obj) : null;
346         if (!rc._obj) _obj = null;
347         return rc;
348     }
349 }
350 
351 
352 private enum sharedAtomicMethods = q{
353 
354     import std.typecons : Flag, Yes;
355 
356     public final shared override @property size_t refCountShared() const
357     {
358         import core.atomic : atomicLoad;
359         return atomicLoad(_refCount);
360     }
361 
362     public final shared override shared(typeof(this)) retainShared()
363     {
364         import core.atomic : atomicOp;
365         immutable rc = atomicOp!"+="(_refCount, 1);
366         debug(rc) {
367             import std.experimental.logger : logf;
368             logf("retain %s: %s -> %s", typeof(this).stringof, rc-1, rc);
369         }
370         return this;
371     }
372 
373     public final shared override shared(typeof(this)) releaseShared(in Flag!"disposeOnZero" disposeOnZero = Yes.disposeOnZero)
374     {
375         import core.atomic : atomicOp;
376         immutable rc = atomicOp!"-="(_refCount, 1);
377 
378         debug(rc) {
379             import std.experimental.logger : logf;
380             logf("release %s: %s -> %s", typeof(this).stringof, rc+1, rc);
381         }
382         if (rc == 0 && disposeOnZero) {
383             debug(rc) {
384                 import std.experimental.logger : logf;
385                 logf("dispose %s", typeof(this).stringof);
386             }
387             synchronized(this) {
388                 // cast shared away
389                 import gfx.core.rc : Disposable;
390                 auto obj = cast(Disposable)this;
391                 obj.dispose();
392             }
393             return null;
394         }
395         else {
396             return this;
397         }
398     }
399 
400     public final shared override shared(typeof(this)) rcLockShared()
401     {
402         import core.atomic : atomicLoad, cas;
403         while (1) {
404             immutable c = atomicLoad(_refCount);
405 
406             if (c == 0) {
407                 debug(rc) {
408                     import std.experimental.logger : logf;
409                     logf("rcLock %s: %s", typeof(this).stringof, c);
410                 }
411                 return null;
412             }
413             if (cas(&_refCount, c, c+1)) {
414                 debug(rc) {
415                     import std.experimental.logger : logf;
416                     logf("rcLock %s: %s", typeof(this).stringof, c+1);
417                 }
418                 return this;
419             }
420         }
421     }
422 };
423 
424 // private string numberizeCodeLines(string code) {
425 //     import std.string : splitLines;
426 //     import std.format : format;
427 //     string res;
428 //     auto lines = code.splitLines();
429 //     foreach (i, l; lines) {
430 //         res ~= format("%s. %s\n", i+1, l);
431 //     }
432 //     return res;
433 // }
434 
435 // pragma(msg, numberizeCodeLines(atomicRcCode));
436 
437 // using version(unittest) instead of private creates link errors
438 // in test builds in apps/libs depending on gfx-d (??)
439 private
440 {
441     int rcCount = 0;
442     int structCount = 0;
443 
444     class RcClass : AtomicRefCounted
445     {
446         mixin(atomicRcCode);
447 
448         this()
449         {
450             rcCount += 1;
451         }
452 
453         override void dispose()
454         {
455             rcCount -= 1;
456         }
457     }
458 
459     struct RcStruct
460     {
461         Rc!RcClass obj;
462     }
463 
464     struct RcArrStruct
465     {
466         Rc!RcClass[] objs;
467 
468         ~this()
469         {
470             foreach(ref o; objs) {
471                 o = Rc!RcClass.init;
472             }
473         }
474     }
475 
476     struct RcArrIndStruct
477     {
478         RcStruct[] objs;
479 
480         ~this()
481         {
482             foreach(ref o; objs) {
483                 o = RcStruct.init;
484             }
485         }
486     }
487 
488 
489     unittest
490     {
491         {
492             auto arr = RcArrStruct([makeRc!RcClass(), makeRc!RcClass()]);
493             assert(rcCount == 2);
494             foreach(obj; arr.objs) {
495                 assert(rcCount == 2);
496             }
497             assert(rcCount == 2);
498         }
499         assert(rcCount == 0);
500     }
501 
502 
503     unittest
504     {
505         {
506             auto obj = makeRc!RcClass();
507             assert(rcCount == 1);
508         }
509         assert(rcCount == 0);
510     }
511 
512     unittest
513     {
514         {
515             auto obj = RcStruct(makeRc!RcClass());
516             assert(rcCount == 1);
517         }
518         assert(rcCount == 0);
519     }
520 
521     unittest
522     {
523         Weak!RcClass weak;
524         {
525             auto locked = weak.lock();
526             assert(!locked.loaded);
527         }
528         {
529             auto rc = makeRc!RcClass();
530             assert(rcCount == 1);
531             assert(rc.refCount == 1);
532             weak = Weak!RcClass(rc);
533             assert(rc.refCount == 1);
534             {
535                 auto locked = weak.lock();
536                 assert(locked.loaded);
537                 assert(rc.refCount == 2);
538             }
539             assert(rc.refCount == 1);
540         }
541         assert(rcCount == 0);
542         assert(weak.disposed);
543         {
544             auto locked = weak.lock();
545             assert(!locked.loaded);
546         }
547     }
548 }
549 
550 static if(false)
551 {
552     /// Creates a new instance of $(D T) and returns it under a $(D Uniq!T).
553     template makeUniq(T)
554     if (is(T : Disposable))
555     {
556         Uniq!T makeUniq(Args...)(Args args)
557         {
558             return Uniq!T(new T (args));
559         }
560     }
561 
562     debug(Uniq)
563     {
564         import std.stdio;
565     }
566 
567     /// A helper struct that manage the lifetime of a Disposable using RAII.
568     /// Note: dlang has capability to enforce a parameter be a lvalue (ref param)
569     /// but has no mechanism such as c++ rvalue reference which would enforce
570     /// true uniqueness by the compiler. Uniq gives additional robustness, but it is
571     /// up to the programmer to make sure that the values passed in by rvalue are
572     /// not referenced somewhere else in the code
573     struct Uniq(T)
574     if (is(T : Disposable) && !hasMemberFunc!(T, "release"))
575     {
576         private T _obj;
577         alias Resource = T;
578 
579         // prevent using Uniq with Refcounted
580         // (invariant handles runtime polymorphism)
581         static assert(!is(T : RefCounted), "Use Rc helper for RefCounted objects");
582         invariant()
583         {
584             // if obj is assigned, it must not cast to a RefCounted
585             assert(!_obj || !(cast(RefCounted)_obj), "Use Rc helper for RefCounted objects");
586         }
587 
588         /// Constructor taking rvalue. Uniqueness is achieve only if there is no
589         /// aliases of the passed reference.
590         this(T obj)
591         {
592             debug(Uniq)
593             {
594                 writefln("build a Uniq!%s from rvalue", T.stringof);
595             }
596             _obj = obj;
597         }
598 
599         /// Constructor taking lvalue. Uniqueness is achieve only if there is no
600         /// other copies of the passed reference.
601         this(ref T obj)
602         {
603             debug(Uniq)
604             {
605                 writefln("build a Uniq!%s from lvalue", T.stringof);
606             }
607             _obj = obj;
608             obj = null;
609         }
610 
611         /// Constructor that take a rvalue.
612         /// $(D u) can only be a rvalue because postblit is disabled.
613         this(U)(Uniq!U u)
614         if (is(U : T))
615         {
616             debug(Uniq)
617             {
618                 writefln("cast building a Uniq from rvalue from %s to %s",
619                     U.stringof, T.stringof
620                 );
621             }
622             _obj = u._obj;
623         }
624 
625         /// Transfer ownership from a Uniq of a type that is convertible to our type.
626         /// $(D u) can only be a rvalue because postblit is disabled.
627         void opAssign(U)(Uniq!U u)
628         if (is(U : T))
629         {
630             debug(Uniq)
631             {
632                 writefln("opAssign a Uniq from rvalue from %s to %s",
633                     U.stringof, T.stringof
634                 );
635             }
636             if (_obj)
637             {
638                 _obj.dispose();
639             }
640             _obj = u._obj;
641             u._obj = null;
642         }
643 
644         /// Shortcut to assigned
645         bool opCast(U : bool)() const
646         {
647             return assigned;
648         }
649 
650         /// Destructor that disposes the resource.
651         ~this()
652         {
653             debug(Uniq)
654             {
655                 writefln("dtor of Uniq!%s", T.stringof);
656             }
657             dispose();
658         }
659 
660         /// A view on the underlying object.
661         @property inout(T) obj() inout
662         {
663             return _obj;
664         }
665 
666         /// Forwarding method calls and member access to the underlying object.
667         alias obj this;
668 
669         /// Transfer the ownership.
670         Uniq release()
671         {
672             debug(Uniq)
673             {
674                 writefln("release of Uniq!%s", T.stringof);
675             }
676             auto u = Uniq(_obj);
677             assert(_obj is null);
678             return u;
679         }
680 
681         /// Explicitely ispose the underlying resource.
682         void dispose()
683         {
684             // Same method than Disposeable on purpose as it disables alias this.
685             // One cannot shortcut Uniq to dispose the resource.
686             if (_obj)
687             {
688                 debug(Uniq)
689                 {
690                     writefln("dispose of Uniq!%s", T.stringof);
691                 }
692                 _obj.dispose();
693                 _obj = null;
694             }
695         }
696 
697         /// Checks whether a resource is assigned.
698         bool assigned() const
699         {
700             return _obj !is null;
701         }
702 
703         // disable copying
704         @disable this(this);
705     }
706 
707     version(unittest)
708     {
709         private int disposeCount;
710 
711         private class UniqTest : Disposable
712         {
713             override void dispose()
714             {
715                 disposeCount += 1;
716             }
717         }
718 
719         private Uniq!UniqTest produce1()
720         {
721             auto u = makeUniq!UniqTest();
722             // Returning without release is fine?
723             // It compiles and passes the test, but not recommended.
724             // return u;
725             return u.release();
726         }
727 
728         private Uniq!UniqTest produce2()
729         {
730             return makeUniq!UniqTest();
731         }
732 
733         private void consume(Uniq!UniqTest /+u+/)
734         {
735         }
736 
737         unittest
738         {
739             disposeCount = 0;
740             auto u = makeUniq!UniqTest();
741             assert(disposeCount == 0);
742             static assert (!__traits(compiles, consume(u)));
743             consume(u.release());
744             assert(disposeCount == 1);
745 
746             {
747                 auto v = makeUniq!UniqTest();
748             }
749             assert(disposeCount == 2);
750 
751             consume(produce1());
752             assert(disposeCount == 3);
753 
754             consume(produce2());
755             assert(disposeCount == 4);
756 
757             auto w = makeUniq!UniqTest();
758             w.dispose();
759             assert(disposeCount == 5);
760         }
761     }
762 
763     private template hasMemberFunc(T, string fun)
764     {
765         enum bool hasMemberFunc = is(typeof(
766         (inout int = 0)
767         {
768             T t = T.init;
769             mixin("t."~fun~"();");
770         }));
771     }
772 
773     static assert(hasMemberFunc!(RefCounted, "retain"));
774     static assert(hasMemberFunc!(Disposable, "dispose"));
775     static assert(!hasMemberFunc!(Disposable, "release"));
776 }