1 /**
2 * a simple template ringbuffer, compatible with @safe, @nogc, pure and nothrow code.
3 * Authors: Susan
4 * Date: 2022-02-03
5 * Licence: AGPL-3.0 or later
6 * Copyright: Susan, 2022
7 *
8 * special thanks to my good friend flussence, whose advice while making this module was invaluable. <3
9 */
10 
11 module ringbuffer;
12 
13 ///
14 struct RingBuffer(DataType, size_t maxLength)
15 {
16 	private size_t readIndex; //todo: size_t is overkill in 99.999% of cases
17 	private size_t writeIndex;
18 	private DataType[maxLength] data;
19 
20 	private auto _length() const @nogc nothrow pure @safe
21 	{
22 		//todo: there is almost certainly a better way to go about this
23 		if (writeIndex == readIndex)
24 		{
25 			return 0;
26 		}
27 
28 		immutable write = sanitize(writeIndex);
29 		immutable read = sanitize(readIndex);
30 
31 		if (write == read)
32 		{
33 			return maxLength;
34 		}
35 
36 		if (write > read)
37 		{
38 			return write - read;
39 		}
40 		else
41 		{
42 			return (write + maxLength) - read;
43 		}
44 	}
45 
46 	invariant(_length <= maxLength);
47 
48 	private auto next(in size_t rhs) const @nogc nothrow pure @safe
49 	{
50 		return rhs % (maxLength * 2);
51 	}
52 
53 	private auto sanitize(in size_t rhs) const @nogc nothrow pure @safe
54 	{
55 		return rhs % maxLength;
56 	}
57 
58 	///
59 	@property auto length() const @nogc nothrow pure @safe
60 	{
61 		return _length;
62 	}
63 
64 	///
65 	@property auto capacity() const @nogc nothrow pure @safe
66 	{
67 		return maxLength - length;
68 	}
69 
70 	/// assignment
71 	void opAssign(R)(R rhs)
72 	in (rhs.length <= maxLength)
73 	{
74 		data[0 .. rhs.length] = rhs;
75 		readIndex = 0;
76 		writeIndex = rhs.length;
77 	}
78 
79 	/// push to buffer (lifo)
80 	void push(DataType rhs)
81 	{
82 		data[sanitize(writeIndex)] = rhs;
83 		writeIndex = next(writeIndex + 1);
84 	}
85 
86 	/// ditto
87 	void push(R)(R rhs)
88 	{
89 		foreach(DataType d; rhs)
90 		{
91 			push(d);
92 		}
93 	}
94 
95 	/// ditto
96 	void opOpAssign(string op)(DataType rhs)
97 		if (op == "~")
98 	in (length + 1 <= maxLength)
99 	{
100 		push(rhs);
101 	}
102 
103 	/// ditto
104 	void opOpAssign(string op, R)(R rhs)
105 		if (op == "~")
106 	in (length + rhs.length <= maxLength)
107 	{
108 		push(rhs);
109 	}
110 
111 	/// push to buffer (fifo)
112 	void unshift(DataType rhs)
113 	{
114 		data[sanitize(readIndex - 1)] = rhs;
115 		readIndex = next(readIndex - 1);
116 	}
117 
118 	/// ditto
119 	void unshift(R)(R rhs)
120 	{
121 		foreach(DataType d; rhs)
122 		{
123 			unshift(d);
124 		}
125 	}
126 
127 	/// retrieve item from buffer (fifo)
128 	DataType shift()
129 	in (length > 0)
130 	{
131 		auto result = data[sanitize(readIndex)];
132 		readIndex = next(readIndex + 1);
133 		return result;
134 	}
135 
136 	/// retrieve item from buffer (lifo)
137 	DataType pop()
138 	in (length > 0)
139 	{
140 		writeIndex = next(writeIndex - 1);
141 		return data[sanitize(writeIndex)];
142 	}
143 
144 	/// empty the buffer
145 	void clear()
146 	{
147 		data[] = DataType.init;
148 		writeIndex = readIndex;
149 	}
150 
151 	/// range interface
152 	auto opIndex()
153 	{
154 		return RingBufferRangeInterface!(DataType, true)(data[], readIndex, length);
155 	}
156 
157 	/// ditto
158 	auto opIndex() const
159 	{
160 		return RingBufferRangeInterface!(DataType, false)(data[], readIndex, length);
161 	}
162 
163 	///
164 	auto opIndex(in size_t index)
165 	in(index < length)
166 	{
167 		return data[sanitize(readIndex + index)];
168 	}
169 }
170 
171 /// example
172 @nogc nothrow pure @safe unittest
173 {
174 	RingBuffer!(int, 5) buff;
175 	buff.push(69);
176 	buff ~= 420; // equivalent to the push syntax
177 	assert(buff.shift == 69);
178 	assert(buff.shift == 420);
179 
180 	import std.array : staticArray;
181 	import std.range : iota;
182 	immutable int[5] temp = staticArray!(iota(5));
183 
184 	buff.push(temp); // multiple items may be pushed in a single call
185 
186 	assert(buff.length == 5);
187 	assert(buff.capacity == 0);
188 
189 	assert(buff.pop == 4);
190 
191 	assert(buff.length == 4);
192 	assert(buff.capacity == 1);
193 
194 	buff.unshift(666);
195 	assert(buff.shift == 666);
196 
197 	buff.clear();
198 
199 	assert(buff.length == 0);
200 	assert(buff.capacity == 5);
201 }
202 
203 private struct RingBufferRangeInterface(DataType, bool isSourceMutable)
204 {
205 	static if(isSourceMutable)
206 	{
207 		private DataType[] source;
208 	}
209 	else
210 	{
211 		private const(DataType[]) source;
212 	}
213 
214 	private size_t startIndex;
215 	private size_t length;
216 
217 	@disable this();
218 
219 	package this(R)(R source, in size_t startIndex, in size_t length)
220 	{
221 		this.source = source;
222 		this.startIndex = startIndex;
223 		this.length = length;
224 	}
225 
226 	bool empty() const
227 	{
228 		return length == 0;
229 	}
230 
231 	auto front()
232 	{
233 		return source[startIndex % source.length];
234 	}
235 
236 	void popFront()
237 	{
238 		++startIndex;
239 		--length;
240 	}
241 
242 	auto back()
243 	{
244 		return source[(startIndex + length) % source.length];
245 	}
246 
247 	void popBack()
248 	{
249 		--length;
250 	}
251 }
252 
253 @nogc nothrow pure @safe unittest
254 {
255 	RingBuffer!(int, 8) foo;
256 	assert(foo.length == 0);
257 	assert(foo.capacity == 8);
258 
259 	foo.push(0); //0
260 	assert(foo.length == 1);
261 	assert(foo.capacity == 7);
262 	assert(foo[0] == 0);
263 
264 	import std.array : staticArray;
265 	import std.range : iota;
266 	immutable int[5] temp = staticArray!(iota(5));
267 
268 	foo ~= temp[1 .. 3]; //0, 1, 2
269 	foo.push(temp[3 .. 5]); //0, 1, 2, 3, 4
270 	assert(foo.length == 5);
271 	assert(foo.capacity == 3);
272 
273 	assert(foo.shift == 0); //1, 2, 3, 4
274 	assert(foo.pop == 4); //1, 2, 3
275 
276 	assert(foo.length == 3);
277 	assert(foo.capacity == 5);
278 
279 	foo.push(temp); //1, 2, 3, 0, 1, 2, 3, 4
280 	assert(foo.length == 8); //not empty. a nasty bug.
281 	assert(foo.capacity == 0);
282 	assert(foo[6] == 3);
283 	assert(foo.shift == 1);
284 	assert(foo[6] == 4);
285 
286 	foo.clear();
287 	assert(foo.length == 0);
288 	assert(foo.capacity == 8);
289 
290 	foo ~= temp;
291 	assert(foo.length == temp.length);
292 	assert(foo.shift == 0);
293 	assert(foo.pop == 4);
294 	assert(foo.length == 3);
295 	assert(foo.capacity == 5);
296 
297 	foo = temp;
298 	assert(foo.shift == 0);
299 	assert(foo.pop == 4);
300 	assert(foo.length == 3);
301 	assert(foo.capacity == 5);
302 
303 	foreach(i; 0 .. 100) //one last stomp
304 	{
305 		foo.push(i);
306 		foo.shift;
307 	}
308 
309 	immutable bar = foo;
310 
311 	// range test
312 	int i;
313 	foreach(val; bar)
314 	{
315 		switch(i)
316 		{
317 			case 0: assert(val == 97); break;
318 			case 1: assert(val == 98); break;
319 			case 2: assert(val == 99); break;
320 			default: assert(false);
321 		}
322 
323 		++i;
324 	}
325 
326 	foo.clear;
327 	foreach_reverse(i2; bar) //test unshift
328 	{
329 		foo.unshift(i2);
330 		assert(foo.pop == i2);
331 	}
332 }
333 
334 nothrow pure @safe unittest
335 {
336 	class C
337 	{
338 		int field;
339 	}
340 
341 	RingBuffer!(C, 6) foo;
342 	assert(foo.length == 0);
343 	assert(foo.capacity == 6);
344 
345 	C c = new C;
346 
347 	foo.push(c);
348 	assert(foo[0] is c);
349 	assert(foo.shift is c);
350 	foo.push(c);
351 	assert(foo.pop is c);
352 
353 	foo.clear;
354 
355 	foreach(i; 0 .. foo.capacity)
356 	{
357 		auto temp = new C;
358 		temp.field = cast(int)i;
359 		foo.push(temp);
360 	}
361 
362 	RingBuffer!(C, 8) bar;
363 	foreach_reverse(c2; foo)
364 	{
365 		auto temp = new C;
366 		temp.field = c2.field;
367 		bar.unshift(temp);
368 	}
369 
370 	import std.range: enumerate;
371 	foreach(i, temp; foo[].enumerate(0))
372 	{
373 		assert(foo.shift.field == i);
374 	}
375 
376 	RingBuffer!(C, 16) baz; //todo: put size as an enum into ringbuffer
377 
378 	baz ~= foo;
379 	baz.unshift(bar);
380 }