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