1 module memutils.refcounted;
2 
3 import memutils.allocators;
4 import memutils.helpers;
5 import memutils.utils;
6 import std.traits : hasMember;
7 struct RefCounted(T, ALLOC = ThreadMem)
8 {
9 	
10 nothrow:
11 @trusted:
12 	mixin Embed!(m_object, false);
13 	enum NOGC = true;
14 	enum isRefCounted = true;
15 	alias ThisType = RefCounted!(T, ALLOC);
16 	enum ElemSize = AllocSize!T;
17 	alias TR = RefTypeOf!T;	
18 	private TR m_object;
19 	private ulong* m_refCount;
20 	private void function(void*) m_free;
21 	
22 	pragma(inline)
23 	static RefCounted opCall(ARGS...)(auto ref ARGS args) nothrow
24 	{
25 		//logTrace("RefCounted opCall");
26 		//try { 
27 			RefCounted!(T, ALLOC) ret;
28 			if (!ret.m_object)
29 				ret.m_object = ObjectAllocator!(T, ALLOC)().alloc(args);
30 			ret.m_refCount = ObjectAllocator!(ulong, ALLOC)().alloc();
31 			(*ret.m_refCount) = 1;
32 			return ret;
33 		//} catch (Throwable e) { assert(false, "RefCounted.opCall(args) Throw: " ~ e.toString()); }
34 		assert(false, "Count not return from opCall");
35 	}
36 	
37 	~this()
38 	{
39 		dtor((cast(RefCounted*)&this)); 
40 	}
41 	
42 	static void dtor(U)(U* ctxt) {
43 		//logTrace("Call dtor ", U.stringof, " for ", typeof(this).stringof);
44 		static if (!is (U == typeof(this))) {
45 			ThisType* this_ = cast(ThisType*)ctxt;
46 			this_.m_object = cast(TR) ctxt.m_object;
47 			this_.m_refCount = cast(ulong*) ctxt.m_refCount;
48 			this_._deinit();
49 		}
50 		else {
51 			ctxt._clear();
52 		}
53 	}
54 	
55 	this(this)
56 	{
57 		//logTrace("this(this)");
58 		(cast(RefCounted*)&this).copyctor();
59 	}
60 	
61 	//@inline
62 	void copyctor() {
63 		
64 		if (!m_object) {
65 			defaultInit(); 
66 			//checkInvariants();
67 		}
68 
69 		else if (m_object) {
70 			//logTrace("copyctr ++", *m_refCount);
71 			(*m_refCount)++;
72 		} 	
73 	}
74 	
75 	void opAssign(U : RefCounted)(in U other) const nothrow
76 	{
77 		if (other.m_object is this.m_object) return;
78 			static if (is(U == RefCounted))
79 				(cast(RefCounted*)&this).opAssignImpl(other);
80 	}
81 	
82 	ref typeof(this) opAssign(U : RefCounted)(in U other) const nothrow
83 	{
84 		if (other.m_object is this.m_object) return;
85 		static if (is(U == RefCounted))
86 			(cast(RefCounted*)&this).opAssignImpl(other);
87 		return this;
88 	}
89 	
90 	private void opAssignImpl(U)(U other) {
91 		_clear();
92 		m_object = other.m_object;
93 		m_refCount = other.m_refCount;
94 		static if (!is (U == typeof(this))) {
95 			static void destr(void* ptr) {
96 				U.dtor(cast(ThisType*)ptr);
97 			}
98 			m_free = &destr;
99 		} else
100 			m_free = other.m_free;
101 		if( m_object ) {
102 			(*m_refCount)++;
103 			//logTrace("Incr: ", U.stringof, " = ", *m_refCount);
104 		}
105 	}
106 	
107 	private void _clear()
108 	{
109 		
110 		//logTrace("Clear: ", T.stringof, " = ", m_object ? *m_refCount : 9);
111 		//checkInvariants();
112 		if( m_object ){
113 			if( --(*m_refCount) == 0 ){
114 				if (m_free)
115 					m_free(cast(void*)&this);
116 				else {
117 					_deinit();
118 				}
119 			}
120 		}
121 		
122 		m_object = null;
123 		m_refCount = null;
124 		m_free = null;
125 	}
126 
127 	bool opCast(U : bool)() const nothrow
128 	{
129 		//try logTrace("RefCounted opcast: bool ", T.stringof); catch {}
130 		return !(!m_object || !m_refCount);
131 	}
132 
133 	U opCast(U)() const nothrow
134 		if (__traits(hasMember, U, "isRefCounted"))
135 	{
136 		//static assert(U.sizeof == typeof(this).sizeof, "Error, U:  != this: ");
137 	
138 		U ret = U.init;
139 		ret.m_object = cast(U.TR)this.m_object;
140 
141 		static if (!is (U == typeof(this))) {
142 			if (!m_free) {
143 				static void destr(void* ptr) @trusted {
144 					dtor(cast(U*)ptr);
145 				}
146 				ret.m_free = &destr;
147 			}
148 			else
149 				ret.m_free = m_free;
150 		}
151 		else ret.m_free = m_free;
152 		
153 		ret.m_refCount = cast(ulong*)this.m_refCount;
154 		//logTrace("OpCast++ ", *ret.m_refCount);
155 		(*ret.m_refCount) += 1;
156 		return ret;
157 	}
158 
159 	U opCast(U : Object)() const nothrow
160 		if (!__traits(hasMember, U, "isRefCounted"))
161 	{
162 		// todo: check this
163 		return cast(U) m_object;
164 	}
165 
166 	U opCast(U : TR)() nothrow
167 		if (!__traits(hasMember, U, "isRefCounted"))
168 	{
169 		// todo: check this
170 		return m_object;
171 	}
172 	//@inline
173 	private @property ulong refCount() const {
174 		if (!m_refCount) return 0;
175 		return *m_refCount;
176 	}
177 
178 	private void _deinit() {
179 		//logTrace("_deinit");
180 		TR obj_ptr = m_object;
181 		//static if (!isPointer!T) // call destructors but not for indirections...
182 		//	.destroy(m_object);
183 		
184 		if (obj_ptr !is null)
185 			ObjectAllocator!(T, ALLOC)().free(obj_ptr);
186 		
187 		ObjectAllocator!(ulong, ALLOC)().free(m_refCount);
188 		m_refCount = null;
189 		m_object = null;
190 	}
191 
192 	pragma(inline)
193 	private void defaultInit(ARGS...)(ARGS args) const {
194 		
195 		if (!m_object) {
196 			//logTrace("DefaultInit1");
197 			auto newObj = this.opCall(args);
198 			(cast()this).m_object = newObj.m_object;
199 			(cast()this).m_refCount = newObj.m_refCount;
200 			newObj.m_object = null;
201 			newObj.m_refCount = null;
202 		}
203 	}
204 	
205 	pragma(inline)
206 	private void defaultInit() const {
207 		
208 		if (!m_object) {
209 			//logTrace("DefaultInit2");
210 			auto newObj = this.opCall();
211 			(cast()this).m_object = newObj.m_object;
212 			(cast()this).m_refCount = newObj.m_refCount;
213 			newObj.m_object = null;
214 			newObj.m_refCount = null;
215 		}
216 	}
217 
218 	pragma(inline)
219 	private void checkInvariants()
220 	const {
221 		//logTrace("Check invariants, m_object ", m_object ? '1' : '0', " refcount ", refCount);
222 		assert(!m_object || refCount > 0);
223 	}
224 }