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