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 C _rcClassGet()
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 		_rcClassDestroy();
57 		_rcClassStore = null;
58 		return this;
59 	} ///
60 
61 	ref typeof(this) opAssign(T)(auto ref T value)
62 	if (is(T == RCClass!U, U) && is(typeof({U u; C c = u;})))
63 	{
64 		_rcClassDestroy();
65 		_rcClassStore = cast(RCClassStore!C*)value._rcClassStore;
66 		if (_rcClassStore)
67 			_rcClassStore.refCount++;
68 		return this;
69 	} ///
70 
71 	T opCast(T)()
72 	if (is(T == RCClass!U, U) && is(typeof({C c; U u = c;})))
73 	{
74 		T result;
75 		result._rcClassStore = cast(typeof(result._rcClassStore))_rcClassStore;
76 		if (_rcClassStore)
77 			_rcClassStore.refCount++;
78 		return result;
79 	} ///
80 
81 	bool opCast(T)()
82 	if (is(T == bool))
83 	{
84 		return !!_rcClassStore;
85 	} ///
86 
87 	auto opCall(Args...)(auto ref Args args)
88 	if (is(typeof(_rcClassGet.opCall(args))))
89 	{
90 		return _rcClassGet.opCall(args);
91 	} ///
92 
93 	// lifetime
94 
95 	void _rcClassDestroy()
96 	{
97 		if (_rcClassStore && --_rcClassStore.refCount == 0)
98 		{
99 			static if (__traits(hasMember, C, "__xdtor"))
100 				_rcClassGet.__xdtor();
101 			GC.free(_rcClassStore);
102 		}
103 	}
104 
105 	this(this)
106 	{
107 		if (_rcClassStore)
108 			_rcClassStore.refCount++;
109 	}
110 
111 	~this()
112 	{
113 		_rcClassDestroy();
114 	}
115 }
116 
117 /// Constructs a new reference-counted instance of `C`.
118 /// (An external factory function is used instead of `static opCall`
119 /// to avoid conflicting with the class's non-static `opCall`.)
120 template rcClass(C)
121 if (is(C == class))
122 {
123 	RCClass!C rcClass(Args...)(auto ref Args args)
124 	if (is(C == class) && is(typeof(emplace!C(null, args))))
125 	{
126 		RCClass!C c;
127 		c._rcClassStore = new RCClassStore!C;
128 		c._rcClassStore.refCount = 1;
129 		emplace!C(c._rcClassStore.data[], args);
130 		return c;
131 	}
132 }
133 
134 /// Constructors
135 unittest
136 {
137 	void ctorTest(bool haveArglessCtor, bool haveArgCtor)()
138 	{
139 		static class C
140 		{
141 			int n = -1;
142 
143 			static if (haveArglessCtor)
144 				this() { n = 1; }
145 
146 			static if (haveArgCtor)
147 				this(int val) { n = val; }
148 
149 			~this() { n = -2; }
150 		}
151 
152 		RCClass!C rc;
153 		assert(!rc);
154 
155 		static if (haveArglessCtor || !haveArgCtor)
156 		{
157 			rc = rcClass!C();
158 			assert(rc);
159 			static if (haveArglessCtor)
160 				assert(rc.n == 1);
161 			else
162 				assert(rc.n == -1); // default value
163 		}
164 		else
165 			static assert(!is(typeof(rcClass!C())));
166 
167 		static if (haveArgCtor)
168 		{
169 			rc = rcClass!C(42);
170 			assert(rc);
171 			assert(rc.n == 42);
172 		}
173 		else
174 			static assert(!is(typeof(rcClass!C(1))));
175 
176 		rc = null;
177 		assert(!rc);
178 	}
179 
180 	import std.meta : AliasSeq;
181 	foreach (haveArglessCtor; AliasSeq!(false, true))
182 		foreach (haveArgCtor; AliasSeq!(false, true))
183 			ctorTest!(haveArglessCtor, haveArgCtor);
184 }
185 
186 /// Lifetime
187 unittest
188 {
189 	static class C
190 	{
191 		static int counter;
192 
193 		this() { counter++; }
194 		~this() { counter--; }
195 	}
196 
197 	{
198 		auto a = rcClass!C();
199 		assert(C.counter == 1);
200 		auto b = a;
201 		assert(C.counter == 1);
202 	}
203 	assert(C.counter == 0);
204 }
205 
206 /// Inheritance
207 unittest
208 {
209 	static class Base
210 	{
211 		int foo() { return 1; }
212 	}
213 
214 	static class Derived : Base
215 	{
216 		override int foo() { return 2; }
217 	}
218 
219 	auto derived = rcClass!Derived();
220 	RCClass!Base base = derived; // initialization
221 	base = derived;              // assignment
222 	static assert(!is(typeof(derived = base)));
223 	auto base2 = cast(RCClass!Base)derived;
224 }
225 
226 /// Non-static opCall
227 unittest
228 {
229 	static class C
230 	{
231 		int calls;
232 		void opCall() { calls++; }
233 	}
234 
235 	auto c = rcClass!C();
236 	assert(c.calls == 0);
237 	c();
238 	assert(c.calls == 1);
239 }