1 /// Copyright: Copyright (c) 2017-2019 Andrey Penechko.
2 /// License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
3 /// Authors: Andrey Penechko.
4 module vox.utils.array;
5 
6 // Optimal for 1, 2, 4 byte items.
7 // Best with POT sized items
8 // Can store inline up to 8 bytes
9 struct Array(T)
10 {
11 	import vox.utils : isPowerOfTwo, nextPOT, divCeil, min, max, writefln, ArrayArena, format;
12 
13 	// Can be 0
14 	enum uint NUM_INLINE_ITEMS = size_t.sizeof / T.sizeof;
15 	enum uint MIN_EXTERNAL_BYTES = max(ArrayArena.MIN_BLOCK_BYTES, nextPOT((NUM_INLINE_ITEMS + 1) * T.sizeof));
16 
17 	private uint _length;
18 	private uint _capacity = NUM_INLINE_ITEMS;
19 
20 	union
21 	{
22 		// Used when length <= NUM_INLINE_ITEMS
23 		private T[NUM_INLINE_ITEMS] inlineItems;
24 
25 		// Used when length > NUM_INLINE_ITEMS
26 		private T* externalArray;
27 	}
28 
29 	bool empty() { return _length == 0; }
30 	uint length() { return _length; }
31 	uint opDollar() { return _length; }
32 	uint capacity() { return _capacity; }
33 	ref T front() { return this[0]; }
34 	ref T back() { return this[$-1]; }
35 	void clear() { _length = 0; }
36 
37 	ref T opIndex(size_t index)
38 	{
39 		assert(index < _capacity, format("opIndex(%s), capacity %s", index, _capacity));
40 		static if (NUM_INLINE_ITEMS > 0) {
41 			if (_capacity == NUM_INLINE_ITEMS) return inlineItems[index];
42 		}
43 
44 		return externalArray[index];
45 	}
46 
47 	Array!T dup(ref ArrayArena arena)
48 	{
49 		Array!T copy = this;
50 
51 		static if (NUM_INLINE_ITEMS > 0) {
52 			if (_capacity == NUM_INLINE_ITEMS) return copy;
53 		}
54 
55 		size_t byteCapacity = nextPOT(_capacity * T.sizeof);
56 
57 		// When we have empty array with NUM_INLINE_ITEMS == 0 and no allocated external array
58 		if (byteCapacity == 0) return copy;
59 
60 		ubyte[] block = (cast(ubyte*)externalArray)[0..byteCapacity];
61 
62 		ubyte[] newBlock = arena.allocBlock(block.length);
63 		newBlock[] = block;
64 		copy.externalArray = cast(T*)newBlock.ptr;
65 		return copy;
66 	}
67 
68 	void voidPut(ref ArrayArena arena, uint howMany)
69 	{
70 		if (_length + howMany > _capacity) extend(arena, howMany);
71 		_length += howMany;
72 	}
73 
74 	void put(ref ArrayArena arena, T[] items...)
75 	{
76 		if (_length + items.length > _capacity) extend(arena, cast(uint)items.length);
77 
78 		_length += items.length;
79 		this[_length-items.length..$][] = items;
80 	}
81 
82 	void putFront(ref ArrayArena arena, T item)
83 	{
84 		putAt(arena, 0, item);
85 	}
86 
87 	// shifts items to the right
88 	void putAt(ref ArrayArena arena, size_t at, T[] items...)
89 	{
90 		replaceAt(arena, at, 0, items);
91 	}
92 
93 	void replaceAt(ref ArrayArena arena, size_t at, size_t numItemsToRemove, T[] itemsToInsert)
94 	{
95 		assert(at + numItemsToRemove <= _length);
96 
97 		size_t numItemsToInsert = itemsToInsert.length;
98 
99 		replaceAtVoid(arena, at, numItemsToRemove, numItemsToInsert);
100 		this[at..at+numItemsToInsert][] = itemsToInsert;
101 	}
102 
103 	void replaceAtVoid(ref ArrayArena arena, size_t at, size_t numItemsToRemove, size_t numItemsToInsert)
104 	{
105 		assert(at + numItemsToRemove <= _length);
106 
107 		if (numItemsToInsert == numItemsToRemove)
108 		{
109 			// no resize or moves needed
110 		}
111 		else
112 		{
113 			ptrdiff_t delta = numItemsToInsert - numItemsToRemove;
114 
115 			if (_length + delta > _capacity) extend(arena, cast(uint)delta);
116 
117 			scope(exit) _length += delta;
118 
119 			size_t start = at + numItemsToRemove;
120 			size_t numItemsToMove = _length - start;
121 			T* ptr = externalArray + start;
122 
123 			static if (NUM_INLINE_ITEMS > 0) {
124 				if (_capacity == NUM_INLINE_ITEMS) ptr = inlineItems.ptr + start;
125 			}
126 
127 			import core.stdc.string : memmove;
128 			memmove(ptr + delta, ptr, numItemsToMove * T.sizeof);
129 		}
130 	}
131 
132 	void unput(size_t numItems)
133 	{
134 		_length = cast(uint)(_length - numItems);
135 	}
136 
137 	void reserve(ref ArrayArena arena, uint howMany)
138 	{
139 		if (_length + howMany > _capacity) extend(arena, howMany);
140 	}
141 
142 	// returns memory to arena and zeroes the length
143 	void free(ref ArrayArena arena) {
144 		scope(exit) {
145 			externalArray = null;
146 			_length = 0;
147 			_capacity = NUM_INLINE_ITEMS;
148 		}
149 		static if (NUM_INLINE_ITEMS > 0) {
150 			if (_capacity == NUM_INLINE_ITEMS) return; // no-op
151 		}
152 
153 		size_t byteCapacity = nextPOT(_capacity * T.sizeof);
154 		ubyte[] oldBlock = (cast(ubyte*)externalArray)[0..byteCapacity];
155 		arena.freeBlock(oldBlock);
156 	}
157 
158 	// extend the storage
159 	private void extend(ref ArrayArena arena, uint items)
160 	{
161 		uint byteCapacityNeeded = cast(uint)nextPOT((_length + items) * T.sizeof);
162 		//writefln("extend %s", _capacity);
163 		if (_capacity == NUM_INLINE_ITEMS) {
164 			ubyte[] newBlock = arena.allocBlock(max(byteCapacityNeeded, MIN_EXTERNAL_BYTES));
165 			static if (NUM_INLINE_ITEMS > 0) {
166 				ubyte[] oldBlock = cast(ubyte[])inlineItems[];
167 				newBlock[0..oldBlock.length] = oldBlock;
168 			}
169 			externalArray = cast(T*)newBlock.ptr;
170 			_capacity = cast(uint)(newBlock.length / T.sizeof);
171 			//writefln("  1 cap %s", _capacity);
172 			return;
173 		}
174 
175 		size_t byteCapacity = nextPOT(_capacity * T.sizeof);
176 		ubyte[] block = (cast(ubyte*)externalArray)[0..byteCapacity];
177 		resizeSmallArray(arena, block, byteCapacityNeeded);
178 		externalArray = cast(T*)block.ptr;
179 		_capacity = cast(uint)(block.length / T.sizeof);
180 		//writefln("  2 cap %s", _capacity);
181 	}
182 
183 	// Doubles the size of block
184 	private void resizeSmallArray(ref ArrayArena arena, ref ubyte[] oldBlock, size_t newLength) {
185 		assert(isPowerOfTwo(oldBlock.length));
186 		assert(oldBlock.length >= ArrayArena.MIN_BLOCK_BYTES);
187 		assert(newLength >= ArrayArena.MIN_BLOCK_BYTES, "too small");
188 
189 		ubyte[] newBlock = arena.allocBlock(newLength);
190 		newBlock[0..oldBlock.length] = oldBlock;
191 		arena.freeBlock(oldBlock);
192 		oldBlock = newBlock;
193 	}
194 
195 	inout(T)[] opSlice() inout
196 	{
197 		static if (NUM_INLINE_ITEMS > 0) {
198 			if (_capacity == NUM_INLINE_ITEMS) return inlineItems.ptr[0.._length];
199 		}
200 		return externalArray[0.._length];
201 	}
202 
203 	inout(T)[] opSlice(size_t from, size_t to) inout
204 	{
205 		return this[][from..to];
206 	}
207 
208 	void removeInPlace(size_t at)
209 	{
210 		if (at+1 != _length)
211 		{
212 			this[at] = this[_length-1];
213 		}
214 		--_length;
215 	}
216 
217 	void removeByShift(size_t at, size_t numToRemove = 1)
218 	{
219 		size_t to = at;
220 		size_t from = at + numToRemove;
221 		while(from < _length)
222 		{
223 			this[to] = this[from];
224 			++to;
225 			++from;
226 		}
227 		_length -= numToRemove;
228 	}
229 
230 	void toString(scope void delegate(const(char)[]) sink) const {
231 		import std.format : formattedWrite;
232 		sink("[");
233 		size_t i;
234 		foreach(const ref T item; opSlice()) {
235 			if (i > 0) sink(", ");
236 			sink.formattedWrite("%s", item);
237 			++i;
238 		}
239 		sink("]");
240 	}
241 }
242 
243 struct InvertedArray(T)
244 {
245 	import vox.utils : isPowerOfTwo, nextPOT, divCeil, min, max, writefln, ArrayArena, format;
246 	import std.range : retro;
247 	Array!(T) array;
248 
249 	bool empty() { return array._length == 0; }
250 	uint length() { return array._length; }
251 	uint opDollar() { return array._length; }
252 	uint capacity() { return array._capacity; }
253 	ref T front() { return array[$-1]; }
254 	ref T back() { return array[0]; }
255 	void clear() { array._length = 0; }
256 
257 	ref T opIndex(size_t index)
258 	{
259 		assert(index < array._capacity, format("opIndex(%s), capacity %s", index, array._capacity));
260 		static if (array.NUM_INLINE_ITEMS > 0) {
261 			if (array._capacity == array.NUM_INLINE_ITEMS) return array.inlineItems[array._length - index - 1];
262 		}
263 
264 		return array.externalArray[array._length - index - 1];
265 	}
266 
267 	InvertedArray!T dup(ref ArrayArena arena) {
268 		InvertedArray!T copy = this;
269 		copy.array = copy.array.dup(arena);
270 		return copy;
271 	}
272 
273 	void put(ref ArrayArena arena, T item) {
274 		array.putAt(arena, 0, item);
275 	}
276 
277 	// shifts items to the right
278 	void putAt(ref ArrayArena arena, size_t at, T[] items...) {
279 		array.replaceAt(arena, array._length - at, 0, items);
280 	}
281 
282 	void replaceAt(A)(ref ArrayArena arena, size_t at, size_t numItemsToRemove, A itemsToInsert) {
283 		assert(at + numItemsToRemove <= array._length);
284 
285 		size_t numItemsToInsert = itemsToInsert.length;
286 
287 		array.replaceAtVoid(arena, array._length-at-numItemsToRemove, numItemsToRemove, numItemsToInsert);
288 		foreach(i; 0..numItemsToInsert)
289 			this[at+numItemsToInsert-i-1] = itemsToInsert[i];
290 	}
291 
292 	void unput(size_t numItems) {
293 		array.removeByShift(0, numItems);
294 	}
295 
296 	void reserve(ref ArrayArena arena, uint howMany) {
297 		if (array._length + howMany > array._capacity) array.extend(arena, howMany);
298 	}
299 
300 	// returns memory to arena and zeroes the length
301 	void free(ref ArrayArena arena) {
302 		array.free(arena);
303 	}
304 
305 	auto opSlice() {
306 		static if (array.NUM_INLINE_ITEMS > 0) {
307 			if (array._capacity == array.NUM_INLINE_ITEMS) return array.inlineItems.ptr[0..array._length].retro;
308 		}
309 		return array.externalArray[0..array._length].retro;
310 	}
311 
312 	auto opSlice(size_t from, size_t to) {
313 		return this[][from..to];
314 	}
315 
316 	void removeByShift(size_t at, size_t numToRemove = 1) {
317 		array.removeByShift(array._length - at - numToRemove, numToRemove);
318 	}
319 
320 	void toString(scope void delegate(const(char)[]) sink) {
321 		import std.format : formattedWrite;
322 		sink("[");
323 		size_t i;
324 		foreach(ref T item; opSlice()) {
325 			if (i > 0) sink(", ");
326 			sink.formattedWrite("%s", item);
327 			++i;
328 		}
329 		sink("]");
330 	}
331 }
332