1 /**
2  * An std::vector-like type for deterministic lifetime.
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.vec;
15 
16 import std.algorithm.mutation : swap, move;
17 import std.meta : allSatisfy;
18 
19 package(ae):
20 
21 /*
22   An array type with deterministic lifetime.
23 
24   Properties:
25   - Owns its data
26   - If copied, will copy its contents
27 	- Use pointers / `ref` or `opSlice` to avoid copying
28     - Use `std.typecons.RefCounted` for reference counting
29   - If destroyed, will destroy (clobber) its contents
30   - O(1) indexing
31   - Does not work with `.init`-less types
32     (wrap in `Nullable` to avoid this)
33 
34   Differences from std.containers.array.Array:
35   - Memory-safe
36   - Like D arrays, has an initial null state (distinct from the empty state)
37   - No reference counting
38   - Uses the D GC heap
39   - Separates object lifetime from memory lifetime:
40     the latter is still managed by the GC,
41 	so `Vec` is always memory-safe regardless of how you try to (mis-)use it
42 */
43 struct Vec(T)
44 {
45 	// Lifetime
46 
47 	/// Construct from a list or slice of values
48 	this(scope T[] values...)
49 	{
50 		data = values.dup;
51 	}
52 
53 	private enum bool isConstituent(C) = is(C == T) || is(C == T[]) || is(C == Vec!T);
54 
55 	/// Construct from any combination of values, slices of values, or
56 	/// other `Vec` instances
57 	this(Args...)(auto ref scope Args args)
58 	if (allSatisfy!(isConstituent, Args))
59 	{
60 		size_t length;
61 		foreach (ref arg; args)
62 			static if (is(typeof(arg) == T))
63 				length++;
64 			else
65 				length += arg.length;
66 		data = new T[length];
67 		size_t p = 0;
68 		foreach (ref arg; args)
69 			static if (is(typeof(arg) == T))
70 				data[p++] = arg;
71 			else
72 			{
73 				static if (is(typeof(arg) == Vec!T))
74 					data[p .. p + arg.length] = arg.data[];
75 				else
76 					data[p .. p + arg.length] = arg[];
77 				p += arg.length;
78 			}
79 	}
80 
81 	/// To avoid performance pitfalls, implicit copying is disabled.
82 	/// Use `.dup` instead.
83 	@disable this(this);
84 
85 	/// Create a shallow copy of this `Vec`.
86 	Vec!T dup()
87 	{
88 		typeof(return) result;
89 		result.data = data.dup;
90 		return result;
91 	}
92 
93 	~this()
94 	{
95 		data[] = T.init;
96 		data = null;
97 	}
98 
99 	/// Array primitives
100 
101 	@property size_t length() const { return data.length; }
102 	alias opDollar = length; /// ditto
103 
104 	@property size_t length(size_t newLength)
105 	{
106 		if (newLength < data.length)
107 		{
108 			data[newLength .. $] = T.init;
109 			data = data[0 .. newLength];
110 		}
111 		else
112 		if (newLength > data.length)
113 		{
114 			T[] newData;
115 			if (newLength <= data.capacity)
116 			{
117 				newData = data;
118 				newData.length = newLength;
119 				assert(newData.ptr == data.ptr);
120 			}
121 			else
122 			{
123 				newData = new T[newLength];
124 				foreach (i; 0 .. data.length)
125 					move(data[i], newData[i]);
126 			}
127 			data = newData;
128 		}
129 		return data.length;
130 	} /// ditto
131 
132 	T opCast(T)() if (is(T == bool))
133 	{
134 		return !!data;
135 	} /// ditto
136 
137 	ref inout(T) opIndex(size_t index) inout
138 	{
139 		return data[index];
140 	} /// ditto
141 
142 	typeof(null) opAssign(typeof(null)) { data[] = T.init; data = null; return null; } /// ditto
143 
144 	ref Vec opOpAssign(string op : "~")(scope T[] values...)
145 	{
146 		auto oldLength = length;
147 		length = oldLength + values.length;
148 		data[oldLength .. $] = values;
149 		return this;
150 	} /// ditto
151 
152 	/// Range-like primitives
153 
154 	@property bool empty() const { return !data.length; }
155 
156 	ref inout(T) front() inout { return data[0]; } /// ditto
157 	ref inout(T) back() inout { return data[$-1]; } /// ditto
158 
159 	void popFront()
160 	{
161 		data[0] = T.init;
162 		data = data[1 .. $];
163 	} /// ditto
164 
165 	void popBack()
166 	{
167 		data[$-1] = T.init;
168 		data = data[0 .. $-1];
169 	} /// ditto
170 
171 	// Other operations
172 
173 	/// Return a slice of the held items.
174 	/// Ownership is unaffected, so this is a "view" into the contents.
175 	/// Can be used to perform range operations and iteration.
176 	inout(T)[] opSlice() inout
177 	{
178 		return data;
179 	}
180 
181 	/// Remove the element with the given `index`, shifting all
182 	/// elements after it to the left.
183 	void remove(size_t index)
184 	{
185 		foreach (i; index + 1 .. data.length)
186 			move(data[i], data[i - 1]);
187 		data = data[0 .. $ - 1];
188 	}
189 
190 private:
191 	T[] data;
192 }
193 
194 // Test object lifetime
195 unittest
196 {
197 	struct S
198 	{
199 		static int numLive;
200 		bool alive;
201 		this(bool) { alive = true; numLive++; }
202 		this(this) { if (alive) numLive++; }
203 		~this() { if (alive) numLive--; }
204 	}
205 
206 	Vec!S v;
207 	assert(S.numLive == 0);
208 	v = Vec!S(S(true));
209 	assert(S.numLive == 1);
210 	v ~= S(true);
211 	assert(S.numLive == 2);
212 	auto v2 = v.dup;
213 	assert(S.numLive == 4);
214 	v2 = null;
215 	assert(S.numLive == 2);
216 	v.popFront();
217 	assert(S.numLive == 1);
218 	v.popBack();
219 	assert(S.numLive == 0);
220 }
221 
222 // Test iteration
223 unittest
224 {
225 	// Ensure iterating twice over a Vec does not consume it.
226 	auto v = Vec!int(1, 2, 3);
227 	foreach (i; v) {}
228 	int sum;
229 	foreach (i; v) sum += i;
230 	assert(sum == 6);
231 }