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