1 module libd.datastructures.smartptr;
2 
3 import libd.algorithm : any;
4 import libd.memory : move, emplaceInit, AllocatorWrapperOf, SystemAllocator, maybeNull;
5 import libd.meta : TypeId, TypeIdOf, isCopyable;
6 
7 private mixin template accessFuncs(bool TypeHasT)
8 {
9     static if(TypeHasT)
10     {
11         void access()(scope void delegate(T) @nogc nothrow func)
12         {
13             static assert(false, "Please mark the parameter as `scope ref`");
14         }
15 
16         void access()(scope void function(T) @nogc nothrow func)
17         {
18             static assert(false, "Please mark the parameter as `scope ref`");
19         }
20 
21         void access(scope void delegate(scope ref T) @nogc nothrow func) { this.accessImpl(func); }
22         void access(scope void function(scope ref T) @nogc nothrow func) { this.accessImpl(func); }
23         @safe
24         void access(scope void delegate(scope ref T) @nogc @safe nothrow func) { this.accessImpl(func); }
25         @safe
26         void access(scope void function(scope ref T) @nogc @safe nothrow func) { this.accessImpl(func); }
27     }
28     else
29     {
30         void access(T)(scope void delegate(T) @nogc nothrow func)
31         {
32             static assert(false, "Please mark the parameter as `scope ref`");
33         }
34 
35         void access(T)(scope void function(T) @nogc nothrow func)
36         {
37             static assert(false, "Please mark the parameter as `scope ref`");
38         }
39 
40         void access(T)(scope void delegate(scope ref T) @nogc nothrow func) { this.accessImpl!T(func); }
41         void access(T)(scope void function(scope ref T) @nogc nothrow func) { this.accessImpl!T(func); }
42         @safe
43         void access(T)(scope void delegate(scope ref T) @nogc @safe nothrow func) { this.accessImpl!T(func); }
44         @safe
45         void access(T)(scope void function(scope ref T) @nogc @safe nothrow func) { this.accessImpl!T(func); }
46     }
47 }
48 
49 struct Unique(alias T)
50 {
51     private T    _value;
52     private bool _set;
53 
54     @disable this(this){}
55 
56     @nogc nothrow:
57 
58     this()(auto ref T value)
59     {
60         this = value;
61     }
62 
63     this(scope ref return typeof(this) rhs)
64     {
65         move(rhs._value, this._value);
66         this._set = true;
67         rhs._set = false;
68     }
69 
70     private void accessImpl(T)(T func)
71     {
72         assert(!this.isNull, "This Unique is null");
73         func(this._value);
74     }
75 
76     mixin accessFuncs!true;
77 
78     void opAssign()(auto ref T value)
79     {
80         move(value, this._value);
81         this._set = true;
82     }
83 
84     void opAssign()(typeof(null) n)
85     {
86         emplaceInit(this._value);
87         this._set = false;
88     }
89 
90     void release()(scope ref return T dest)
91     {
92         move(this._value, dest);
93         this._set = false;
94     }
95 
96     void release()(scope ref return typeof(this) dest)
97     {
98         move(this._value, dest._value);
99         this._set = false;
100         dest._set = true;
101     }
102 
103     typeof(this) release()
104     {
105         typeof(this) ret;
106         this.release(ret);
107         return ret; // NRVO makes this possible for non-copyable types.
108     }
109 
110     T releaseRaw()()
111     {
112         T ret;
113         move(this._value, ret);
114         this._set = false;
115         return ret;
116     }
117 
118     @property @safe
119     bool isNull() const
120     {
121         return !this._set;
122     }
123 
124     @property
125     inout(T)* ptrUnsafe() inout
126     {
127         assert(!this.isNull, "This Unique is null.");
128         return &this._value;
129     }
130 }
131 ///
132 @("Unique")
133 unittest
134 {
135     int dtor = 0;
136     static struct S
137     {
138         int num;
139         int* dtor;
140         @disable this(this){}
141         @nogc nothrow ~this()
142         {
143             if(dtor)
144                 (*dtor)++;
145         }
146     }
147 
148     S s = S(200, &dtor);
149     Unique!S a = s;
150     Unique!S b;
151 
152     assert(s == S.init);
153     assert(!a.isNull);
154     assert(b.isNull);
155     assert(a.ptrUnsafe.num == 200);
156     a.access((scope ref v)
157     {
158         assert(v.num == 200);
159         v.num *= 2;
160     });
161 
162     move(a, b);
163     assert(a.isNull);
164     assert(a._value == S.init);
165     assert(!b.isNull);
166     assert(b.ptrUnsafe.num == 400);
167 
168     a = Unique!S(S(800, &dtor));
169     assert(!a.isNull);
170     assert(a.ptrUnsafe.num == 800);
171     a = null;
172     assert(a.isNull);
173     assert(a._value == S.init);
174     assert(dtor == 1);
175 
176     b.release(s);
177     assert(b.isNull);
178     assert(s.num == 400);
179 
180     a = s;
181     a.release(b);
182     assert(a.isNull);
183     assert(!b.isNull);
184 
185     void test(Unique!S s)
186     {
187         assert(!s.isNull);
188         auto ss = s.releaseRaw();
189         assert(s.isNull);
190         assert(ss.num == 400);
191     }
192 
193     test(b.release);
194     assert(dtor == 2);
195 
196     Unique!S test2(Unique!S s)
197     {
198         assert(!s.isNull);
199         return s.release;
200     }
201 
202     a = S(200, &dtor);
203     b = test2(a.release);
204     assert(dtor == 2);
205     assert(a.isNull);
206     assert(!b.isNull);
207     b.__xdtor();
208     assert(dtor == 3);
209 }
210 
211 Unique!T makeUnique(T)(auto ref T value)
212 {
213     return typeof(return)(value);
214 }
215 
216 struct Shared(alias T, alias AllocT = SystemAllocator)
217 {
218     private static struct Store
219     {
220         T value;
221         ulong count;
222     } 
223 
224     private Store* _store;
225     private AllocatorWrapperOf!AllocT _alloc;
226 
227     @nogc nothrow:
228 
229     @safe
230     this(AllocatorWrapperOf!AllocT alloc)
231     {
232         this._alloc = alloc;
233     }
234 
235     this()(auto ref T value, AllocatorWrapperOf!AllocT alloc = AllocatorWrapperOf!AllocT.init)
236     {
237         this._alloc = alloc;
238         this.createNewStore(value);
239     }
240 
241     this(scope ref return typeof(this) copy)
242     {
243         auto oldStore = this._store;
244 
245         this._store = copy._store;
246         this._alloc = copy._alloc;
247         if(this._store !is null)
248             this.refUp();
249         if(oldStore !is null)
250             this.refDown();
251     }
252 
253     private void accessImpl(T)(T func)
254     {
255         assert(!this.isNull, "This Shared is null.");
256         func(this._store.value);
257     }
258 
259     mixin accessFuncs!true;
260 
261     ~this()
262     {
263         if(!this.isNull)
264             this.refDown();
265     }
266 
267     void createNewStore()(auto ref T value)
268     {
269         if(this._store !is null)
270             this.refDown();
271         this._store = this._alloc.make!Store();
272         if(this._store is null)
273             assert(false, "Memory allocation failed.");
274         move(value, this._store.value);
275         this.refUp();
276     }
277 
278     void opAssign()(typeof(null) n)
279     {
280         if(this._store !is null)
281             this.refDown();
282         this._store = null;
283     }
284 
285     @property @safe
286     bool isNull() const
287     {
288         return this._store is null;
289     }
290 
291     @property
292     inout(T)* ptrUnsafe() inout
293     {
294         assert(!this.isNull, "This Shared is null.");
295         return &this._store.value;
296     }
297 
298     @safe
299     private void refUp()
300     {
301         this._store.count++;
302     }
303 
304     private void refDown()()
305     {
306         this._store.count--;
307         if(this._store.count == 0)
308         {
309             this._alloc.dispose(this._store.maybeNull!(AllocT.Tag));
310             this._store = null;
311         }
312     }
313 }
314 ///
315 @("Shared")
316 unittest
317 {
318     int dtor;
319     static struct S
320     {
321         int value;
322         int* dtor;
323         @disable this(this);
324 
325         ~this() @nogc nothrow
326         {
327             if(dtor)
328                 (*dtor)++;
329         }
330     }
331 
332     // Simple
333     {
334         auto a = S(200, &dtor).makeShared;
335     }
336     assert(dtor == 1);
337     dtor = 0;
338 
339     // Copying
340     {
341         auto a = S(200, &dtor).makeShared;
342         auto b = a;
343     }
344     assert(dtor == 1);
345     dtor = 0;
346 
347     // Copying over existing
348     {
349         auto a = S(200, &dtor).makeShared;
350         auto b = S(400, &dtor).makeShared;
351         b = a;
352     }
353     assert(dtor == 2);
354     dtor = 0;
355 
356     // Null assign
357     {
358         auto a = S(200, &dtor).makeShared;
359         a = null;
360         assert(dtor == 1);
361     }
362 
363     // Access
364     {
365         auto a = S(200, &dtor).makeShared;
366         auto b = a;
367 
368         b.access((scope ref v)
369         {
370             v.value *= 2;
371         });
372         assert(a.ptrUnsafe.value == b.ptrUnsafe.value);
373         assert(a.ptrUnsafe.value == 400);
374     }
375 }
376 
377 Shared!(T, AllocT) makeShared(T, alias AllocT = SystemAllocator)(auto ref T value, AllocatorWrapperOf!AllocT alloc = AllocatorWrapperOf!AllocT.init)
378 {
379     return typeof(return)(value, alloc);
380 }
381 
382 struct TypedPtrBase(alias AllocT = SystemAllocator)
383 {
384     private void*  _ptr;
385     private TypeId _id;
386     private AllocatorWrapperOf!AllocT _alloc;
387 
388     @disable this(this){}
389 
390     @nogc nothrow:
391 
392     this(AllocatorWrapperOf!AllocT alloc)
393     {
394         this._alloc = alloc;
395     }
396 
397     this(T)(auto ref T value, AllocatorWrapperOf!AllocT alloc = AllocatorWrapperOf!AllocT.init)
398     {
399         this._alloc = alloc;
400         this.setByForce(value);
401     }
402 
403     ~this()
404     {
405         if(this._ptr !is null)
406         {
407             this._alloc.dispose(this._ptr.maybeNull!(AllocT.Tag));
408             this._ptr = null;
409         }
410     }
411 
412     @trusted // Because ptrUnsafeAs is not @safe. It technically *is* @safe by itself due to the safety checks, but the user can use it to perform @system behaviour.
413     private void accessImpl(ValueT, FuncT)(FuncT func)
414     {
415         assert(!this.isNull, "This TypedPtr is null.");
416         func(*this.ptrUnsafeAs!ValueT);
417     }
418 
419     mixin accessFuncs!false;
420 
421     @safe
422     bool contains(alias T)() const
423     {
424         assert(!this.isNull, "This TypedPtr is null.");
425         return this._id == TypeIdOf!T;
426     }
427 
428     void setByForce(T)(auto ref T value)
429     {
430         static if(is(T == struct))
431             static assert(__traits(isPOD, T), "Type `"~T.stringof~"` must be a POD struct.");
432 
433         if(this._ptr is null)
434         {
435             this._ptr = this._alloc.make!T();
436             if(this._ptr is null)
437                 assert(false, "Memory allocation failed.");
438         }
439         else if(this._id != TypeIdOf!value)
440         {
441             this._alloc.dispose(this._ptr.maybeNull!(AllocT.Tag));
442             this._ptr = null;
443             this.setByForce(value);
444             return;
445         }
446 
447         move(value, *(cast(T*)this._ptr));
448         this._id = TypeIdOf!T;
449     }
450 
451     void opAssign(T)(auto ref T value)
452     {
453         assert(
454             this.isNull || this._id == TypeIdOf!T, 
455             "opAssign cannot store a value of a different type from the current value. Use `setByForce` for that."
456         );
457         this.setByForce(value);
458     }
459 
460     void opAssign()(typeof(null) n)
461     {
462         if(!this.isNull)
463         {
464             this._id = TypeId.init;
465             this._alloc.dispose(this._ptr);
466         }
467     }
468 
469     @property @safe
470     bool isNull() const
471     {
472         return this._ptr is null;
473     }
474 
475     @property
476     inout(void*) ptrUnsafe() inout
477     {
478         assert(!this.isNull, "This TypedPtr is null.");
479         return this._ptr;
480     }
481 
482     @property
483     inout(T)* ptrUnsafeAs(T)() inout
484     {
485         assert(!this.isNull, "This TypePtr is null.");
486         assert(this._id == TypeIdOf!T, "Type mismatch. This TypedPtr does not store `"~T.stringof~"`");
487         return cast(inout(T)*)this._ptr;
488     }
489 }
490 alias TypedPtr = TypedPtrBase!();
491 ///
492 @("TypedPtr")
493 unittest
494 {
495     static struct S
496     {
497         int value;
498     }
499     
500     auto ptr = S(200).makeTyped;
501     assert(!ptr.isNull);
502     assert(ptr.contains!S);
503     assert(ptr.ptrUnsafeAs!S.value == 200);
504     ptr.access!S((scope ref s)
505     {
506         s.value *= 2;
507     });
508     assert(ptr.ptrUnsafeAs!S.value == 400);
509     ptr = S(100);
510     assert(ptr.ptrUnsafeAs!S.value == 100);
511     
512     // opAssign cannot change types.
513     // bool threw = false;
514     // try ptr = 200;
515     // catch(Error error)
516     //     threw = true;
517     // assert(threw);
518 
519     // But setForce can
520     ptr.setByForce(200);
521     assert(ptr.contains!int);
522     assert(*ptr.ptrUnsafeAs!int == 200);
523 
524     ptr = null;
525     assert(ptr.isNull);
526 }
527 
528 TypedPtrBase!AllocT makeTyped(T, alias AllocT = SystemAllocator)(auto ref T value, AllocatorWrapperOf!AllocT alloc = AllocatorWrapperOf!AllocT.init)
529 {
530     return typeof(return)(value, alloc);
531 }