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