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;
14 
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 		ret.m_object = ObjectAllocator!(T, ALLOC).alloc(args);
27 		ret.m_refCount = ObjectAllocator!(ulong, ALLOC).alloc();
28 		(*ret.m_refCount) = 1;
29 		logTrace("Allocating: ", cast(void*)ret.m_object, " of ", T.stringof, " sz: ", ElemSize, " allocator: ", ALLOC.stringof);
30 		return ret;
31 	}
32 	
33 	const ~this()
34 	{
35 		//logDebug("RefCounted dtor: ", T.stringof);
36 		dtor((cast(RefCounted*)&this));
37 	}
38 	
39 	static void dtor(U)(U* ctxt) {
40 		static if (!is (U == typeof(this))) {
41 			typeof(this)* this_ = cast(typeof(this)*)ctxt;
42 			this_.m_object = cast(typeof(this.m_object)) ctxt.m_object;
43 			this_._deinit();
44 		}
45 		else {
46 			ctxt._clear();
47 		}
48 	}
49 	
50 	const this(this)
51 	{
52 		//logDebug("RefCounted copy ctor");
53 		(cast(RefCounted*)&this).copyctor();
54 	}
55 	
56 	void copyctor() {
57 		
58 		if (!m_object)
59 			defaultInit();
60 
61 		checkInvariants();
62 		if (m_object) (*m_refCount)++; 
63 		
64 	}
65 	
66 	void opAssign(U : RefCounted)(in U other) const
67 	{
68 		if (other.m_object is this.m_object) return;
69 		static if (is(U == RefCounted))
70 			(cast(RefCounted*)&this).opAssignImpl(*cast(U*)&other);
71 	}
72 	
73 	ref typeof(this) opAssign(U : RefCounted)(in U other) const
74 	{
75 		if (other.m_object is this.m_object) return;
76 		static if (is(U == RefCounted))
77 			(cast(RefCounted*)&this).opAssignImpl(*cast(U*)&other);
78 		return this;
79 	}
80 	
81 	private void opAssignImpl(U)(U other) {
82 		_clear();
83 		m_object = cast(typeof(this.m_object))other.m_object;
84 		m_refCount = other.m_refCount;
85 		static if (!is (U == typeof(this))) {
86 			static void destr(void* ptr) {
87 				U.dtor(cast(typeof(&this))ptr);
88 			}
89 			m_free = &destr;
90 		} else
91 			m_free = other.m_free;
92 		if( m_object )
93 			(*m_refCount)++;
94 	}
95 	
96 	private void _clear()
97 	{
98 		checkInvariants();
99 		if( m_object ){
100 			if( --(*m_refCount) == 0 ){
101 				//logTrace("RefCounted clear: ", T.stringof);
102 				logTrace("Clearing Object: ", cast(void*)m_object);
103 				if (m_free)
104 					m_free(cast(void*)&this);
105 				else {
106 					_deinit();
107 				}
108 			}
109 		}
110 		
111 		m_object = null;
112 		m_refCount = null;
113 		m_free = null;
114 	}
115 
116 	bool opCast(U : bool)() const nothrow
117 	{
118 		//try logTrace("RefCounted opcast: bool ", T.stringof); catch {}
119 		return !(m_object is null && !m_refCount && !m_free);
120 	}
121 
122 	U opCast(U)() const nothrow
123 		if (__traits(hasMember, U, "isRefCounted") && (isImplicitlyConvertible!(U.T, T) || isImplicitlyConvertible!(T, U.T)))
124 	{
125 		//try logTrace("RefCounted opcast: ", T.stringof, " => ", U.stringof); catch {}
126 		static assert(U.sizeof == typeof(this).sizeof, "Error, U: "~ U.sizeof.to!string~ " != this: " ~ typeof(this).sizeof.to!string);
127 		try { 
128 			U ret = U.init;
129 			ret.m_object = cast(U.TR)this.m_object;
130 			
131 			static if (!is (U == typeof(this))) {
132 				if (!m_free) {
133 					static void destr(void* ptr) {
134 						dtor(cast(U*)ptr);
135 					}
136 					ret.m_free = &destr;
137 				}
138 				else
139 					ret.m_free = m_free;
140 			}
141 			else ret.m_free = m_free;
142 			
143 			ret.m_refCount = cast(ulong*)this.m_refCount;
144 			(*ret.m_refCount) += 1;
145 			return ret;
146 		} catch(Throwable e) { try logError("Error in catch: ", e.toString()); catch {} }
147 		return U.init;
148 	}
149 
150 	private void _deinit() {
151 		//logTrace("Freeing: ", T.stringof, " ptr ", cast(void*) m_object, " sz: ", ElemSize, " allocator: ", ALLOC.stringof);
152 		ObjectAllocator!(T, ALLOC).free(m_object);
153 		//logTrace("Freeing refCount: ", cast(void*)m_refCount);
154 		ObjectAllocator!(ulong, ALLOC).free(m_refCount);
155 		m_refCount = null;
156 		m_object = null;
157 	}
158 
159 
160 	private @property ulong refCount() const {
161 		if (!m_refCount) return 0;
162 		return *m_refCount;
163 	}
164 
165 
166 	private void defaultInit() const {
167 		static if (__traits(compiles, { this.opCall(); }())) {
168 			if (!m_object) {
169 				auto newObj = this.opCall();
170 				(cast(RefCounted*)&this).m_object = newObj.m_object;
171 				(cast(RefCounted*)&this).m_refCount = newObj.m_refCount;
172 				newObj.m_object = null;
173 			}
174 		}
175 		
176 	}
177 	
178 	private void checkInvariants()
179 	const {
180 		assert(!m_object || refCount > 0, (!m_object) ? "No m_object" : "Zero Refcount: " ~ refCount.to!string ~ " for " ~ T.stringof);
181 	}
182 }