1 /++
2 $(H1 Thread-safe reference-counted shared pointers).
3 
4 This implementation supports class and struct (`alias this`) polymorphism.
5 +/
6 module mir.rc.ptr;
7 
8 import mir.rc.context;
9 import mir.type_info;
10 import std.traits;
11 
12 package static immutable allocationExcMsg = "mir_rcptr: out of memory error.";
13 package static immutable getExcMsg = "mir_rcptr: trying to use null value.";
14 
15 version (D_Exceptions)
16 {
17     import core.exception: OutOfMemoryError, InvalidMemoryOperationError;
18     package static immutable allocationError = new OutOfMemoryError(allocationExcMsg);
19 }
20 
21 /++
22 Thread safe reference counting array.
23 
24 This implementation supports class and struct (`alias this`) polymorphism.
25 
26 `__xdtor` if any is used to destruct objects.
27 
28 The implementation never adds roots into the GC.
29 +/
30 struct mir_rcptr(T)
31 {
32     static if (is(T == class) || is(T == interface) || is(T == struct) || is(T == union))
33         static assert(!__traits(isNested, T), "mir_rcptr does not support nested types.");
34 
35     ///
36     static if (is(T == class) || is(T == interface))
37         package Unqual!T _value;
38     else
39         package T* _value;
40     package mir_rc_context* _context;
41 
42     package ref mir_rc_context context() inout scope return @trusted @property
43     {
44         return *cast(mir_rc_context*)_context;
45     }
46 
47     package void _reset()
48     {
49         _value = null;
50         _context = null;
51     }
52 
53     inout(void)* _thisPtr() inout scope return @trusted @property
54     {
55         return cast(inout(void)*) _value;
56     }
57 
58     package alias ThisTemplate = .mir_rcptr;
59 
60     /// ditto
61     alias opUnary(string op : "*") = _get_value;
62     /// ditto
63     alias _get_value this;
64 
65     static if (is(T == class) || is(T == interface))
66         ///
67         pragma(inline, true)
68         inout(T) _get_value() scope inout @property
69         {
70             assert(this, getExcMsg);
71             return _value;
72         }
73     else
74         ///
75         pragma(inline, true)
76         ref inout(T) _get_value() scope inout @property
77         {
78             assert(this, getExcMsg);
79             return *_value;
80         }
81 
82     ///
83     void proxySwap(ref typeof(this) rhs) pure nothrow @nogc @safe
84     {
85         auto t0 = this._value;
86         auto t1 = this._context;
87         this._value = rhs._value;
88         this._context = rhs._context;
89         rhs._value = t0;
90         rhs._context = t1;
91     }
92 
93     ///
94     this(typeof(null))
95     {
96     }
97 
98     ///
99     mixin CommonRCImpl;
100 
101 
102     ///
103     pragma(inline, true)
104     bool opEquals(typeof(null)) @safe scope const pure nothrow @nogc
105     {
106         return !this;
107     }
108 
109     /// ditto
110     bool opEquals(Y)(auto ref scope const ThisTemplate!Y rhs) @safe scope const pure nothrow @nogc
111     {
112         return _thisPtr == rhs._thisPtr;
113     }
114 
115     ///
116     sizediff_t opCmp(Y)(auto ref scope const ThisTemplate!Y rhs) @trusted scope const pure nothrow @nogc
117     {
118         return cast(void*)_thisPtr - cast(void*)rhs._thisPtr;
119     }
120 
121     ///
122     size_t toHash() @trusted scope const pure nothrow @nogc
123     {
124         return cast(size_t) _thisPtr;
125     }
126 
127     ///
128     ~this() nothrow
129     {
130         static if (hasElaborateDestructor!T || hasDestructor!T)
131         {
132             if (false) // break @safe and pure attributes
133             {
134                 Unqual!T* object;
135                 (*object).__xdtor;
136             }
137         }
138         if (this)
139         {
140             (() @trusted { mir_rc_decrease_counter(context); })();
141             debug _reset;
142         }
143     }
144 
145     static if (is(T == const) || is(T == immutable))
146     this(return ref scope const typeof(this) rhs) @trusted pure nothrow @nogc
147     {
148         if (rhs)
149         {
150             this._value = cast(typeof(this._value))rhs._value;
151             this._context = cast(typeof(this._context))rhs._context;
152             mir_rc_increase_counter(context);
153         }
154     }
155 
156     static if (is(T == immutable))
157     this(return ref scope const typeof(this) rhs) immutable @trusted pure nothrow @nogc
158     {
159         if (rhs)
160         {
161             this._value = cast(typeof(this._value))rhs._value;
162             this._context = cast(typeof(this._context))rhs._context;
163             mir_rc_increase_counter(context);
164         }
165     }
166 
167     static if (is(T == immutable))
168     this(return ref scope const typeof(this) rhs) const @trusted pure nothrow @nogc
169     {
170         if (rhs)
171         {
172             this._value = cast(typeof(this._value))rhs._value;
173             this._context = cast(typeof(this._context))rhs._context;
174             mir_rc_increase_counter(context);
175         }
176     }
177 
178     this(return ref scope inout typeof(this) rhs) inout @trusted pure nothrow @nogc
179     {
180         if (rhs)
181         {
182             this._value = rhs._value;
183             this._context = rhs._context;
184             mir_rc_increase_counter(context);
185         }
186     }
187 
188     ///
189     ref opAssign(typeof(null)) return @trusted // pure nothrow @nogc
190     {
191         this = typeof(this).init;
192     }
193 
194     ///
195     ref opAssign(return typeof(this) rhs) return @trusted // pure nothrow @nogc
196     {
197         this.proxySwap(rhs);
198         return this;
199     }
200 
201     ///
202     ref opAssign(Q)(return ThisTemplate!Q rhs) return @trusted // pure nothrow @nogc
203         if (isImplicitlyConvertible!(Q*, T*))
204     {
205         this.proxySwap(*()@trusted{return cast(typeof(this)*)&rhs;}());
206         return this;
207     }
208 }
209 
210 ///
211 alias RCPtr = mir_rcptr;
212 
213 /++
214 Returns: shared pointer of the member and the context from the current pointer. 
215 +/
216 auto shareMember(string member, T, Args...)(return mir_rcptr!T context, auto ref Args args)
217 {
218     import core.lifetime: move;
219     void foo(A)(auto ref A) {}
220     assert(context != null);
221     static if (args.length)
222     {
223         // breaks safaty
224         if (false) foo(__traits(getMember, context._get_value, member)(forward!args));
225         return (()@trusted => createRCWithContext(__traits(getMember, context._get_value, member)(forward!args), context.move))();
226     }
227     else
228     {
229         // breaks safaty
230         if (false) foo(__traits(getMember, context._get_value, member));
231         return (()@trusted => createRCWithContext(__traits(getMember, context._get_value, member), context.move))();
232     }
233 }
234 
235 /++
236 Returns: shared pointer constructed with current context. 
237 +/
238 @system .mir_rcptr!R createRCWithContext(R, F)(return R value, return const mir_rcptr!F context)
239     if (is(R == class) || is(R == interface))
240 {
241     typeof(return) ret;
242     ret._value = cast()value;
243     ret._context = cast(mir_rc_context*)context._context;
244     (*cast(mir_rcptr!F*)&context)._value = null;
245     (*cast(mir_rcptr!F*)&context)._context = null;
246     return ret;
247 }
248 
249 ///ditto
250 @system .mir_rcptr!R createRCWithContext(R, F)(return ref R value, return const mir_rcptr!F context)
251     if (!is(R == class) && !is(R == interface))
252 {
253     typeof(return) ret;
254     ret._value = &value;
255     ret._context = cast(mir_rc_context*)context._context;
256     (*cast(mir_rcptr!F*)&context)._value = null;
257     (*cast(mir_rcptr!F*)&context)._context = null;
258     return ret;
259 }
260 
261 /++
262 Construct a shared pointer of a required type with a current context.
263 Provides polymorphism abilities for classes and structures with `alias this` syntax.
264 +/
265 mir_rcptr!R castTo(R, T)(return mir_rcptr!T context) @trusted
266     if (isImplicitlyConvertible!(T, R))
267 {
268     import core.lifetime: move;
269     return createRCWithContext(cast(R)context._get_value, move(context));
270 }
271 
272 /// ditto
273 mir_rcptr!(const R) castTo(R, T)(return const mir_rcptr!T context) @trusted
274     if (isImplicitlyConvertible!(const T, const R))
275 {
276     import core.lifetime: move;
277     return createRCWithContext(cast(const R)context._get_value, move(*cast(mir_rcptr!T*)&context));
278 }
279 
280 /// ditto
281 mir_rcptr!(immutable R) castTo(R, T)(return immutable mir_rcptr!T context) @trusted
282     if (isImplicitlyConvertible!(immutable T, immutable R))
283 {
284     import core.lifetime: move;
285     return createRCWithContext(cast(immutable R)context._get_value, move(*cast(mir_rcptr!T*)&context));
286 }
287 
288 ///
289 template createRC(T)
290     if (!is(T == interface) && !__traits(isAbstractClass, T))
291 {
292     ///
293     mir_rcptr!T createRC(Args...)(auto ref Args args)
294     {
295         typeof(return) ret;
296         with (ret) () @trusted {
297             _context = mir_rc_create(mir_get_type_info!T, 1, mir_get_payload_ptr!T);
298             if (!_context)
299             {
300                 version(D_Exceptions)
301                     throw allocationError;
302                 else
303                     assert(0, allocationExcMsg);
304             }
305             _value = cast(typeof(_value))(_context + 1);
306         } ();
307         import core.lifetime: forward;
308         import mir.conv: emplace;
309         cast(void) emplace!T(ret._value, forward!args);
310         return ret;
311     }
312 }
313 
314 ///
315 version(mir_test)
316 @safe pure @nogc nothrow
317 unittest
318 {
319     auto a = createRC!double(10);
320     auto b = a;
321     assert(*b == 10);
322     *b = 100;
323     assert(*a == 100);
324 }
325 
326 /// Classes with empty constructor
327 version(mir_test)
328 @safe pure @nogc nothrow
329 unittest
330 {
331     static class C
332     {
333         int index = 34;
334     }
335     assert(createRC!C.index == 34);
336 }
337 
338 ///
339 version(mir_test)
340 @safe pure @nogc nothrow
341 unittest
342 {
343     static interface I { ref double bar() @safe pure nothrow @nogc; }
344     static abstract class D { int index; }
345     static class C : D, I
346     {
347         double value;
348         ref double bar() @safe pure nothrow @nogc { return value; }
349         this(double d) { value = d; }
350     }
351     auto a = createRC!C(10);
352     assert(a._counter == 1);
353     auto b = a;
354     assert(a._counter == 2);
355     assert((*b).value == 10);
356     b.value = 100; // access via alias this syntax
357     assert(a.value == 100);
358     assert(a._counter == 2);
359 
360     auto d = a.castTo!D; //RCPtr!D
361     assert(d._counter == 3);
362     d.index = 234;
363     assert(a.index == 234);
364     auto i = a.castTo!I; //RCPtr!I
365     assert(i.bar == 100);
366     assert(i._counter == 4);
367 
368     auto v = a.shareMember!"value"; //RCPtr!double
369     auto w = a.shareMember!"bar"; //RCPtr!double
370     assert(i._counter == 6);
371     assert(*v == 100);
372     ()@trusted{assert(&*w is &*v);}();
373 }
374 
375 /// 'Alias This' support
376 version(mir_test)
377 @safe pure @nogc nothrow
378 unittest
379 {
380     struct S
381     {
382         double e;
383     }
384     struct C
385     {
386         int i;
387         S s;
388         // 'alias' should be accesable by reference
389         // or a class/interface
390         alias s this;
391     }
392 
393     auto a = createRC!C(10, S(3));
394     auto s = a.castTo!S; // RCPtr!S
395     assert(s._counter == 2);
396     assert(s.e == 3);
397 }
398 
399 version(unittest):
400 
401 package struct _test_unpure_system_dest_s__ {
402     static int numStructs;
403     int i;
404 
405     this(this This)(int i) {
406         this.i = i;
407         ++numStructs;
408     }
409 
410     ~this() @system nothrow {
411         auto d = new int[2];
412         --numStructs;
413     }
414 }
415 
416 version(mir_test)
417 @system nothrow
418 unittest
419 {
420     import mir.rc.array;
421     auto ptr = createRC!_test_unpure_system_dest_s__(42);
422     auto arr = rcarray!_test_unpure_system_dest_s__(3);
423 }