1 module lwdr.refcount;
2 
3 import lwdr : LWDR;
4 import lwdr.tracking;
5 
6 // Based on AutoMem
7 
8 /// Detects if a type is ref countable (is an interface, class, pointer or dynamic array)
9 enum isRefCountable(T) = 
10 	is(T == interface) || // interface
11 	is(T == class) || // class
12 	is(T == U*, U) || // pointer to something
13 	is(T == U[], U); // dynamic array
14 
15 /++
16 Reference count for item of type `T`. Intended for use with
17 interfaces, classes, pointers to structs, and arrays.
18 ++/
19 struct RefCount(T)
20 	if(isRefCountable!T)
21 {
22 	/// Begin reference counting an existing item
23 	this(auto ref T t)
24 	{
25 		version(LWDR_TrackMem)
26 		{
27 			bool tracking = isCurrentTracking();
28 
29 			if(tracking)
30 				disableMemoryTracking();
31 
32 			scope(exit)
33 			{
34 				if(tracking)
35 					enableMemoryTracking();
36 			}
37 		}
38 
39 		interior = new Interior(t);
40 	}
41 
42 	/// Create a new item and reference count it
43 	this(Args...)(auto ref Args args)
44 	{
45 		version(LWDR_TrackMem)
46 		{
47 			bool tracking = isCurrentTracking();
48 
49 			if(tracking)
50 				disableMemoryTracking();
51 
52 			scope(exit)
53 			{
54 				if(tracking)
55 					enableMemoryTracking();
56 			}
57 		}
58 
59 		static if(is(T == class))
60 			interior = new Interior(new T(args));
61         static if(is(T == U*, U))
62         {
63             alias root = rootType!T;
64             static if(is(root == struct) || is(root == union))
65                 interior = new Interior(new root(args));
66             else
67             {
68                 interior = new Interior(new root);
69                 *interior.item = args[0];
70             }
71         }
72         static if(is(T == U[], U))
73         {
74             alias root = rootType!T;
75             interior = new Interior([args]);
76         }
77 	}
78 
79 	/// Copy
80 	this(this)
81 	{
82 		if(interior !is null)
83 			inc;
84 	}
85 
86 	~this()
87 	{
88 		release;
89 	}
90 
91 	/// Assign from an l-value RefCount
92 	void opAssign(ref RefCount other)
93 	{
94 		if(interior == other.interior) return;
95 		if(interior !is null) release;
96 		interior = other.interior;
97 		inc;
98 	}
99 
100 	/// Dereference the RefCount and access the contained item
101 	ref auto opUnary(string s)() inout if(s == "*")
102 	{
103 		return interior.item;
104 	}
105 
106 	static if(is(T == U[], U)) // if an array
107     {
108         /// Expose array's opSlice and opIndex
109         auto ref opSlice(B, E)(auto ref B b, auto ref E e)
110         {
111             return interior.item[b .. e];
112         }
113         /// ditto
114         auto ref opIndex(A...)(auto ref A args)
115         {
116             return interior.item[args];
117         }
118         /// ditto
119         auto ref opIndexAssign(I, V)(auto ref V v, auto ref I i)
120         {
121             return interior.item[i] = v;
122         }
123     }
124     else
125     {
126         /// Expose item's opSlice and opIndex
127         auto ref opSlice(A...)(auto ref A args)
128             if(__traits(compiles, (rootType!T).init.opSlice(args)))
129 		{
130 			return interior.item.opSlice(args);
131 		}
132         /// ditto
133         auto ref opIndex(A...)(auto ref A args)
134             if(__traits(compiles, (rootType!T).init.opIndex(args)))
135 		{
136 			return interior.item.opIndex(args);
137 		}
138         /// ditto
139         auto ref opIndexAssign(A...)(auto ref A args)
140             if(__traits(compiles, (rootType!T).init.opIndexAssign(args)))
141 		{
142 			return interior.item.opIndexAssign(args);
143 		}
144     }
145 
146 	/// A heap allocated struct which contains the item and number of references
147 	static struct Interior
148 	{
149 		static if(is(T == shared))
150 			shared size_t count;
151 		else 
152 			size_t count;
153 
154 		T item;
155 		alias item this;
156 
157 		private this(T item) 
158 		{
159 			count = 1;
160 			this.item = item;
161 		}
162 
163 		/// Increment number of references
164 		private void inc() 
165 		{
166 			static if(is(T == shared))
167 			{
168 				import core.atomic : atomicOp;
169 				count.atomicOp!"+="(1);
170 			}
171 			else
172 				count++;
173 		}
174 
175 		/// Decrement number of references
176 		private void dec() 
177 		{
178 			static if(is(T == shared))
179 			{
180 				import core.atomic : atomicOp;
181 				count.atomicOp!"-="(1);
182 			}
183 			else
184 				count--;
185 		}
186 	}
187 
188 	static if(is(T == shared))
189 		shared Interior* interior;
190 	else
191 		Interior* interior;
192 	alias interior this;
193 
194 	/// Release reference to the interior. If count is zero, then deallocate
195 	private void release()
196 	{
197 		if(interior is null) return;
198 		assert(interior.count > 0, "Try to release RefCount but count is <=0");
199 
200 		dec;
201 
202 		if(interior.count == 0)
203 			LWDR.free(interior.item);
204 	}
205 }