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 }