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