1 /*
2 MIT License
3 Copyright (c) 2017 Boris Barboris
4 */
5 
6 module daii.refcounted;
7 
8 import core.atomic: atomicOp;
9 import std.experimental.allocator: make, dispose;
10 import std.experimental.allocator.mallocator: Mallocator;
11 import std.functional: forward;
12 import std.traits: isArray, isAbstractClass, isAssignable;
13 
14 import daii.utils;
15 
16 /// Reference-counting memory owner, holds one shared instance of type T.
17 /// Don't use it to hold built-in arrays, use custom array type instead.
18 /// Deallocates in destructor, when reference count is zero.
19 struct RefCounted(T, bool Atomic = true, Allocator = Mallocator)
20     if (isAllocator!Allocator && !isArray!T)
21 {
22     enum HoldsAllocator = !isStaticAllocator!Allocator;
23 
24     static if (isClassOrIface!T)
25     {
26         alias PtrT = T;
27         @property inout(T) v() inout @nogc @safe
28         {
29             assert(valid);
30             return ptr;
31         }
32     }
33     else
34     {
35         alias PtrT = T*;
36         @property ref inout(T) v() inout @nogc @safe
37         {
38             assert(valid);
39             return *ptr;
40         }
41     }
42 
43     static if (Atomic)
44         alias RefCounterT = shared size_t;
45     else
46         alias RefCounterT = size_t;
47 
48     private RefCounterT* refcount;
49     private PtrT ptr;
50 
51     @property bool valid() const @nogc @safe { return *refcount > 0; }
52 
53     @disable this();
54 
55     static if (HoldsAllocator)
56         private Allocator allocator;
57 
58     // don't generate constructors for abstract types
59     static if (!(is(T == interface) || isAbstractClass!(T)))
60     {
61         static if (!HoldsAllocator)
62         {
63             private this(PtrT ptr) @trusted
64             {
65                 this.refcount = cast(RefCounterT*) Allocator.instance.make!size_t(1);
66                 this.ptr = ptr;
67             }
68 
69             static RefCounted!(T, Atomic, Allocator)
70             make(Args...)(auto ref Args args) @trusted
71             {
72                 auto ptr = Allocator.instance.make!(T)(forward!args);
73                 auto rq = RefCounted!(T, Atomic, Allocator)(ptr);
74                 assert(rq.valid);
75                 return rq;
76             }
77         }
78         else
79         {
80             private this(PtrT ptr, Allocator alloc) @trusted
81             {
82                 this.refcount = cast(RefCounterT*) alloc.make!size_t(1);
83                 this.ptr = ptr;
84                 this.allocator = alloc;
85             }
86 
87             static RefCounted!(T, Atomic, Allocator)
88             make(Args...)(auto ref Allocator alloc, auto ref Args args) @trusted
89             {
90                 auto ptr = alloc.make!(T)(forward!args);
91                 auto rq = RefCounted!(T, Atomic, Allocator)(ptr, alloc);
92                 assert(rq.valid);
93                 return rq;
94             }
95         }
96     }
97 
98     // bread and butter
99     ~this() @safe
100     {
101         decrement();
102     }
103 
104     // destroy the resource (destructor + free memory)
105     private void destroy() @trusted
106     {
107         static if (HoldsAllocator)
108             allocator.dispose(ptr);
109         else
110             Allocator.instance.dispose(ptr);
111     }
112 
113     private void decrement() @safe
114     {
115         assert(valid);
116         static if (Atomic)
117         {
118             if (atomicOp!"-="(*refcount, 1) == 0)
119                 destroy();
120         }
121         else
122         {
123             if ((*refcount -= 1) == 0)
124                 destroy();
125         }
126     }
127 
128     private void increment() @safe @nogc
129     {
130         assert(valid);
131         static if (Atomic)
132             atomicOp!"+="(*refcount, 1);
133         else
134             *refcount += 1;
135     }
136 
137     this(this) @safe @nogc
138     {
139         increment();
140     }
141 
142     // handle polymorphism
143     static if (isClassOrIface!T)
144     {
145         // Polymorphism-aware assign operator
146         ref RefCounted!(T, Atomic, Allocator)
147         opAssign(DT)(const RefCounted!(DT, Atomic, Allocator) rhs) @trusted
148             if (isClassOrIface!DT && isAssignable!(T, DT))
149         {
150             decrement();
151             this.ptr = cast(PtrT) rhs.ptr;
152             this.refcount = cast(RefCounterT*) rhs.refcount;
153             static if (HoldsAllocator)
154                 this.allocator = cast(Allocator) rhs.allocator;
155             increment();
156             return this;
157         }
158 
159         // Polymorphism-aware constructor
160         this(DT)(const RefCounted!(DT, Atomic, Allocator) rhs) @trusted
161             if (isClassOrIface!DT && isAssignable!(T, DT))
162         {
163             this.ptr = cast(PtrT) rhs.ptr;
164             this.refcount = cast(RefCounterT*) rhs.refcount;
165             static if (HoldsAllocator)
166                 this.allocator = cast(Allocator) rhs.allocator;
167             increment();
168         }
169 
170         // Polymorphic upcast
171         RefCounted!(BT, Atomic, Allocator) to(BT)() const @safe
172             if (isClassOrIface!BT && isAssignable!(BT, T))
173         {
174             assert(valid);
175             RefCounted!(BT, Atomic, Allocator) rv =
176                 RefCounted!(BT, Atomic, Allocator)(this);
177             assert(rv.valid);
178             return rv;
179         }
180     }
181     else
182     {
183         ref opAssign(const RefCounted!(T, Atomic, Allocator) rhs) @trusted
184         {
185             decrement();
186             this.ptr = cast(PtrT) rhs.ptr;
187             this.refcount = cast(RefCounterT*) rhs.refcount;
188             static if (HoldsAllocator)
189                 this.allocator = cast(Allocator) rhs.allocator;
190             increment();
191             return this;
192         }
193     }
194 }
195 
196 unittest
197 {
198     auto uq = RefCounted!int.make(5);
199     assert(uq.valid);
200     assert(uq.v == 5);
201     uq.v = 7;
202     assert(uq.v == 7);
203 }
204 
205 unittest
206 {
207     auto create()
208     {
209         return RefCounted!int.make(0);
210     }
211     void consume(RefCounted!int u)
212     {
213         assert(u.valid);
214         assert(*u.refcount == 2);
215         assert(u.v == 5);
216     }
217     auto u = create();
218     assert(u.valid);
219     assert(*u.refcount == 1);
220     assert(u.v == 0);
221     u.v = 5;
222     consume(u);
223     assert(*u.refcount == 1);
224 }
225 
226 unittest
227 {
228     struct TS
229     {
230         int x = -3;
231     }
232     auto u = RefCounted!TS.make;
233     assert(u.v.x == -3);
234 }
235 
236 unittest
237 {
238     static int count = 0;
239     static int total = 0;
240     class TC
241     {
242         this() { count++; total++; }
243         ~this() { count--; }
244         int j = 3;
245     }
246     auto u1 = RefCounted!TC.make;
247     assert(count == 1);
248     {
249         auto u2 = RefCounted!TC.make;
250         assert(count == 2);
251         assert(u2.v !is null);
252         assert(u2.v.j == 3);
253         u2 = u1;
254         assert(count == 1);
255         assert(u1.valid);
256         assert(u2.valid);
257         assert(*(u1.refcount) == 2);
258     }
259     assert(count == 1);
260     assert(*(u1.refcount) == 1);
261     assert(total == 2);
262 }
263 
264 import std.experimental.allocator.showcase;
265 
266 unittest
267 {
268     static int count = 0;
269     static int total = 0;
270     class TC
271     {
272         this() { count++; total++; }
273         ~this() { count--; }
274         int j = 3;
275     }
276     alias Alloc = StackFront!(4096, Mallocator);
277     Alloc al;
278     alias Uq = RefCounted!(TC, true, Alloc*);
279     auto u1 = Uq.make(&al);
280     assert(count == 1);
281     {
282         auto u2 = Uq.make(&al);
283         assert(count == 2);
284         assert(u2.v !is null);
285         assert(u2.v.j == 3);
286         u2 = u1;
287         assert(count == 1);
288         assert(u1.valid);
289         assert(u2.valid);
290         assert(*(u1.refcount) == 2);
291     }
292     assert(count == 1);
293     assert(*(u1.refcount) == 1);
294     assert(total == 2);
295 }
296 
297 unittest
298 {
299     static int ades = 0;
300     static int bdes = 0;
301     class A { ~this() {ades++;}}
302     class B: A { ~this(){bdes++;}}
303     {
304         RefCounted!A ag = RefCounted!A.make();
305         {
306             RefCounted!A aq = ag;
307             assert(aq.valid && ag.valid);
308             {
309                 RefCounted!A bq = RefCounted!B.make();
310                 assert(ades == 0);
311                 assert(bdes == 0);
312             }
313             assert(ades == 1);
314             assert(bdes == 1);
315         }
316         assert(ades == 1);
317         assert(bdes == 1);
318     }
319     assert(ades == 2);
320     assert(bdes == 1);
321 }
322 
323 unittest
324 {
325     static int ades = 0;
326     static int bdes = 0;
327     class A { ~this() {ades++;}}
328     class B: A { ~this(){bdes++;}}
329     void consume(RefCounted!A uq)
330     {
331         assert(uq.valid);
332     }
333     {
334         RefCounted!B aq = RefCounted!B.make();
335         consume(aq.to!A);
336         assert(ades == 0);
337         assert(bdes == 0);
338     }
339     assert(ades == 1);
340     assert(bdes == 1);
341 }