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 }