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