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 }