1 /**
2 Copyright: Copyright (c) 2021, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 Some code is copied from Atila Neves automem.
7 
8 Convenient functions to use std.experimental.allocator with classes.
9 */
10 module my.alloc.class_;
11 
12 /** Allocate a class using `allocator` and initialize with `args`
13  *
14  * The class will always be tracked by the GC, no option here. This is because
15  * then it is easy to use this function correctly.
16  */
17 T make(T, Allocator, Args...)(auto ref Allocator allocator, auto ref Args args)
18         if (is(T == class) || is(T == interface)) {
19     import core.memory : GC;
20     import std.experimental.allocator : make;
21     import std.functional : forward;
22 
23     auto obj = () @trusted { return make!T(allocator, forward!args); }();
24     () @trusted {
25         enum sz = __traits(classInstanceSize, T);
26         auto repr = (cast(void*) obj)[0 .. sz];
27         GC.addRange(&repr[(void*).sizeof], sz - (void*).sizeof);
28     }();
29 
30     return obj;
31 }
32 
33 void dispose(T, Allocator)(auto ref Allocator allocator_, auto ref T obj)
34         if (is(T == class) || is(T == interface)) {
35     enum sz = __traits(classInstanceSize, T);
36     dispose(allocator_, cast(Object) obj, sz);
37 }
38 
39 void dispose(Allocator)(auto ref Allocator allocator_, Object obj, size_t sz) {
40     import core.memory : GC;
41     static import my.alloc.dispose_;
42 
43     () @trusted {
44         my.alloc.dispose_.dispose(allocator_, obj);
45         auto repr = (cast(void*) obj)[0 .. sz];
46         GC.removeRange(&repr[(void*).sizeof]);
47     }();
48 }
49 
50 /** A bundle of classes (different classes) that are destroyed and freed when
51  * the bundles destructor is called.
52  *
53  * Intended for parts of a program where classes are continuously allocated and
54  * all have the same lifetime. They are then destroyed as one. It is important
55  * to not let any references to classes escape to other parts of the program
56  * because that will lead to random crashes.
57  *
58  */
59 @safe struct Bundle(Allocator) {
60     import std.traits : hasMember;
61     import std.experimental.allocator : theAllocator;
62 
63     enum isSingleton = hasMember!(Allocator, "instance");
64     enum isTheAllocator = is(Allocator == typeof(theAllocator));
65     enum isGlobal = isSingleton || isTheAllocator;
66 
67     static if (isSingleton)
68         alias allocator_ = Allocator.instance;
69     else static if (isTheAllocator)
70         alias allocator_ = theAllocator;
71     else
72         Allocator allocator_;
73 
74     private {
75         static struct AllocObj {
76             Object obj;
77             // the size of an object is variable and not possible to derive
78             // from obj.
79             size_t sz;
80         }
81 
82         AllocObj[] objects;
83     }
84 
85     static if (!isGlobal) {
86         /// Non-singleton allocator, must be passed in.
87         this(Allocator allocator) {
88             allocator_ = allocator;
89         }
90     }
91 
92     ~this() {
93         release;
94     }
95 
96     T make(T, Args...)(auto ref Args args) if (is(T == class) || is(T == interface)) {
97         import std.functional : forward;
98 
99         enum sz = __traits(classInstanceSize, T);
100         auto o = .make!T(allocator_, forward!args);
101         objects ~= AllocObj(o, sz);
102         return o;
103     }
104 
105     /// Destroying and release the memory of all objects.
106     void release() @trusted {
107         foreach (n; objects) {
108             .dispose(allocator_, n.obj, n.sz);
109         }
110         objects = null;
111     }
112 
113     bool empty() @safe pure nothrow const @nogc {
114         return objects.length == 0;
115     }
116 
117     size_t length() @safe pure nothrow const @nogc {
118         return objects.length;
119     }
120 }
121 
122 @("shall alloc and destroy objects")
123 @safe unittest {
124     import std.experimental.allocator.mallocator : Mallocator;
125 
126     bool isDestroyed;
127 
128     {
129         static class Foo {
130             bool* x;
131             this(ref bool x) @trusted {
132                 this.x = &x;
133             }
134 
135             ~this() {
136                 *x = true;
137             }
138         }
139 
140         Bundle!Mallocator b;
141         auto foo = b.make!Foo(isDestroyed);
142         assert(!isDestroyed);
143     }
144 
145     assert(isDestroyed);
146 }