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 }