1 /// Wrappers for automatically managing memory.
2 module tern.typecons.automem;
3 
4 import tern.typecons.security;
5 import tern.traits;
6 import tern.meta;
7 import std.experimental.allocator;
8 import std.typecons;
9 import std.conv;
10 
11 private alias NULL = typeof(null);
12 /// Automatically releases a pointer of type `T` when exiting scope, assumes no ownership is passed.
13 public class Unique(T, alias FREE = typeof(null))
14 {
15     T* ptr;
16     alias ptr this;
17 
18 public:
19 final:
20     this(P : U*, U)(P ptr)
21     {
22         this.ptr = cast(T*)ptr;
23     }
24 
25     auto opAssign(A)(A ahs)
26     {
27         static assert(0, "Cannot reassign a Unique!");
28     }
29 
30     /// Releases/frees this manager. Implementation defined.
31     void release()
32     {
33         void[] arr;
34         (cast(size_t*)&arr)[0] = T.sizeof;
35         (cast(void**)&arr)[1] = cast(void*)ptr;
36 
37         if (theAllocator.owns(arr) == Ternary.yes)
38             theAllocator.deallocate(arr);
39         else if (!is(FREE == NULL))
40             FREE(ptr);
41         else
42         {
43             if (ptr !is null)
44                 destroy(*ptr);
45 
46             destroy(ptr);
47         }
48     }
49 
50     ~this()
51     {
52         release();
53     }
54 }
55 
56 /// Helper function for creating a unique.
57 Unique!T unique(T : U*, U)(T ptr)
58 {
59     return new Unique!T(ptr);
60 }
61 
62 /// Automatically releases a pointer of type `T` when exiting scope, allows any kind of ownership, but does not ref count.
63 public class Scoped(T, alias FREE = typeof(null))
64 {
65     T* ptr;
66     alias ptr this;
67 
68 public:
69 final:
70     this(P : U*, U)(P ptr)
71     {
72         this.ptr = cast(T*)ptr;
73     }
74 
75     /// Releases/frees this manager. Implementation defined.
76     void release()
77     {
78         void[] arr;
79         (cast(size_t*)&arr)[0] = T.sizeof;
80         (cast(void**)&arr)[1] = cast(void*)ptr;
81 
82         if (theAllocator.owns(arr) == Ternary.yes)
83             theAllocator.deallocate(arr);
84         else if (!is(FREE == NULL))
85             FREE(ptr);
86         else
87         {
88             if (ptr !is null)
89                 destroy(*ptr);
90 
91             destroy(ptr);
92         }
93     }
94 
95     ~this()
96     {
97         release();
98     }
99 }
100 
101 /// Helper function for creating a scoped.
102 Scoped!T scoped(T : U*, U)(T ptr)
103 {
104     return new Scoped!T(ptr);
105 }
106 
107 /// Counts references to the origin `RefCounted` and only releases if all references have also destructed.
108 public class RefCounted(T, alias FREE = typeof(null))
109 {
110     T* ptr;
111     alias ptr this;
112 
113 public:
114 final:
115     shared Atomic!ptrdiff_t refs;
116     shared(Atomic!(ptrdiff_t))* pref;
117 
118     this(P : U*, U)(P ptr)
119     {
120         this.ptr = cast(T*)ptr;
121         this.refs = 1;
122         this.pref = &refs;
123     }
124 
125     this(P : RefCounted, U)(P ptr)
126     {
127         this.ptr = cast(T*)ptr.ptr;
128         this.pref = ptr.pref;
129         (*pref)++;
130     }
131 
132     auto opUnary(string op)()
133     {
134         static assert (op != "*" || !is(T == class), "Cannot dereference a RefCounted(T) where T is a class!");
135         return mixin(op~"ptr");
136     }
137 
138     auto opAssign(A)(A ahs)
139         if (is(A : U*, U) || is(A : RefCounted, U))
140     {
141         static if (is(A == RefCounted))
142         {
143             this.ptr = cast(T*)ahs.ptr;
144             this.pref = ahs.pref;
145             (*pref)++;
146         }
147         else
148         {
149             this.ptr = cast(T*)ahs;
150             this.refs = 1;
151             this.pref = &refs;
152         }
153         return this;
154     }
155 
156     /// Releases/frees this manager. Implementation defined.
157     void release()
158     {
159         (*pref)--;
160         if (*pref <= 0)
161         {
162             void[] arr;
163             (cast(size_t*)&arr)[0] = T.sizeof;
164             (cast(void**)&arr)[1] = cast(void*)ptr;
165 
166             if (theAllocator.owns(arr) == Ternary.yes)
167                 theAllocator.deallocate(arr);
168             else if (!is(FREE == NULL))
169                 FREE(ptr);
170             else
171             {
172                 if (ptr !is null)
173                     destroy(*ptr);
174 
175                 destroy(ptr);
176             }
177         }
178     }
179 
180     ~this()
181     {
182         release();
183     }
184 }
185 
186 /// Helper function for creating a ref counted.
187 RefCounted!T refCounted(T : U*, U)(T ptr)
188 {
189     return RefCounted!T(ptr);
190 }
191 
192 private void*[] tracking;
193 /// Stores all pointers and sweeps through the array after a `Tracked` destructs.
194 public class Tracked(T, alias FREE = typeof(null))
195     if (is(T == class))
196 {
197     T* ptr;
198     alias ptr this;
199 
200 public:
201 final:
202     this(P : U*, U)(P ptr)
203     {
204         this.ptr = cast(T*)ptr;
205         tracking ~= cast(void*)ptr;
206     }
207 
208     ~this()
209     {
210         foreach (ref ptr; tracking)
211         {
212             if (ptr != null && ptr.isNull)
213             {
214                 void[] arr;
215                 (cast(size_t*)&arr)[0] = T.sizeof;
216                 (cast(void**)&arr)[1] = cast(void*)ptr;
217 
218                 if (theAllocator.owns(arr) == Ternary.yes)
219                     theAllocator.deallocate(arr);
220                 else if (!is(FREE == NULL))
221                     FREE(ptr);
222                 else
223                 {
224                     if (ptr !is null)
225                         destroy(*ptr);
226 
227                     destroy(ptr);
228                 }
229             }
230             ptr = null;
231         }
232     }
233 }
234 
235 /// Helper function for creating a tracked.
236 Tracked!T tracked(T : U*, U)(T ptr)
237     if (is(T == class))
238 {
239     return new Tracked!T(ptr);
240 }
241 
242 /// Automatically disposing wrapper, will attempt to dispose by calling `close`, `dispose`, or `destroy` if all else fails.
243 public class Disposable(T)
244 {
245     T value;
246     alias value this;
247 
248 public:
249 final:
250     this(T val)
251     {
252         value = val;
253     }
254 
255     /// Releases/frees this manager. Implementation defined.
256     void release()
257     {
258         static if (seqContains!("close", Functions!T))
259         {
260             static assert(Parameters!(TypeOf!(T, "close")).length == 0, "Close function expected to have no parameters!");
261             mixin("value.close();");
262         }
263         else static if (seqContains!("dispose", Functions!T))
264         {
265             static assert(Parameters!(TypeOf!(T, "dispose")).length == 0, "Dispose function expected to have no parameters!");
266             mixin("value.dispose();");
267         }
268         else
269             destroy(value);
270     }
271     
272     ~this()
273     {
274         release();
275     }
276 }
277 
278 /// Helper function for creating a disposable.
279 Disposable!T disposable(T)(T val)
280 {
281     return new Disposable!T(val);
282 }