1 /**
2 License: public domain
3 Authors: Simon Bürger
4 */
5 
6 module jive.queue;
7 
8 import core.exception : RangeError;
9 import core.stdc.string : memmove, memcpy, memset;
10 import std.algorithm;
11 import std.conv : emplace;
12 import std.range;
13 import std.traits;
14 import jive.internal;
15 
16 
17 /**
18  *  Array structure that allows addition/deletion at both ends.
19  *
20  *  Intended to be used as a FIFO-queue or as a stack by combining
21  *  `pushFront`/`pushBack` and `popFront`/`popBack` appropriately. Implemented
22  *  as a circular buffer inside a continuous block of memory that is
23  *  automatically expanded as necessary, similar to jive.Array.
24  */
25 struct Queue(V)
26 {
27 	private V* _ptr = null;			// unused elements are undefined
28 	private size_t _capacity = 0;	// size of buf
29 	private size_t _length = 0;		// used size
30 	private size_t _left = 0;		// offset into buffer
31 
32 	/** constructor that gets content from arbitrary range */
33 	this(Stuff)(Stuff stuff)
34 		if(isInputRange!Stuff && is(ElementType!Stuff:V))
35 	{
36 		static if(hasLength!Stuff)
37 			reserve(_length + stuff.length);
38 
39 		foreach(ref x; stuff)
40 			pushBack(x);
41 	}
42 
43 	/** post-blit that does a full copy */
44 	this(this)
45 	{
46 		auto newPtr = jiveMalloc!V(_length);
47 
48 		static if(hasElaborateCopyConstructor!V)
49 		{
50 			for(size_t i = 0; i < _length; ++i)
51 				emplace(newPtr + i, this[i]);
52 		}
53 		else
54 		{
55 			if(_left + _length <=  _capacity)
56 			{
57 				memcpy(newPtr, _ptr + _left, V.sizeof * _length);
58 			}
59 			else
60 			{
61 				memcpy(newPtr, _ptr + _left, V.sizeof * (_capacity - _left));
62 				memcpy(newPtr + _capacity - _left, _ptr, V.sizeof * (_length - _capacity + _left));
63 			}
64 		}
65 		_ptr = newPtr;
66 		_capacity = _length;
67 		_left = 0;
68 	}
69 
70 	/** destructor */
71 	~this()
72 	{
73 		static if (hasElaborateDestructor!V)
74 			foreach (ref x; this[])
75 				destroy(x);
76 
77 		jiveFree(_ptr);
78 		_ptr = null; // probably not necessary, just a precaution
79 	}
80 
81 	/** check for emptiness */
82 	bool empty() const pure nothrow @property @safe
83 	{
84 		return _length == 0;
85 	}
86 
87 	/** number of elements */
88 	size_t length() const pure nothrow @property @safe
89 	{
90 		return _length;
91 	}
92 
93 	/** ditto */
94 	size_t opDollar() const pure nothrow @property @safe
95 	{
96 		return _length;
97 	}
98 
99 	/** number of elements this structure can hold without further allocations */
100 	size_t capacity() const pure nothrow @property @safe
101 	{
102 		return _capacity;
103 	}
104 
105 	/**
106 	 * Allocated heap memory in bytes.
107 	 * This is recursive if V has a `.memUsage` property. Otherwise it is equal
108 	 * to `V.sizeof * capacity`
109 	 */
110 	size_t memUsage() const pure nothrow @property @trusted
111 	{
112 		size_t r = V.sizeof*_capacity;
113 		static if(hasMember!(V, "memUsage"))
114 			for(size_t i = 0; i < _length; ++i)
115 				r += this[i].memUsage;
116 		return r;
117 	}
118 
119 	/** make sure this structure can contain given number of elements without further allocs */
120 	void reserve(size_t newCap, bool overEstimate = false) nothrow @trusted
121 	{
122 		if(newCap <= _capacity)
123 			return;
124 
125 		if(overEstimate)
126 			newCap = max(newCap, 2*_capacity);
127 
128 		auto newPtr = jiveMalloc!V(newCap);
129 		if(_left + _length <=  _capacity)
130 		{
131 			memcpy(newPtr, _ptr + _left, V.sizeof * _length);
132 		}
133 		else
134 		{
135 			memcpy(newPtr, _ptr + _left, V.sizeof * (_capacity - _left));
136 			memcpy(newPtr + _capacity - _left, _ptr, V.sizeof * (_length - _capacity + _left));
137 		}
138 
139 		static if(hasIndirections!V)
140 			memset(newPtr + _length, 0, V.sizeof * (newCap - _length)); // prevent false pointers
141 
142 		jiveFree(_ptr);
143 		_ptr = newPtr;
144 		_capacity = newCap;
145 		_left = 0;
146 	}
147 
148 	/** default range */
149 	auto opSlice() nothrow pure @trusted
150 	{
151 		if(_left + _length <= _capacity)
152 			return chain(
153 					_ptr[_left .. _left + _length],
154 					_ptr[0 .. 0]
155 					);
156 		else
157 			return chain(
158 					_ptr[_left .. _capacity],
159 					_ptr[0 .. _left + _length - _capacity]
160 					);
161 	}
162 
163 	/** ditto */
164 	auto opSlice() const nothrow pure @trusted
165 	{
166 		if(_left + _length <= _capacity)
167 			return chain(
168 					_ptr[_left .. _left + _length],
169 					_ptr[0 .. 0]
170 					);
171 		else
172 			return chain(
173 					_ptr[_left .. _capacity],
174 					_ptr[0 .. _left + _length - _capacity]
175 					);
176 	}
177 
178 	/** ditto */
179 	auto opSlice() immutable nothrow pure @trusted
180 	{
181 		if(_left + _length <= _capacity)
182 			return chain(
183 					_ptr[_left .. _left + _length],
184 					_ptr[0 .. 0]
185 					);
186 		else
187 			return chain(
188 					_ptr[_left .. _capacity],
189 					_ptr[0 .. _left + _length - _capacity]
190 					);
191 	}
192 
193 	/** indexing */
194 	ref inout(V) opIndex(string file = __FILE__, int line = __LINE__)(size_t i) inout pure nothrow @trusted
195 	{
196 		if(boundsChecks && i >= _length)
197 			assert(false, boundsCheckMsg!(file, line));
198 		return _ptr[(_left + i) % _capacity];
199 	}
200 
201 	/** first element, same as this[0] */
202 	ref inout(V) front(string file = __FILE__, int line = __LINE__)() inout pure nothrow @property
203 	{
204 		if(boundsChecks && empty)
205 			assert(false, boundsCheckMsg!(file, line));
206 		return _ptr[_left];
207 	}
208 
209 	/** last element, same as this[$-1] */
210 	ref inout(V) back(string file = __FILE__, int line = __LINE__)() inout pure nothrow @property
211 	{
212 		if(boundsChecks && empty)
213 			assert(false, boundsCheckMsg!(file, line));
214 		return _ptr[(_left + _length - 1) % _capacity];
215 	}
216 
217 	/** add an element to the front of the queue */
218 	void pushFront(V val) @trusted
219 	{
220 		reserve(_length + 1, true);
221 		++_length;
222 		if(_left == 0)
223 			_left = _capacity-1;
224 		else
225 			--_left;
226 		moveEmplace(val, front);
227 	}
228 
229 	/** add multiple elements to the front of the queue */
230 	void pushFront(Stuff)(Stuff stuff)
231 		if(isInputRange!Stuff && is(ElementType!Stuff:V))
232 	{
233 		static if(hasLength!Stuff)
234 			reserve(_length + stuff.length, true);
235 
236 		foreach(ref x; stuff)
237 			pushFront(x);
238 	}
239 
240 	/** add an element to the back of the queue */
241 	void pushBack(V val) @trusted
242 	{
243 		reserve(_length + 1, true);
244 		++_length;
245 		moveEmplace(val, back);
246 	}
247 
248 	/** add multiple elements to the back of the queue */
249 	void pushBack(Stuff)(Stuff stuff)
250 		if(!is(Stuff:V) && isInputRange!Stuff && is(ElementType!Stuff:V))
251 	{
252 		static if(hasLength!Stuff)
253 			reserve(_length + stuff.length, true);
254 
255 		foreach(ref x; stuff)
256 			pushBack(x);
257 	}
258 
259 	/** removes first element of the queue and returns it */
260 	V popFront(string file = __FILE__, int line = __LINE__)() @trusted
261 	{
262 		if(boundsChecks && empty)
263 			assert(false, boundsCheckMsg!(file, line));
264 
265 		V r = void;
266 		memcpy(&r, &front(), V.sizeof);
267 		static if(hasIndirections!V)
268 			memset(&front(), 0, V.sizeof);
269 		_left = (_left + 1) % _capacity;
270 		--_length;
271 		return r;
272 	}
273 
274 	/** removes last element of the queue and returns it */
275 	V popBack(string file = __FILE__, int line = __LINE__)() @trusted
276 	{
277 		if(boundsChecks && empty)
278 			assert(false, boundsCheckMsg!(file, line));
279 
280 		V r = void;
281 		memcpy(&r, &back(), V.sizeof);
282 		static if(hasIndirections!V)
283 			memset(&back(), 0, V.sizeof);
284 		--_length;
285 		return r;
286 	}
287 
288 	/** remove all content but keep allocated memory */
289 	void clear() @trusted
290 	{
291 		// TODO: remove @trusted in case V's destructor is @system
292 		static if(hasElaborateDestructor!V)
293 			for(size_t i = 0; i < _length; ++i)
294 				destroy(this[i]);
295 		static if(hasIndirections!V)
296 			memset(_ptr, 0, V.sizeof * _capacity);
297 		_length = 0;
298 	}
299 }
300 
301 ///
302 /*@nogc*/ nothrow pure @safe unittest
303 {
304 	Queue!int a;
305 	a.pushBack([1,2,3]);
306 	a.pushFront([4,5,6]);
307 
308 	assert(equal(a[], [6,5,4,1,2,3]));
309 
310 	assert(a.popFront == 6);
311 	assert(a.popBack == 3);
312 
313 	assert(a.length == 4);
314 }
315 
316 // check actual 'circular' buffer
317 @nogc nothrow pure @safe unittest
318 {
319 	Queue!int q;
320 	q.reserve(3);
321 	assert(q.capacity == 3);
322 	assert(q.empty);
323 
324 	// forward
325 	q.pushBack(1);
326 	q.pushBack(2);
327 	q.pushBack(3);
328 	assert(q.popFront == 1); q.pushBack(4);
329 	assert(q.popFront == 2); q.pushBack(5);
330 	assert(q.popFront == 3); q.pushBack(6);
331 	assert(q.popFront == 4); q.pushBack(7);
332 	assert(q[0] == 5);
333 	assert(q[1] == 6);
334 	assert(q[2] == 7);
335 
336 	q.clear();
337 	assert(q.empty);
338 	assert(q.capacity == 3);
339 
340 	// backward
341 	q.pushFront(1);
342 	q.pushFront(2);
343 	q.pushFront(3);
344 	assert(q.popBack == 1); q.pushFront(4);
345 	assert(q.popBack == 2); q.pushFront(5);
346 	assert(q.popBack == 3); q.pushFront(6);
347 	assert(q.popBack == 4); q.pushFront(7);
348 	assert(q[$-1] == 5);
349 	assert(q[$-2] == 6);
350 	assert(q[$-3] == 7);
351 }
352 
353 // check correct invocation of postblit/destructor
354 unittest
355 {
356 	int counter = 0;
357 
358 	struct S
359 	{
360 		bool active;
361 		this(bool active) { this.active = active; if(active) ++counter; }
362 		this(this) { if(active) ++counter; }
363 		~this() { if(active) --counter; }
364 	}
365 
366 	{
367 		Queue!S a;
368 		assert(counter == 0);
369 		a.pushBack(S(true));
370 		assert(counter == 1);
371 		a.pushFront(a[0]);
372 		assert(counter == 2);
373 		a.reserve(5);
374 		a.pushBack(a[]);
375 
376 		Queue!S b = a;
377 		assert(equal(a[], b[]));
378 		assert(counter == 8);
379 	}
380 	assert(counter == 0);
381 }
382 
383 // check move-semantics
384 unittest
385 {
386 	struct S3
387 	{
388 		int x;
389 		alias x this;
390 		this(this) { assert(x == 0); }
391 	}
392 
393 	Queue!S3 a;
394 	a.pushBack(S3(1));
395 	a.pushBack(S3(2));
396 	a.pushFront(S3(3));
397 	a.reserve(5);
398 	a.popBack();
399 	a.popFront();
400 	a[0] = S3(4);
401 	a.clear();
402 }
403 
404 // type with no @safe/pure/etc-attributes
405 unittest
406 {
407 	struct S
408 	{
409 		int* x;
410 		this(this){ }
411 		~this(){ }
412 	}
413 
414 	static assert(hasIndirections!S);
415 	static assert(hasElaborateDestructor!S);
416 
417 	S s;
418 	Queue!S a;
419 	a.pushBack(s);
420 	a.pushBack([s,s]);
421 	a.pushFront(s);
422 	a.pushFront([s,s]);
423 	a.popBack();
424 	a.popFront();
425 	Queue!S b = a;
426 	a.clear();
427 	assert(equal(b[], [s,s,s,s]));
428 }