1 module memutils.refcounted; 2 3 import memutils.allocators; 4 import memutils.helpers; 5 import std.conv : to, emplace; 6 import std.traits : hasIndirections, Unqual, isImplicitlyConvertible; 7 import memutils.utils; 8 import std.algorithm : countUntil; 9 10 struct RefCounted(T, ALLOC = ThreadMem) 11 { 12 import core.memory : GC; 13 mixin Embed!(m_object, false); 14 static if (!is(ALLOC == AppMem)) enum NOGC = true; 15 enum isRefCounted = true; 16 17 enum ElemSize = AllocSize!T; 18 alias TR = RefTypeOf!T; 19 private TR m_object; 20 private ulong* m_refCount; 21 private void function(void*) m_free; 22 23 static RefCounted opCall(ARGS...)(auto ref ARGS args) 24 { 25 RefCounted ret; 26 27 ret.m_object = ObjectAllocator!(T, ALLOC).alloc(args); 28 ret.m_refCount = ObjectAllocator!(ulong, ALLOC).alloc(); 29 // logTrace("refcount: ", cast(void*)ret.m_refCount, " m_object: ", cast(void*)ret.m_object, " Type: ", T.stringof); 30 (*ret.m_refCount) = 1; 31 logTrace("Allocating: ", cast(void*)ret.m_object, " of ", T.stringof, " sz: ", ElemSize, " allocator: ", ALLOC.stringof); 32 return ret; 33 } 34 35 ~this() 36 { 37 //logDebug("RefCounted dtor: ", T.stringof); 38 dtor(&this); 39 } 40 41 static void dtor(U)(U* ctxt) { 42 static if (!is (U == typeof(this))) { 43 typeof(this)* this_ = cast(typeof(this)*)ctxt; 44 this_.m_object = cast(typeof(this.m_object)) ctxt.m_object; 45 this_.m_refCount = cast(typeof(this.m_refCount)) ctxt.m_refCount; 46 this_._deinit(); 47 } 48 else { 49 ctxt._clear(); 50 } 51 } 52 53 this(this) 54 { 55 //logDebug("RefCounted copy ctor"); 56 copyctor(); 57 } 58 59 void copyctor() { 60 61 if (!m_object) 62 defaultInit(); 63 64 checkInvariants(); 65 if (m_object) (*m_refCount)++; 66 67 } 68 69 void opAssign(U : RefCounted)(in U other) const 70 { 71 if (other.m_object is this.m_object) return; 72 static if (is(U == RefCounted)) 73 (cast(RefCounted*)&this).opAssignImpl(*cast(U*)&other); 74 } 75 76 ref typeof(this) opAssign(U : RefCounted)(in U other) const 77 { 78 if (other.m_object is this.m_object) return; 79 static if (is(U == RefCounted)) 80 (cast(RefCounted*)&this).opAssignImpl(*cast(U*)&other); 81 return this; 82 } 83 84 private void opAssignImpl(U)(U other) { 85 _clear(); 86 m_object = cast(typeof(this.m_object))other.m_object; 87 m_refCount = other.m_refCount; 88 static if (!is (U == typeof(this))) { 89 static void destr(void* ptr) { 90 U.dtor(cast(typeof(&this))ptr); 91 } 92 m_free = &destr; 93 } else 94 m_free = other.m_free; 95 if( m_object ) 96 (*m_refCount)++; 97 } 98 99 private void _clear() 100 { 101 checkInvariants(); 102 if( m_object ){ 103 if( --(*m_refCount) == 0 ){ 104 //logTrace("RefCounted clear: ", T.stringof); 105 logTrace("Clearing Object: ", cast(void*)m_object); 106 if (m_free) 107 m_free(cast(void*)&this); 108 else { 109 _deinit(); 110 } 111 } 112 } 113 114 m_object = null; 115 m_refCount = null; 116 m_free = null; 117 } 118 119 bool opCast(U : bool)() const nothrow 120 { 121 //try logTrace("RefCounted opcast: bool ", T.stringof); catch {} 122 return !(m_object is null && !m_refCount && !m_free); 123 } 124 125 U opCast(U : Object)() const nothrow 126 if (!__traits(hasMember, U, "isRefCounted")) 127 { 128 return cast(U) m_object; 129 } 130 131 U opCast(U)() const nothrow 132 if (__traits(hasMember, U, "isRefCounted") && (isImplicitlyConvertible!(U.T, T) || isImplicitlyConvertible!(T, U.T))) 133 { 134 //try logTrace("RefCounted opcast: ", T.stringof, " => ", U.stringof); catch {} 135 static assert(U.sizeof == typeof(this).sizeof, "Error, U: "~ U.sizeof.to!string~ " != this: " ~ typeof(this).sizeof.to!string); 136 try { 137 U ret = U.init; 138 ret.m_object = cast(U.TR)this.m_object; 139 140 static if (!is (U == typeof(this))) { 141 if (!m_free) { 142 static void destr(void* ptr) { 143 dtor(cast(U*)ptr); 144 } 145 ret.m_free = &destr; 146 } 147 else 148 ret.m_free = m_free; 149 } 150 else ret.m_free = m_free; 151 152 ret.m_refCount = cast(ulong*)this.m_refCount; 153 (*ret.m_refCount) += 1; 154 return ret; 155 } catch(Throwable e) { try logError("Error in catch: ", e.toString()); catch {} } 156 return U.init; 157 } 158 159 private void _deinit() { 160 //logTrace("Freeing: ", T.stringof, " ptr ", cast(void*) m_object, " sz: ", ElemSize, " allocator: ", ALLOC.stringof); 161 ObjectAllocator!(T, ALLOC).free(m_object); 162 //logTrace("Freeing refcount: ", cast(void*)m_refCount, " object: ", cast(void*)m_object, " Type: ", T.stringof); 163 ObjectAllocator!(ulong, ALLOC).free(m_refCount); 164 m_refCount = null; 165 m_object = null; 166 } 167 168 169 private @property ulong refCount() const { 170 if (!m_refCount) return 0; 171 return *m_refCount; 172 } 173 174 175 private void defaultInit() const { 176 static if (__traits(compiles, { this.opCall(); }())) { 177 if (!m_object) { 178 auto newObj = this.opCall(); 179 (cast(RefCounted*)&this).m_object = newObj.m_object; 180 (cast(RefCounted*)&this).m_refCount = newObj.m_refCount; 181 newObj.m_object = null; 182 } 183 } 184 185 } 186 187 private void checkInvariants() 188 const { 189 assert(!m_object || refCount > 0, (!m_object) ? "No m_object" : "Zero Refcount: " ~ refCount.to!string ~ " for " ~ T.stringof); 190 } 191 }