1 /**
2  * Basic reference-counting for classes.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.utils.meta.rcclass;
15 
16 import core.memory;
17 
18 import std.conv : emplace;
19 
20 private struct RCClassStore(C)
21 {
22 	size_t refCount = void;
23 	void[__traits(classInstanceSize, C)] data = void;
24 }
25 
26 /// Wraps class type `C` into a reference-counting value type.
27 struct RCClass(C)
28 if (is(C == class))
29 {
30 	// storage
31 
32 	private RCClassStore!C* _rcClassStore;
33 
34 	@property inout(C) _rcClassGet() inout
35 	{
36 		return cast(C)_rcClassStore.data.ptr;
37 	}
38 
39 	alias _rcClassGet this;
40 
41 	// construction
42 
43 	this(T)(T value)
44 	if (is(T == RCClass!U, U) && is(typeof({U u; C c = u;})))
45 	{
46 		_rcClassStore = cast(RCClassStore!C*)value._rcClassStore;
47 		if (_rcClassStore)
48 			_rcClassStore.refCount++;
49 	}
50 
51 	// operations
52 
53 	ref typeof(this) opAssign(T)(T value)
54 	if (is(T == typeof(null)))
55 	{
56 		assert(value is null);
57 		_rcClassDestroy();
58 		_rcClassStore = null;
59 		return this;
60 	} ///
61 
62 	ref typeof(this) opAssign(T)(auto ref T value)
63 	if (is(T == RCClass!U, U) && is(typeof({U u; C c = u;})))
64 	{
65 		_rcClassDestroy();
66 		_rcClassStore = cast(RCClassStore!C*)value._rcClassStore;
67 		if (_rcClassStore)
68 			_rcClassStore.refCount++;
69 		return this;
70 	} ///
71 
72 	inout(T) opCast(T)() inout
73 	if (is(T == RCClass!U, U) && is(typeof({C c; U u = cast(U)c;})))
74 	{
75 		static if (!is(T == RCClass!U, U)) // Destructure into U
76 			assert(false);
77 		inout(T) result;
78 		// Check if dynamic cast is valid
79 		if (!cast(U)this._rcClassGet())
80 			return result;
81 		cast()result._rcClassStore = cast(typeof(result._rcClassStore))_rcClassStore;
82 		if (_rcClassStore)
83 			(cast()_rcClassStore.refCount)++;
84 		return result;
85 	} ///
86 
87 	bool opCast(T)() const
88 	if (is(T == bool))
89 	{
90 		return !!_rcClassStore;
91 	} ///
92 
93 	auto opCall(Args...)(auto ref Args args)
94 	if (is(typeof(_rcClassGet.opCall(args))))
95 	{
96 		return _rcClassGet.opCall(args);
97 	} ///
98 
99 	// lifetime
100 
101 	void _rcClassDestroy()
102 	{
103 		if (_rcClassStore && --_rcClassStore.refCount == 0)
104 		{
105 			static if (__traits(hasMember, C, "__xdtor"))
106 				_rcClassGet.__xdtor();
107 			GC.free(_rcClassStore);
108 		}
109 	}
110 
111 	this(this)
112 	{
113 		if (_rcClassStore)
114 			_rcClassStore.refCount++;
115 	}
116 
117 	~this()
118 	{
119 		_rcClassDestroy();
120 	}
121 }
122 
123 /// Constructs a new reference-counted instance of `C`.
124 /// (An external factory function is used instead of `static opCall`
125 /// to avoid conflicting with the class's non-static `opCall`.)
126 template rcClass(C)
127 if (is(C == class))
128 {
129 	RCClass!C rcClass(Args...)(auto ref Args args)
130 	if (is(C == class) && is(typeof(emplace!C(null, args))))
131 	{
132 		RCClass!C c;
133 		c._rcClassStore = new RCClassStore!C;
134 		c._rcClassStore.refCount = 1;
135 		emplace!C(c._rcClassStore.data[], args);
136 		return c;
137 	}
138 }
139 
140 /// Constructors
141 unittest
142 {
143 	void ctorTest(bool haveArglessCtor, bool haveArgCtor)()
144 	{
145 		static class C
146 		{
147 			int n = -1;
148 
149 			static if (haveArglessCtor)
150 				this() { n = 1; }
151 
152 			static if (haveArgCtor)
153 				this(int val) { n = val; }
154 
155 			~this() { n = -2; }
156 		}
157 
158 		RCClass!C rc;
159 		assert(!rc);
160 
161 		static if (haveArglessCtor || !haveArgCtor)
162 		{
163 			rc = rcClass!C();
164 			assert(rc);
165 			static if (haveArglessCtor)
166 				assert(rc.n == 1);
167 			else
168 				assert(rc.n == -1); // default value
169 		}
170 		else
171 			static assert(!is(typeof(rcClass!C())));
172 
173 		static if (haveArgCtor)
174 		{
175 			rc = rcClass!C(42);
176 			assert(rc);
177 			assert(rc.n == 42);
178 		}
179 		else
180 			static assert(!is(typeof(rcClass!C(1))));
181 
182 		rc = null;
183 		assert(!rc);
184 	}
185 
186 	import std.meta : AliasSeq;
187 	foreach (haveArglessCtor; AliasSeq!(false, true))
188 		foreach (haveArgCtor; AliasSeq!(false, true))
189 			ctorTest!(haveArglessCtor, haveArgCtor);
190 }
191 
192 /// Lifetime
193 unittest
194 {
195 	static class C
196 	{
197 		static int counter;
198 
199 		this() { counter++; }
200 		~this() { counter--; }
201 	}
202 
203 	{
204 		auto a = rcClass!C();
205 		assert(C.counter == 1);
206 		auto b = a;
207 		assert(C.counter == 1);
208 	}
209 	assert(C.counter == 0);
210 }
211 
212 /// Inheritance
213 unittest
214 {
215 	static class Base
216 	{
217 		int foo() { return 1; }
218 	}
219 
220 	static class Derived : Base
221 	{
222 		override int foo() { return 2; }
223 	}
224 
225 	auto derived = rcClass!Derived();
226 	RCClass!Base base = derived; // initialization
227 	base = derived;              // assignment
228 	static assert(!is(typeof(derived = base)));
229 	auto base2 = cast(RCClass!Base)derived;
230 }
231 
232 /// Non-static opCall
233 unittest
234 {
235 	static class C
236 	{
237 		int calls;
238 		void opCall() { calls++; }
239 	}
240 
241 	auto c = rcClass!C();
242 	assert(c.calls == 0);
243 	c();
244 	assert(c.calls == 1);
245 }
246 
247 /// Casting
248 unittest
249 {
250 	static class A {}
251 	static class B : A {}
252 	static class C : A {}
253 	RCClass!A a = rcClass!B();
254 	assert( cast(RCClass!B)a);
255 	assert(!cast(RCClass!C)a);
256 }