1 /**
2    A reference-counted smart pointer.
3  */
4 module automem.ref_counted;
5 
6 import automem.traits: isAllocator;
7 import automem.unique: Unique;
8 import stdx.allocator: theAllocator, processAllocator;
9 import std.typecons: Flag;
10 
11 
12 alias RC = RefCounted;
13 
14 /**
15    A reference-counted smart pointer similar to C++'s std::shared_ptr.
16  */
17 struct RefCounted(Type,
18                   Allocator = typeof(theAllocator),
19                   Flag!"supportGC" supportGC = Flag!"supportGC".yes)
20     if(isAllocator!Allocator)
21 {
22 
23     import std.traits: hasMember;
24 
25     enum isSingleton = hasMember!(Allocator, "instance");
26     enum isTheAllocator = is(Allocator == typeof(theAllocator));
27     enum isGlobal = isSingleton || isTheAllocator;
28 
29     static if(isGlobal)
30         /**
31            The allocator is a singleton, so no need to pass it in to the
32            constructor
33         */
34         this(Args...)(auto ref Args args) {
35             this.makeObject!args();
36         }
37     else
38         /**
39            Non-singleton allocator, must be passed in
40         */
41         this(Args...)(Allocator allocator, auto ref Args args) {
42             _allocator = allocator;
43             this.makeObject!args();
44         }
45 
46     static if(isGlobal)
47         /**
48             Factory method so can construct with zero args.
49         */
50         static typeof(this) construct(Args...)(auto ref Args args) {
51             static if (Args.length != 0)
52                 return typeof(return)(args);
53             else {
54                 typeof(return) ret;
55                 ret.makeObject!()();
56                 return ret;
57             }
58         }
59     else
60         /**
61             Factory method. Not necessary with non-global allocator
62             but included for symmetry.
63         */
64         static typeof(this) construct(Args...)(auto ref Allocator allocator, auto ref Args args) {
65             return typeof(return)(allocator, args);
66         }
67 
68     ///
69     this(this) {
70         // assert(_impl !is null, "Postblit ctor with null impl");
71         // inc;
72         if(_impl !is null) inc;
73     }
74 
75     ///
76     ~this() {
77         release;
78     }
79 
80     /**
81        Assign to an lvalue RefCounted
82     */
83     void opAssign(ref RefCounted other) {
84 
85         if (_impl == other._impl) return;
86 
87         if(_impl !is null) release;
88 
89         static if(!isGlobal)
90             _allocator = other._allocator;
91 
92         _impl = other._impl;
93 
94         if(_impl !is null) inc;
95     }
96 
97     /**
98        Assign to an rvalue RefCounted
99      */
100     void opAssign(RefCounted other) {
101         import std.algorithm: swap;
102         swap(_impl, other._impl);
103         static if(!isGlobal)
104             swap(_allocator, other._allocator);
105     }
106 
107     /**
108        Dereference the smart pointer and yield a reference
109        to the contained type.
110      */
111     ref auto opUnary(string s)() inout if (s == "*") {
112         return _impl._get;
113     }
114 
115     /**
116         Prevent opSlice and opIndex from being hidden by Impl*.
117         This comment is deliberately not DDOC.
118     */
119     auto ref opSlice(A...)(auto ref A args)
120     if (__traits(compiles, Type.init.opSlice(args)))
121     {
122         return _impl._get.opSlice(args);
123     }
124     /// ditto
125     auto ref opIndex(A...)(auto ref A args)
126     if (__traits(compiles, Type.init.opIndex(args)))
127     {
128         return _impl._get.opIndex(args);
129     }
130     /// ditto
131     auto ref opIndexAssign(A...)(auto ref A args)
132     if (__traits(compiles, Type.init.opIndexAssign(args)))
133     {
134         return _impl._get.opIndexAssign(args);
135     }
136 
137     alias _impl this;
138 
139 private:
140 
141     static struct Impl {
142 
143         static if(is(Type == class)) {
144 
145             align ((void*).alignof)
146             void[__traits(classInstanceSize, Type)] _rawMemory;
147 
148         } else
149             Type _object;
150 
151         static if(is(Type == shared))
152             shared size_t _count;
153         else
154             size_t _count;
155 
156         static if (is(Type == class)) {
157             inout(Type) _get() inout {
158                 return cast(inout(Type))&_rawMemory[0];
159             }
160 
161             inout(shared(Type)) _get() inout shared {
162                 return cast(inout(shared(Type)))&_rawMemory[0];
163             }
164         } else {
165             ref inout(Type) _get() inout {
166                 return _object;
167             }
168 
169             ref inout(shared(Type)) _get() inout shared {
170                 return _object;
171             }
172         }
173 
174         alias _get this;
175     }
176 
177     static if(isSingleton)
178         alias _allocator = Allocator.instance;
179     else static if(isTheAllocator) {
180         static if (is(Type == shared))
181             // 'processAllocator' should be used for allocating
182             // memory shared across threads
183             alias _allocator = processAllocator;
184         else
185             alias _allocator = theAllocator;
186     }
187     else
188         Allocator _allocator;
189 
190     static if(is(Type == shared))
191         alias ImplType = shared Impl;
192     else
193         alias ImplType = Impl;
194 
195     public ImplType* _impl; // public or alias this doesn't work
196 
197     void allocateImpl() {
198         import stdx.allocator: make;
199         import std.traits: hasIndirections;
200 
201         _impl = cast(typeof(_impl))_allocator.allocate(Impl.sizeof);
202         _impl._count= 1;
203 
204         static if (is(Type == class)) {
205             // class representation:
206             // void* classInfoPtr
207             // void* monitorPtr
208             // []    interfaces
209             // T...  members
210             import core.memory: GC;
211             if (supportGC && !(typeid(Type).m_flags & TypeInfo_Class.ClassFlags.noPointers))
212                 // members have pointers: we have to watch the monitor
213                 // and all members; skip the classInfoPtr
214                 GC.addRange(&_impl._rawMemory[(void*).sizeof],
215                         __traits(classInstanceSize, Type) - (void*).sizeof);
216             else
217                 // representation doesn't have pointers, just watch the
218                 // monitor pointer; skip the classInfoPtr
219                 // need to watch the monitor pointer even if supportGC is false.
220                 GC.addRange(&_impl._rawMemory[(void*).sizeof], (void*).sizeof);
221         } else static if (supportGC && hasIndirections!Type) {
222             import core.memory: GC;
223             GC.addRange(cast(void*) &_impl._object, Type.sizeof);
224         }
225     }
226 
227     void release() {
228         import std.traits : hasIndirections;
229         import core.memory : GC;
230         import automem.utils : destruct;
231 
232         if(_impl is null) return;
233         assert(_impl._count > 0, "Trying to release a RefCounted but ref count is 0 or less");
234 
235         dec;
236 
237         if(_impl._count == 0) {
238             () @trusted { destruct(_impl._get); }();
239             static if (is(Type == class)) {
240                 // need to watch the monitor pointer even if supportGC is false.
241                 () @trusted { GC.removeRange(&_impl._rawMemory[(void*).sizeof]); }();
242             } else static if (supportGC && hasIndirections!Type) {
243                 () @trusted { GC.removeRange(cast(void*) &_impl._object); }();
244             }
245             auto mem = () @trusted { return cast(void*)_impl; }();
246             () @trusted { _allocator.deallocate(mem[0 .. Impl.sizeof]); }();
247         }
248     }
249 
250     void inc() {
251         static if(is(Type == shared)) {
252             import core.atomic: atomicOp;
253             _impl._count.atomicOp!"+="(1);
254         } else
255             ++_impl._count;
256 
257     }
258 
259     void dec() {
260         static if(is(Type == shared)) {
261             import core.atomic: atomicOp;
262             _impl._count.atomicOp!"-="(1);
263         } else
264             --_impl._count;
265     }
266 
267 }
268 
269 private template makeObject(args...)
270 {
271     void makeObject(Type, A)(ref RefCounted!(Type, A) rc) @trusted {
272         import std.conv: emplace;
273         import std.functional : forward;
274 
275         rc.allocateImpl;
276 
277         static if(is(Type == class))
278             emplace!Type(rc._impl._rawMemory, forward!args);
279         else
280             emplace(&rc._impl._object, forward!args);
281     }
282 }
283 
284 
285 
286 auto refCounted(Type, Allocator)(Unique!(Type, Allocator) ptr) {
287 
288     RefCounted!(Type, Allocator) ret;
289 
290     static if(!ptr.isGlobal)
291         ret._allocator = ptr.allocator;
292 
293     ret.allocateImpl;
294     *ret = *ptr;
295 
296     return ret;
297 }