1 /**
2 	Utility functions for circular array processing
3 	Copyright: © 2012 RejectedSoftware e.K., © 2014-2015 Etienne Cimon
4 	License: Subject to the terms of the MIT license, as written in the included LICENSE file.
5 	Authors: Sönke Ludwig, Etienne Cimon
6 */
7 module memutils.circularbuffer;
8 
9 import memutils.allocators;
10 import memutils.constants;
11 import std.algorithm;
12 import std.traits : hasElaborateDestructor, isBasicType;
13 import memutils.utils;
14 
15 struct CircularBuffer(T, size_t N = 0, ALLOC = ThreadMem) {
16 	@disable this(this);
17 
18 	private {
19 		static if( N > 0 ) T[N] m_buffer;
20 		else T[] m_buffer;
21 		size_t m_start = 0;
22 		size_t m_fill = 0;
23 	}
24 	static if( N == 0 ){
25 		this(size_t capacity) { m_buffer = allocArray!(T, ALLOC)(capacity); }
26 		~this() { if (m_buffer) freeArray!(T, ALLOC)(m_buffer); }
27 	} 
28 	else {
29 		// clear ring buffer static fields upon removal (to run struct destructors, if T is a struct)
30 		~this() 
31 		{ 
32 			// TODO: Test this
33 			// destroy(m_buffer[m_start .. m_fill]); 
34 		}
35 	}
36 	@property bool empty() const { return m_fill == 0; }
37 	@property bool full() const { return m_fill == m_buffer.length; }
38 	@property size_t length() const { return m_fill; }
39 	@property size_t freeSpace() const { return m_buffer.length - m_fill; }
40 	@property size_t capacity() const { return m_buffer.length; }
41 	static if( N == 0 ){
42 		@property void capacity(size_t new_size)
43 		{
44 			if( m_buffer.length ){
45 				auto temp = allocArray!(T, ALLOC)(new_size);
46 				size_t tmp_fill = m_fill;
47 				read(temp[0 .. m_fill]);
48 				m_start = 0;
49 				m_fill = tmp_fill;
50 				freeArray(m_buffer);
51 				m_buffer = temp;
52 			} else m_buffer = allocArray!(T, ALLOC)(new_size);
53 		}
54 	}
55 	@property ref inout(T) front() inout { assert(!empty); return m_buffer[m_start]; }
56 	@property ref inout(T) back() inout { assert(!empty); return m_buffer[mod(m_start+m_fill-1)]; }
57 	void clear()
58 	{
59 		popFrontN(length);
60 		assert(m_fill == 0);
61 		m_start = 0;
62 	}
63 	void put()(T itm) { assert(m_fill < m_buffer.length); m_buffer[mod(m_start + m_fill++)] = itm; }
64 	void put(TC : T)(TC[] itms)
65 	{
66 		if( !itms.length ) return;
67 		assert(m_fill+itms.length <= m_buffer.length, "Cannot write to buffer, it is full.");
68 		if( mod(m_start+m_fill) >= mod(m_start+m_fill+itms.length) ){
69 			size_t chunk1 = m_buffer.length - (m_start+m_fill);
70 			size_t chunk2 = itms.length - chunk1;
71 			m_buffer[m_start+m_fill .. m_buffer.length] = itms[0 .. chunk1];
72 			m_buffer[0 .. chunk2] = itms[chunk1 .. $];
73 		} else {
74 			m_buffer[mod(m_start+m_fill) .. mod(m_start+m_fill)+itms.length] = itms[];
75 		}
76 		m_fill += itms.length;
77 	}
78 	void putN(size_t n) { assert(m_fill+n <= m_buffer.length); m_fill += n; }
79 	void popFront() { assert(!empty); m_start = mod(m_start+1); m_fill--; }
80 	void popFrontN(size_t n) { 
81 		import std.c.string : memset; 
82 		assert(length >= n); 
83 		m_start = mod(m_start + n);
84 		m_fill -= n;
85 	}
86 	void popBack() { assert(!empty); m_fill--; }
87 	void popBackN(size_t n) { assert(length >= n); m_fill -= n; }
88 
89 	// moves all the values from the buffer one step down at start of the reference range
90 	void removeAt(Range r)
91 	{
92 		assert(r.m_buffer is m_buffer);
93 		if( m_start + m_fill > m_buffer.length ){
94 			assert(r.m_start >= m_start && r.m_start < m_buffer.length || r.m_start < mod(m_start+m_fill));
95 			if( r.m_start > m_start ){
96 				foreach(i; r.m_start .. m_buffer.length-1)
97 					m_buffer[i] = m_buffer[i+1];
98 				m_buffer[$-1] = m_buffer[0];
99 				foreach(i; 0 .. mod(m_start + m_fill - 1))
100 					m_buffer[i] = m_buffer[i+1];
101 			} else {
102 				foreach(i; r.m_start .. mod(m_start + m_fill - 1))
103 					m_buffer[i] = m_buffer[i+1];
104 			}
105 		} else {
106 			assert(r.m_start >= m_start && r.m_start < m_start+m_fill);
107 			foreach(i; r.m_start .. m_start+m_fill-1)
108 				m_buffer[i] = m_buffer[i+1];
109 		}
110 		m_fill--;
111 		static if (hasElaborateDestructor!T) { // calls destructors
112 			static if (is(T == struct) && isPointer!T) .destroy(*m_buffer[mod(m_start+m_fill)]);
113 			else .destroy(m_buffer[mod(m_start+m_fill)]);
114 		}
115 	}
116 	inout(T)[] peek() inout { return m_buffer[m_start .. min(m_start+m_fill, m_buffer.length)]; }
117 	T[] peekDst() {
118 		if( m_start + m_fill < m_buffer.length ) return m_buffer[m_start+m_fill .. $];
119 		else return m_buffer[mod(m_start+m_fill) .. m_start];
120 	}
121 	void read(T[] dst)
122 	{
123 		import std.c.string : memset;
124 		assert(dst.length <= length);
125 		if( !dst.length ) return;
126 		if( mod(m_start) >= mod(m_start+dst.length) ){
127 			size_t chunk1 = m_buffer.length - m_start;
128 			size_t chunk2 = dst.length - chunk1;
129 			dst[0 .. chunk1] = m_buffer[m_start .. $];
130 			dst[chunk1 .. $] = m_buffer[0 .. chunk2];
131 			//static if (is(ALLOC == SecureMem)) 
132 			//{
133 			//	memset(m_buffer.ptr + m_start, 0, chunk1);
134 			//	memset(m_buffer.ptr, 0, chunk2);
135 			//}
136 		} else {
137 			dst[] = m_buffer[m_start .. m_start+dst.length];
138 			//static if (is(ALLOC == SecureMem)) 
139 			//	memset(m_buffer.ptr + m_start, 0, dst.length);
140 		}
141 		popFrontN(dst.length);
142 	}
143 	int opApply(scope int delegate(ref T itm) del)
144 	{
145 		if( m_start+m_fill > m_buffer.length ){
146 			foreach(i; m_start .. m_buffer.length)
147 				if( auto ret = del(m_buffer[i]) )
148 					return ret;
149 			foreach(i; 0 .. mod(m_start+m_fill))
150 				if( auto ret = del(m_buffer[i]) )
151 					return ret;
152 		} else {
153 			foreach(i; m_start .. m_start+m_fill)
154 				if( auto ret = del(m_buffer[i]) )
155 					return ret;
156 		}
157 		return 0;
158 	}
159 	ref inout(T) opIndex(size_t idx) inout { assert(idx < length); return m_buffer[mod(m_start+idx)]; }
160 	Range opSlice() { return Range(m_buffer, m_start, m_fill); }
161 	Range opSlice(size_t from, size_t to)
162 	{
163 		assert(from <= to);
164 		assert(to <= m_fill);
165 		return Range(m_buffer, mod(m_start+from), to-from);
166 	}
167 	size_t opDollar(size_t dim)() const if(dim == 0) { return length; }
168 	private size_t mod(size_t n)
169 	const {
170 		static if( N == 0 ){
171 			/*static if(PotOnly){
172             return x & (m_buffer.length-1);
173             } else {*/
174 			return n % m_buffer.length;
175 			//}
176 		} else static if( ((N - 1) & N) == 0 ){
177 			return n & (N - 1);
178 		} else return n % N;
179 	}
180 	static struct Range {
181 		private {
182 			T[] m_buffer;
183 			size_t m_start;
184 			size_t m_length;
185 		}
186 		private this(T[] buffer, size_t start, size_t length)
187 		{
188 			m_buffer = buffer;
189 			m_start = start;
190 			m_length = length;
191 		}
192 		@property bool empty() const { return m_length == 0; }
193 		@property inout(T) front() inout { assert(!empty); return m_buffer[m_start]; }
194 		void popFront()
195 		{
196 			assert(!empty);
197 			m_start++;
198 			m_length--;
199 			if( m_start >= m_buffer.length )
200 				m_start = 0;
201 		}
202 	}
203 }
204 
205 unittest {
206 	import std.range : isInputRange, isOutputRange;
207 	static assert(isInputRange!(CircularBuffer!int) && isOutputRange!(CircularBuffer!int, int));
208 
209 	// test static buffer
210 	CircularBuffer!(int, 5) buf;
211 	assert(buf.length == 0 && buf.freeSpace == 5); buf.put(1); // |1 . . . .
212 	assert(buf.length == 1 && buf.freeSpace == 4); buf.put(2); // |1 2 . . .
213 	assert(buf.length == 2 && buf.freeSpace == 3); buf.put(3); // |1 2 3 . .
214 	assert(buf.length == 3 && buf.freeSpace == 2); buf.put(4); // |1 2 3 4 .
215 	assert(buf.length == 4 && buf.freeSpace == 1); buf.put(5); // |1 2 3 4 5
216 	assert(buf.length == 5 && buf.freeSpace == 0);
217 	assert(buf.front == 1);
218 	buf.popFront(); // .|2 3 4 5
219 	assert(buf.front == 2);
220 	buf.popFrontN(2); // . . .|4 5
221 	assert(buf.front == 4);
222 	assert(buf.length == 2 && buf.freeSpace == 3);
223 	buf.put([6, 7, 8]); // 6 7 8|4 5
224 	assert(buf.length == 5 && buf.freeSpace == 0);
225 	int[5] dst;
226 	buf.read(dst); // . . .|. .
227 	assert(dst == [4, 5, 6, 7, 8]);
228 	assert(buf.length == 0 && buf.freeSpace == 5);
229 	buf.put([1, 2]); // . . .|1 2
230 	assert(buf.length == 2 && buf.freeSpace == 3);
231 	buf.read(dst[0 .. 2]); //|. . . . .
232 	assert(dst[0 .. 2] == [1, 2]);
233 }