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