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