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