1 /** 2 License: public domain 3 Authors: Simon Bürger 4 */ 5 6 module jive.queue; 7 8 import core.exception : RangeError; 9 import core.stdc.string : memmove, memcpy, memset; 10 import std.algorithm; 11 import std.conv : emplace; 12 import std.range; 13 import std.traits; 14 import jive.internal; 15 16 17 /** 18 * Array structure that allows addition/deletion at both ends. 19 * 20 * Intended to be used as a FIFO-queue or as a stack by combining 21 * `pushFront`/`pushBack` and `popFront`/`popBack` appropriately. Implemented 22 * as a circular buffer inside a continuous block of memory that is 23 * automatically expanded as necessary, similar to jive.Array. 24 */ 25 struct Queue(V) 26 { 27 private V* _ptr = null; // unused elements are undefined 28 private size_t _capacity = 0; // size of buf 29 private size_t _length = 0; // used size 30 private size_t _left = 0; // offset into buffer 31 32 /** constructor that gets content from arbitrary range */ 33 this(Stuff)(Stuff stuff) 34 if(isInputRange!Stuff && is(ElementType!Stuff:V)) 35 { 36 static if(hasLength!Stuff) 37 reserve(_length + stuff.length); 38 39 foreach(ref x; stuff) 40 pushBack(x); 41 } 42 43 /** post-blit that does a full copy */ 44 this(this) 45 { 46 auto newPtr = jiveMalloc!V(_length); 47 48 static if(hasElaborateCopyConstructor!V) 49 { 50 for(size_t i = 0; i < _length; ++i) 51 emplace(newPtr + i, this[i]); 52 } 53 else 54 { 55 if(_left + _length <= _capacity) 56 { 57 memcpy(newPtr, _ptr + _left, V.sizeof * _length); 58 } 59 else 60 { 61 memcpy(newPtr, _ptr + _left, V.sizeof * (_capacity - _left)); 62 memcpy(newPtr + _capacity - _left, _ptr, V.sizeof * (_length - _capacity + _left)); 63 } 64 } 65 _ptr = newPtr; 66 _capacity = _length; 67 _left = 0; 68 } 69 70 /** destructor */ 71 ~this() 72 { 73 static if (hasElaborateDestructor!V) 74 foreach (ref x; this[]) 75 destroy(x); 76 77 jiveFree(_ptr); 78 _ptr = null; // probably not necessary, just a precaution 79 } 80 81 /** check for emptiness */ 82 bool empty() const pure nothrow @property @safe 83 { 84 return _length == 0; 85 } 86 87 /** number of elements */ 88 size_t length() const pure nothrow @property @safe 89 { 90 return _length; 91 } 92 93 /** ditto */ 94 size_t opDollar() const pure nothrow @property @safe 95 { 96 return _length; 97 } 98 99 /** number of elements this structure can hold without further allocations */ 100 size_t capacity() const pure nothrow @property @safe 101 { 102 return _capacity; 103 } 104 105 /** 106 * Allocated heap memory in bytes. 107 * This is recursive if V has a `.memUsage` property. Otherwise it is equal 108 * to `V.sizeof * capacity` 109 */ 110 size_t memUsage() const pure nothrow @property @trusted 111 { 112 size_t r = V.sizeof*_capacity; 113 static if(hasMember!(V, "memUsage")) 114 for(size_t i = 0; i < _length; ++i) 115 r += this[i].memUsage; 116 return r; 117 } 118 119 /** make sure this structure can contain given number of elements without further allocs */ 120 void reserve(size_t newCap, bool overEstimate = false) nothrow @trusted 121 { 122 if(newCap <= _capacity) 123 return; 124 125 if(overEstimate) 126 newCap = max(newCap, 2*_capacity); 127 128 auto newPtr = jiveMalloc!V(newCap); 129 if(_left + _length <= _capacity) 130 { 131 memcpy(newPtr, _ptr + _left, V.sizeof * _length); 132 } 133 else 134 { 135 memcpy(newPtr, _ptr + _left, V.sizeof * (_capacity - _left)); 136 memcpy(newPtr + _capacity - _left, _ptr, V.sizeof * (_length - _capacity + _left)); 137 } 138 139 static if(hasIndirections!V) 140 memset(newPtr + _length, 0, V.sizeof * (newCap - _length)); // prevent false pointers 141 142 jiveFree(_ptr); 143 _ptr = newPtr; 144 _capacity = newCap; 145 _left = 0; 146 } 147 148 /** default range */ 149 auto opSlice() nothrow pure @trusted 150 { 151 if(_left + _length <= _capacity) 152 return chain( 153 _ptr[_left .. _left + _length], 154 _ptr[0 .. 0] 155 ); 156 else 157 return chain( 158 _ptr[_left .. _capacity], 159 _ptr[0 .. _left + _length - _capacity] 160 ); 161 } 162 163 /** ditto */ 164 auto opSlice() const nothrow pure @trusted 165 { 166 if(_left + _length <= _capacity) 167 return chain( 168 _ptr[_left .. _left + _length], 169 _ptr[0 .. 0] 170 ); 171 else 172 return chain( 173 _ptr[_left .. _capacity], 174 _ptr[0 .. _left + _length - _capacity] 175 ); 176 } 177 178 /** ditto */ 179 auto opSlice() immutable nothrow pure @trusted 180 { 181 if(_left + _length <= _capacity) 182 return chain( 183 _ptr[_left .. _left + _length], 184 _ptr[0 .. 0] 185 ); 186 else 187 return chain( 188 _ptr[_left .. _capacity], 189 _ptr[0 .. _left + _length - _capacity] 190 ); 191 } 192 193 /** indexing */ 194 ref inout(V) opIndex(string file = __FILE__, int line = __LINE__)(size_t i) inout pure nothrow @trusted 195 { 196 if(boundsChecks && i >= _length) 197 assert(false, boundsCheckMsg!(file, line)); 198 return _ptr[(_left + i) % _capacity]; 199 } 200 201 /** first element, same as this[0] */ 202 ref inout(V) front(string file = __FILE__, int line = __LINE__)() inout pure nothrow @property 203 { 204 if(boundsChecks && empty) 205 assert(false, boundsCheckMsg!(file, line)); 206 return _ptr[_left]; 207 } 208 209 /** last element, same as this[$-1] */ 210 ref inout(V) back(string file = __FILE__, int line = __LINE__)() inout pure nothrow @property 211 { 212 if(boundsChecks && empty) 213 assert(false, boundsCheckMsg!(file, line)); 214 return _ptr[(_left + _length - 1) % _capacity]; 215 } 216 217 /** add an element to the front of the queue */ 218 void pushFront(V val) @trusted 219 { 220 reserve(_length + 1, true); 221 ++_length; 222 if(_left == 0) 223 _left = _capacity-1; 224 else 225 --_left; 226 moveEmplace(val, front); 227 } 228 229 /** add multiple elements to the front of the queue */ 230 void pushFront(Stuff)(Stuff stuff) 231 if(isInputRange!Stuff && is(ElementType!Stuff:V)) 232 { 233 static if(hasLength!Stuff) 234 reserve(_length + stuff.length, true); 235 236 foreach(ref x; stuff) 237 pushFront(x); 238 } 239 240 /** add an element to the back of the queue */ 241 void pushBack(V val) @trusted 242 { 243 reserve(_length + 1, true); 244 ++_length; 245 moveEmplace(val, back); 246 } 247 248 /** add multiple elements to the back of the queue */ 249 void pushBack(Stuff)(Stuff stuff) 250 if(!is(Stuff:V) && isInputRange!Stuff && is(ElementType!Stuff:V)) 251 { 252 static if(hasLength!Stuff) 253 reserve(_length + stuff.length, true); 254 255 foreach(ref x; stuff) 256 pushBack(x); 257 } 258 259 /** removes first element of the queue and returns it */ 260 V popFront(string file = __FILE__, int line = __LINE__)() @trusted 261 { 262 if(boundsChecks && empty) 263 assert(false, boundsCheckMsg!(file, line)); 264 265 V r = void; 266 memcpy(&r, &front(), V.sizeof); 267 static if(hasIndirections!V) 268 memset(&front(), 0, V.sizeof); 269 _left = (_left + 1) % _capacity; 270 --_length; 271 return r; 272 } 273 274 /** removes last element of the queue and returns it */ 275 V popBack(string file = __FILE__, int line = __LINE__)() @trusted 276 { 277 if(boundsChecks && empty) 278 assert(false, boundsCheckMsg!(file, line)); 279 280 V r = void; 281 memcpy(&r, &back(), V.sizeof); 282 static if(hasIndirections!V) 283 memset(&back(), 0, V.sizeof); 284 --_length; 285 return r; 286 } 287 288 /** remove all content but keep allocated memory */ 289 void clear() @trusted 290 { 291 // TODO: remove @trusted in case V's destructor is @system 292 static if(hasElaborateDestructor!V) 293 for(size_t i = 0; i < _length; ++i) 294 destroy(this[i]); 295 static if(hasIndirections!V) 296 memset(_ptr, 0, V.sizeof * _capacity); 297 _length = 0; 298 } 299 } 300 301 /// 302 /*@nogc*/ nothrow pure @safe unittest 303 { 304 Queue!int a; 305 a.pushBack([1,2,3]); 306 a.pushFront([4,5,6]); 307 308 assert(equal(a[], [6,5,4,1,2,3])); 309 310 assert(a.popFront == 6); 311 assert(a.popBack == 3); 312 313 assert(a.length == 4); 314 } 315 316 // check actual 'circular' buffer 317 @nogc nothrow pure @safe unittest 318 { 319 Queue!int q; 320 q.reserve(3); 321 assert(q.capacity == 3); 322 assert(q.empty); 323 324 // forward 325 q.pushBack(1); 326 q.pushBack(2); 327 q.pushBack(3); 328 assert(q.popFront == 1); q.pushBack(4); 329 assert(q.popFront == 2); q.pushBack(5); 330 assert(q.popFront == 3); q.pushBack(6); 331 assert(q.popFront == 4); q.pushBack(7); 332 assert(q[0] == 5); 333 assert(q[1] == 6); 334 assert(q[2] == 7); 335 336 q.clear(); 337 assert(q.empty); 338 assert(q.capacity == 3); 339 340 // backward 341 q.pushFront(1); 342 q.pushFront(2); 343 q.pushFront(3); 344 assert(q.popBack == 1); q.pushFront(4); 345 assert(q.popBack == 2); q.pushFront(5); 346 assert(q.popBack == 3); q.pushFront(6); 347 assert(q.popBack == 4); q.pushFront(7); 348 assert(q[$-1] == 5); 349 assert(q[$-2] == 6); 350 assert(q[$-3] == 7); 351 } 352 353 // check correct invocation of postblit/destructor 354 unittest 355 { 356 int counter = 0; 357 358 struct S 359 { 360 bool active; 361 this(bool active) { this.active = active; if(active) ++counter; } 362 this(this) { if(active) ++counter; } 363 ~this() { if(active) --counter; } 364 } 365 366 { 367 Queue!S a; 368 assert(counter == 0); 369 a.pushBack(S(true)); 370 assert(counter == 1); 371 a.pushFront(a[0]); 372 assert(counter == 2); 373 a.reserve(5); 374 a.pushBack(a[]); 375 376 Queue!S b = a; 377 assert(equal(a[], b[])); 378 assert(counter == 8); 379 } 380 assert(counter == 0); 381 } 382 383 // check move-semantics 384 unittest 385 { 386 struct S3 387 { 388 int x; 389 alias x this; 390 this(this) { assert(x == 0); } 391 } 392 393 Queue!S3 a; 394 a.pushBack(S3(1)); 395 a.pushBack(S3(2)); 396 a.pushFront(S3(3)); 397 a.reserve(5); 398 a.popBack(); 399 a.popFront(); 400 a[0] = S3(4); 401 a.clear(); 402 } 403 404 // type with no @safe/pure/etc-attributes 405 unittest 406 { 407 struct S 408 { 409 int* x; 410 this(this){ } 411 ~this(){ } 412 } 413 414 static assert(hasIndirections!S); 415 static assert(hasElaborateDestructor!S); 416 417 S s; 418 Queue!S a; 419 a.pushBack(s); 420 a.pushBack([s,s]); 421 a.pushFront(s); 422 a.pushFront([s,s]); 423 a.popBack(); 424 a.popFront(); 425 Queue!S b = a; 426 a.clear(); 427 assert(equal(b[], [s,s,s,s])); 428 }