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