1 /** 2 * Cyclic Buffer 3 * Copyright: © 2016 Economic Modeling Specialists, Intl. 4 * Authors: Nickolay Bukreyev 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 8 module containers.cyclicbuffer; 9 10 private import core.exception : onRangeError; 11 private import std.experimental.allocator.mallocator : Mallocator; 12 private import std.range.primitives : empty, front, back, popFront, popBack; 13 private import containers.internal.node : shouldAddGCRange; 14 15 /** 16 * Array that provides constant time (amortized) appending and popping 17 * at either end, as well as random access to the elements. 18 * 19 * Params: 20 * T = the array element type 21 * Allocator = the allocator to use. Defaults to `Mallocator`. 22 * supportGC = true if the container should support holding references to GC-allocated memory. 23 */ 24 struct CyclicBuffer(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T) 25 { 26 @disable this(this); 27 28 private import std.conv : emplace; 29 private import std.experimental.allocator.common : stateSize; 30 private import std.traits : isImplicitlyConvertible, hasElaborateDestructor; 31 32 static if (stateSize!Allocator != 0) 33 { 34 /// No default construction if an allocator must be provided. 35 @disable this(); 36 37 /** 38 * Use the given `allocator` for allocations. 39 */ 40 this(Allocator allocator) nothrow pure @safe @nogc 41 in 42 { 43 static if (is(typeof(allocator is null))) 44 assert(allocator !is null, "Allocator must not be null"); 45 } 46 do 47 { 48 this.allocator = allocator; 49 } 50 } 51 52 ~this() 53 { 54 clear(); 55 static if (useGC) 56 { 57 import core.memory : GC; 58 GC.removeRange(storage.ptr); 59 } 60 allocator.deallocate(storage); 61 } 62 63 /** 64 * Removes all contents from the buffer. 65 */ 66 void clear() 67 { 68 if (!empty) 69 { 70 static if (hasElaborateDestructor!T) 71 { 72 if (start <= end) 73 foreach (ref item; storage[start .. end + 1]) 74 .destroy(item); 75 else 76 { 77 foreach (ref item; storage[start .. $]) 78 .destroy(item); 79 foreach (ref item; storage[0 .. end + 1]) 80 .destroy(item); 81 } 82 } 83 start = (end + 1) % capacity; 84 _length = 0; 85 } 86 } 87 88 /** 89 * Ensures capacity is at least as large as specified. 90 */ 91 size_t reserve(size_t newCapacity) 92 { 93 immutable oldCapacity = capacity; 94 if (newCapacity <= oldCapacity) 95 return oldCapacity; 96 auto old = storage; 97 if (oldCapacity == 0) 98 storage = cast(typeof(storage)) allocator.allocate(newCapacity * T.sizeof); 99 else 100 { 101 auto a = cast(void[]) old; 102 allocator.reallocate(a, newCapacity * T.sizeof); 103 storage = cast(typeof(storage)) a; 104 } 105 static if (useGC) 106 { 107 import core.memory : GC; 108 //Add, then remove. Exactly in that order. 109 GC.addRange(storage.ptr, newCapacity * T.sizeof); 110 GC.removeRange(old.ptr); 111 } 112 if (empty) 113 end = (start - 1 + capacity) % capacity; 114 else if (start > end) 115 { 116 //The buffer was wrapped around prior to reallocation. 117 118 //`moveEmplaceAll` is only available in 2.069+, so use a low level alternative. 119 //Even more, we don't have to .init the moved away data, because we don't .destroy it. 120 import core.stdc.string : memcpy, memmove; 121 immutable prefix = end + 1; 122 immutable suffix = oldCapacity - start; 123 if (prefix <= suffix) 124 { 125 //The prefix is being moved right behind of suffix. 126 immutable space = newCapacity - oldCapacity; 127 if (space >= prefix) 128 { 129 memcpy(storage.ptr + oldCapacity, storage.ptr, prefix * T.sizeof); 130 end += oldCapacity; 131 } 132 else 133 { 134 //There is not enough space, so move what we can, 135 //and shift the rest to the start of the buffer. 136 memcpy(storage.ptr + oldCapacity, storage.ptr, space * T.sizeof); 137 end -= space; 138 memmove(storage.ptr, storage.ptr + space, (end + 1) * T.sizeof); 139 } 140 } 141 else 142 { 143 //The suffix is being moved forward, to the end of the buffer. 144 //Due to the fact that these locations may overlap, use `memmove`. 145 memmove(storage.ptr + newCapacity - suffix, storage.ptr + start, suffix * T.sizeof); 146 start = newCapacity - suffix; 147 } 148 //Ensure everything is still alright. 149 if (start <= end) 150 assert(end + 1 - start == length); 151 else 152 assert(end + 1 + (newCapacity - start) == length); 153 } 154 return capacity; 155 } 156 157 /** 158 * Inserts the given item into the start of the buffer. 159 */ 160 void insertFront(U)(U value) if (isImplicitlyConvertible!(U, T)) 161 { 162 if (empty) 163 reserve(4); 164 else if ((end + 1) % capacity == start) 165 reserve(capacity >= 65_536 ? capacity + 65_536 : capacity * 2); 166 start = (start - 1 + capacity) % capacity; 167 _length++; 168 emplace(&storage[start], value); 169 } 170 171 /** 172 * Inserts the given item into the end of the buffer. 173 */ 174 void insertBack(U)(U value) if (isImplicitlyConvertible!(U, T)) 175 { 176 if (empty) 177 reserve(4); 178 else if ((end + 1) % capacity == start) 179 reserve(capacity >= 65_536 ? capacity + 65_536 : capacity * 2); 180 end = (end + 1) % capacity; 181 _length++; 182 emplace(&storage[end], value); 183 } 184 185 /// ditto 186 alias insert = insertBack; 187 188 /// ditto 189 alias insertAnywhere = insertBack; 190 191 /// ditto 192 alias put = insertBack; 193 194 /** 195 * Removes the item at the start of the buffer. 196 */ 197 void removeFront() 198 { 199 version (assert) if (empty) onRangeError(); 200 size_t pos = start; 201 start = (start + 1) % capacity; 202 _length--; 203 static if (hasElaborateDestructor!T) 204 .destroy(storage[pos]); 205 } 206 207 /// ditto 208 alias popFront = removeFront; 209 210 /** 211 * Removes the item at the end of the buffer. 212 */ 213 void removeBack() 214 { 215 version (assert) if (empty) onRangeError(); 216 size_t pos = end; 217 end = (end - 1 + capacity) % capacity; 218 _length--; 219 static if (hasElaborateDestructor!T) 220 .destroy(storage[pos]); 221 } 222 223 /// ditto 224 alias popBack = removeBack; 225 226 /// Accesses to the item at the start of the buffer. 227 auto ref front(this This)() nothrow pure @property @safe 228 { 229 version (assert) if (empty) onRangeError(); 230 alias ET = ContainerElementType!(This, T, true); 231 return cast(ET) storage[start]; 232 } 233 234 /// Accesses to the item at the end of the buffer. 235 auto ref back(this This)() nothrow pure @property @safe 236 { 237 version (assert) if (empty) onRangeError(); 238 alias ET = ContainerElementType!(This, T, true); 239 return cast(ET) storage[end]; 240 } 241 242 /// buffer[i] 243 auto ref opIndex(this This)(size_t i) nothrow pure @safe 244 { 245 version (assert) if (i >= length) onRangeError(); 246 alias ET = ContainerElementType!(This, T, true); 247 return cast(ET) storage[(start + i) % $]; 248 } 249 250 /// buffer[] 251 Range!This opIndex(this This)() nothrow pure @safe @nogc 252 { 253 if (empty) 254 return typeof(return)(storage[0 .. 0], storage[0 .. 0]); 255 if (start <= end) 256 return typeof(return)(storage[start .. end + 1], storage[0 .. 0]); 257 return typeof(return)(storage[start .. $], storage[0 .. end + 1]); 258 } 259 260 /// buffer[i .. j] 261 size_t[2] opSlice(size_t k: 0)(size_t i, size_t j) const nothrow pure @safe @nogc 262 { 263 return [i, j]; 264 } 265 266 /// ditto 267 Range!This opIndex(this This)(size_t[2] indices) nothrow pure @safe 268 { 269 size_t i = indices[0], j = indices[1]; 270 version (assert) 271 { 272 if (i > j) onRangeError(); 273 if (j > length) onRangeError(); 274 } 275 if (i == j) 276 return typeof(return)(storage[0 .. 0], storage[0 .. 0]); 277 i = (start + i) % capacity; 278 j = (start + j) % capacity; 279 if (i < j) 280 return typeof(return)(storage[i .. j], storage[0 .. 0]); 281 return typeof(return)(storage[i .. $], storage[0 .. j]); 282 } 283 284 static struct Range(ThisT) 285 { 286 private 287 { 288 static if (is(ThisT == immutable)) 289 { 290 alias SliceT = immutable(ContainerStorageType!T)[]; 291 } 292 else static if (is(ThisT == const)) 293 { 294 alias SliceT = const(ContainerStorageType!T)[]; 295 } 296 else 297 { 298 alias SliceT = ContainerStorageType!T[]; 299 } 300 } 301 302 @disable this(); 303 304 this(SliceT a, SliceT b) nothrow pure @safe @nogc 305 { 306 head = a; 307 tail = b; 308 } 309 310 This save(this This)() nothrow pure @property @safe @nogc 311 { 312 return this; 313 } 314 315 bool empty() const nothrow pure @property @safe @nogc 316 { 317 return head.empty && tail.empty; 318 } 319 320 size_t length() const nothrow pure @property @safe @nogc 321 { 322 return head.length + tail.length; 323 } 324 325 alias opDollar = length; 326 327 auto ref front(this This)() nothrow pure @property @safe 328 { 329 if (!head.empty) 330 return cast(ET) head.front; 331 return cast(ET) tail.front; 332 } 333 334 auto ref back(this This)() nothrow pure @property @safe 335 { 336 if (!tail.empty) 337 return cast(ET) tail.back; 338 return cast(ET) head.back; 339 } 340 341 void popFront() nothrow pure @safe 342 { 343 if (head.empty) 344 { 345 import std.algorithm.mutation : swap; 346 //Always try to keep `head` non-empty. 347 swap(head, tail); 348 } 349 head.popFront(); 350 } 351 352 void popBack() nothrow pure @safe 353 { 354 if (!tail.empty) 355 tail.popBack(); 356 else 357 head.popBack(); 358 } 359 360 /// range[i] 361 auto ref opIndex(this This)(size_t i) nothrow pure @safe 362 { 363 return cast(ET) (i < head.length ? head[i] : tail[i - head.length]); 364 } 365 366 /// range[] 367 This opIndex(this This)() nothrow pure @safe @nogc 368 { 369 return this.save; 370 } 371 372 /// range[i .. j] 373 size_t[2] opSlice(size_t k: 0)(size_t i, size_t j) const nothrow pure @safe @nogc 374 { 375 return [i, j]; 376 } 377 378 /// ditto 379 This opIndex(this This)(size_t[2] indices) nothrow pure @safe 380 { 381 size_t i = indices[0], j = indices[1]; 382 version (assert) 383 { 384 if (i > j) onRangeError(); 385 if (j > length) onRangeError(); 386 } 387 if (i >= head.length) 388 return typeof(return)(tail[i - head.length .. j - head.length], tail[0 .. 0]); 389 if (j <= head.length) 390 return typeof(return)(head[i .. j], head[0 .. 0]); 391 return typeof(return)(head[i .. $], tail[0 .. j - head.length]); 392 } 393 394 /// range[...]++ 395 auto ref opUnary(string op)() nothrow pure @safe @nogc 396 if (op == "++" || op == "--") 397 { 398 mixin(op ~ "head[];"); 399 mixin(op ~ "tail[];"); 400 return this; 401 } 402 403 /// range[...] = value 404 auto ref opAssign(U)(const auto ref U value) nothrow pure @safe @nogc 405 { 406 head[] = value; 407 tail[] = value; 408 return this; 409 } 410 411 /// range[...] += value 412 auto ref opOpAssign(string op, U)(const auto ref U value) nothrow pure @safe @nogc 413 { 414 mixin("head[] " ~ op ~ "= value;"); 415 mixin("tail[] " ~ op ~ "= value;"); 416 return this; 417 } 418 419 private: 420 421 alias ET = ContainerElementType!(ThisT, T); 422 423 SliceT head, tail; 424 } 425 426 /// Returns: the number of items in the buffer. 427 size_t length() const nothrow pure @property @safe @nogc { return _length; } 428 429 /// ditto 430 alias opDollar = length; 431 432 /// Returns: maximal number of items the buffer can hold without reallocation. 433 size_t capacity() const nothrow pure @property @safe @nogc { return storage.length; } 434 435 /// Returns: whether or not the CyclicBuffer is empty. 436 bool empty() const nothrow pure @property @safe @nogc { return length == 0; } 437 438 private: 439 440 import containers.internal.storage_type : ContainerStorageType; 441 import containers.internal.element_type : ContainerElementType; 442 import containers.internal.mixins : AllocatorState; 443 444 enum bool useGC = supportGC && shouldAddGCRange!T; 445 mixin AllocatorState!Allocator; 446 ContainerStorageType!T[] storage; 447 size_t start, end, _length; 448 } 449 450 version(emsi_containers_unittest) private 451 { 452 import std.algorithm.comparison : equal; 453 import std.experimental.allocator.gc_allocator : GCAllocator; 454 import std.experimental.allocator.building_blocks.free_list : FreeList; 455 import std.range : iota, lockstep, StoppingPolicy; 456 457 struct S 458 { 459 int* a; 460 461 ~this() 462 { 463 (*a)++; 464 } 465 } 466 467 class C 468 { 469 int* a; 470 471 this(int* a) 472 { 473 this.a = a; 474 } 475 476 ~this() 477 { 478 (*a)++; 479 } 480 } 481 } 482 483 version(emsi_containers_unittest) unittest 484 { 485 static void test(int size) 486 { 487 { 488 CyclicBuffer!int b; 489 assert(b.empty); 490 foreach (i; 0 .. size) 491 { 492 assert(b.length == i); 493 b.insertBack(i); 494 assert(b.back == i); 495 } 496 assert(b.length == size); 497 foreach (i; 0 .. size) 498 { 499 assert(b.length == size - i); 500 assert(b.front == i); 501 b.removeFront(); 502 } 503 assert(b.empty); 504 } 505 { 506 CyclicBuffer!int b; 507 foreach (i; 0 .. size) 508 { 509 assert(b.length == i); 510 b.insertFront(i); 511 assert(b.front == i); 512 } 513 assert(b.length == size); 514 foreach (i; 0 .. size) 515 { 516 assert(b.length == size - i); 517 assert(b.back == i); 518 b.removeBack(); 519 } 520 assert(b.empty); 521 } 522 } 523 524 foreach (size; [1, 2, 3, 4, 5, 7, 8, 9, 512, 520, 0x10000, 0x10001, 0x20000]) 525 test(size); 526 } 527 528 version(emsi_containers_unittest) unittest 529 { 530 static void test(int prefix, int suffix, int newSize) 531 { 532 CyclicBuffer!int b; 533 foreach_reverse (i; 0 .. suffix) 534 b.insertFront(i); 535 foreach (i; suffix .. suffix + prefix) 536 b.insertBack(i); 537 assert(b.length == prefix + suffix); 538 b.reserve(newSize); 539 assert(b.length == prefix + suffix); 540 assert(equal(b[], iota(prefix + suffix))); 541 } 542 543 immutable prefixes = [2, 3, 3, 4, 4]; 544 immutable suffixes = [3, 2, 4, 3, 4]; 545 immutable sizes = [16, 16, 9, 9, 9]; 546 547 foreach (a, b, c; lockstep(prefixes, suffixes, sizes, StoppingPolicy.requireSameLength)) 548 test(a, b, c); 549 } 550 551 version(emsi_containers_unittest) unittest 552 { 553 int* a = new int; 554 { 555 CyclicBuffer!S b; 556 { 557 S s = { a }; 558 foreach (i; 0 .. 5) 559 b.insertBack(s); 560 assert(*a == 5); 561 foreach (i; 0 .. 5) 562 b.insertBack(S(a)); 563 assert(*a == 10); 564 foreach (i; 0 .. 5) 565 { 566 b.removeBack(); 567 b.removeFront(); 568 } 569 assert(*a == 20); 570 } 571 assert(*a == 21); 572 } 573 assert(*a == 21); 574 } 575 576 version(emsi_containers_unittest) unittest 577 { 578 int* a = new int; 579 CyclicBuffer!C b; 580 { 581 C c = new C(a); 582 foreach (i; 0 .. 10) 583 b.insertBack(c); 584 assert(*a == 0); 585 foreach (i; 0 .. 5) 586 { 587 b.removeBack(); 588 b.removeFront(); 589 } 590 foreach (i; 0 .. b.capacity) 591 b.insertFront(null); 592 assert(*a == 0); 593 } 594 string s = ""; 595 foreach (i; 0 .. 1_000) 596 s = s ~ 'a'; 597 s = ""; 598 import core.memory : GC; 599 GC.collect(); 600 assert(*a == 0 || *a == 1); 601 } 602 603 version(emsi_containers_unittest) unittest 604 { 605 CyclicBuffer!int b; 606 b.insertFront(10); 607 assert(b[0] == 10); 608 b.insertFront(20); 609 assert(b[0] == 20); 610 assert(b[1] == 10); 611 b.insertFront(30); 612 assert(b[0] == 30); 613 assert(b[1] == 20); 614 assert(b[2] == 10); 615 b.insertBack(5); 616 assert(b[0] == 30); 617 assert(b[1] == 20); 618 assert(b[2] == 10); 619 assert(b[3] == 5); 620 b.back = 7; 621 assert(b[3] == 7); 622 } 623 624 version(emsi_containers_unittest) unittest 625 { 626 import std.range : isInputRange, isForwardRange, isBidirectionalRange, isRandomAccessRange; 627 CyclicBuffer!int b; 628 static assert(isInputRange!(typeof(b[]))); 629 static assert(isForwardRange!(typeof(b[]))); 630 static assert(isBidirectionalRange!(typeof(b[]))); 631 static assert(isRandomAccessRange!(typeof(b[]))); 632 } 633 634 version(emsi_containers_unittest) unittest 635 { 636 CyclicBuffer!int b; 637 assert(b[].empty); 638 } 639 640 version(emsi_containers_unittest) unittest 641 { 642 FreeList!(Mallocator, 0, 64) alloc; 643 FreeList!(GCAllocator, 0, 64) alloc2; 644 auto b = CyclicBuffer!(int, typeof(&alloc))(&alloc); 645 auto b2 = CyclicBuffer!(int, typeof(&alloc2))(&alloc2); 646 auto b3 = CyclicBuffer!(int, GCAllocator)(); 647 } 648 649 version(emsi_containers_unittest) unittest 650 { 651 static void testConst(const ref CyclicBuffer!int b, int x) 652 { 653 assert(b[0] == x); 654 assert(b.front == x); 655 static assert(!__traits(compiles, { ++b[0]; } )); 656 assert(equal(b[], [x])); 657 } 658 659 CyclicBuffer!int b; 660 b.insertFront(0); 661 assert(b.front == 0); 662 b.front++; 663 assert(b[0] == 1); 664 b[0]++; 665 ++b[0]; 666 assert(b.front == 3); 667 assert(!b.empty); 668 b[0] *= 2; 669 assert(b[0] == 6); 670 testConst(b, 6); 671 b[]++; 672 assert(equal(b[], [7])); 673 b[0] = 5; 674 assert(b[0] == 5); 675 assert(b.front == 5); 676 testConst(b, 5); 677 assert(b[][0] == 5); 678 } 679 680 version(emsi_containers_unittest) unittest 681 { 682 int* a = new int; 683 { 684 CyclicBuffer!S b; 685 foreach (i; 0 .. 5) 686 b.insertBack(S(a)); 687 assert(*a == 5); 688 } 689 assert(*a == 10); 690 *a = 0; 691 { 692 CyclicBuffer!S b; 693 foreach (i; 0 .. 4) 694 b.insertBack(S(a)); 695 assert(*a == 4); 696 b.removeFront(); 697 assert(*a == 5); 698 b.insertBack(S(a)); 699 assert(*a == 6); 700 } 701 assert(*a == 10); 702 } 703 704 version(emsi_containers_unittest) unittest 705 { 706 CyclicBuffer!int b; 707 foreach (i; 0 .. 4) 708 b.insertBack(i); 709 b.removeFront(); 710 b.removeFront(); 711 b.insertBack(4); 712 b.insertBack(5); 713 assert(equal(b[], [2, 3, 4, 5])); 714 b.reserve(5); 715 assert(equal(b[], [2, 3, 4, 5])); 716 } 717 718 version(emsi_containers_unittest) unittest 719 { 720 CyclicBuffer!int b; 721 foreach (i; 0 .. 4) 722 b.insertBack(i); 723 b.removeFront(); 724 b.removeFront(); 725 b.removeFront(); 726 b.insertBack(4); 727 b.insertBack(5); 728 b.insertBack(6); 729 assert(equal(b[], [3, 4, 5, 6])); 730 b.reserve(5); 731 assert(equal(b[], [3, 4, 5, 6])); 732 } 733 734 version(emsi_containers_unittest) unittest 735 { 736 static void test(ref CyclicBuffer!int b) 737 { 738 assert(equal(b[], [4, 5, 6, 7, 8, 9, 10, 11])); 739 assert(b[3 .. 3].empty); 740 auto slice = b[1 .. 6]; 741 assert(equal(slice, [5, 6, 7, 8, 9])); 742 slice[3 .. 5] = 0; 743 assert(equal(b[], [4, 5, 6, 7, 0, 0, 10, 11])); 744 slice[0 .. 2] += 1; 745 assert(equal(b[], [4, 6, 7, 7, 0, 0, 10, 11])); 746 slice[0 .. 2]--; 747 assert(equal(b[], [4, 5, 6, 7, 0, 0, 10, 11])); 748 auto copy = slice.save; 749 assert(equal(slice, copy)); 750 assert(equal(slice, copy[])); 751 assert(slice.back == 0); 752 slice.popBack(); 753 assert(equal(slice, [5, 6, 7, 0])); 754 assert(slice.back == 0); 755 slice.popBack(); 756 assert(equal(slice, [5, 6, 7])); 757 assert(slice.back == 7); 758 slice.popBack(); 759 assert(equal(slice, [5, 6])); 760 assert(equal(copy, [5, 6, 7, 0, 0])); 761 slice[1] = 10; 762 assert(-copy[1] == -10); 763 copy[1] *= 2; 764 assert(slice[1] == 20); 765 assert(b[2] == 20); 766 auto copy2 = copy[0 .. $]; 767 assert(equal(copy, copy2)); 768 } 769 770 { 771 CyclicBuffer!int b; 772 foreach (i; 4 .. 12) 773 b.insertBack(i); 774 test(b); 775 } 776 { 777 CyclicBuffer!int b; 778 foreach (i; 0 .. 8) 779 b.insertBack(i); 780 foreach (i; 0 .. 4) 781 b.removeFront(); 782 foreach (i; 8 .. 12) 783 b.insertBack(i); 784 test(b); 785 } 786 } 787 788 version(emsi_containers_unittest) unittest 789 { 790 CyclicBuffer!int b; 791 foreach (i; 0 .. 10) 792 b.insertBack(i); 793 assert(b.capacity >= 10); 794 b.reserve(12); 795 assert(b.capacity >= 12); 796 } 797 798 version(emsi_containers_unittest) unittest 799 { 800 CyclicBuffer!int b; 801 foreach (i; 0 .. 6) 802 b.insertBack(i); 803 foreach (i; 6 .. 8) 804 b.insertFront(i); 805 assert(equal(b[], [7, 6, 0, 1, 2, 3, 4, 5])); 806 b.reserve(b.capacity + 1); 807 assert(equal(b[], [7, 6, 0, 1, 2, 3, 4, 5])); 808 } 809 810 version(emsi_containers_unittest) unittest 811 { 812 static class Foo 813 { 814 string name; 815 } 816 817 CyclicBuffer!Foo b; 818 }