1 module stdx.collections.dlist; 2 3 import stdx.collections.common; 4 5 debug(CollectionDList) import std.stdio; 6 7 version(unittest) 8 { 9 import std.experimental.allocator.mallocator; 10 import std.experimental.allocator.building_blocks.stats_collector; 11 import std.experimental.allocator : RCIAllocator, allocatorObject; 12 13 private alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed); 14 } 15 16 struct DList(T) 17 { 18 import std.experimental.allocator : RCIAllocator, RCISharedAllocator, 19 theAllocator, processAllocator, make, dispose; 20 import std.experimental.allocator.building_blocks.affix_allocator; 21 import std.traits : isImplicitlyConvertible; 22 import std.range.primitives : isInputRange, ElementType; 23 import std.variant : Algebraic; 24 import std.conv : emplace; 25 import core.atomic : atomicOp; 26 27 private: 28 struct Node 29 { 30 T _payload; 31 Node *_next; 32 Node *_prev; 33 34 this(T v, Node *n, Node *p) 35 { 36 debug(CollectionDList) writefln("DList.Node.ctor: Constructing node" ~ 37 " with payload: %s", v); 38 _payload = v; 39 _next = n; 40 _prev = p; 41 } 42 43 ~this() 44 { 45 debug(CollectionDList) writefln("DList.Node.dtor: Destroying node" ~ 46 " with payload: %s", _payload); 47 } 48 } 49 50 Node *_head; 51 52 alias LocalAllocT = AffixAllocator!(RCIAllocator, size_t); 53 alias SharedAllocT = AffixAllocator!(RCISharedAllocator, size_t); 54 alias MutableAlloc = Algebraic!(LocalAllocT, SharedAllocT); 55 Mutable!MutableAlloc _ouroborosAllocator; 56 57 /// Returns the actual allocator from ouroboros 58 @trusted ref auto allocator(this Q)() 59 { 60 static if (is(Q == immutable) || is(Q == const)) 61 { 62 assert(_ouroborosAllocator.get.peek!(SharedAllocT) !is null); 63 return _ouroborosAllocator.get.get!(SharedAllocT); 64 } 65 else 66 { 67 assert(_ouroborosAllocator.get.peek!(LocalAllocT) !is null); 68 return _ouroborosAllocator.get.get!(LocalAllocT); 69 } 70 } 71 72 /// Constructs the ouroboros allocator from allocator if the ouroboros 73 //allocator wasn't previously set 74 public @trusted bool setAllocator(RCIAllocator allocator) 75 { 76 if (_ouroborosAllocator.get.peek!(LocalAllocT) is null) 77 { 78 _ouroborosAllocator = Mutable!(MutableAlloc)(allocator, 79 MutableAlloc(LocalAllocT(allocator))); 80 return true; 81 } 82 return false; 83 } 84 85 mixin template setSharedAllocator(RCISharedAllocator allocator) 86 { 87 _ouroborosAllocator = Mutable!(MutableAlloc)(allocator, 88 MutableAlloc(SharedAllocT(allocator))); 89 } 90 91 public @trusted auto ref getAllocator(this Q)() 92 { 93 static if (is(Q == immutable) || is(Q == const)) 94 { 95 return _ouroborosAllocator.get.peek!(SharedAllocT) is null ? 96 RCISharedAllocator(null) : allocator().parent; 97 } 98 else 99 { 100 return _ouroborosAllocator.get.peek!(LocalAllocT) is null ? 101 RCIAllocator(null) : allocator().parent; 102 } 103 } 104 105 @trusted void addRef(QualNode, this Q)(QualNode node) 106 { 107 assert(node !is null); 108 debug(CollectionDList) 109 { 110 writefln("DList.addRef: Node %s has refcount: %s; will be: %s", 111 node._payload, *prefCount(node), *prefCount(node) + 1); 112 } 113 static if (is(Q == immutable) || is(Q == const)) 114 { 115 atomicOp!"+="(*prefCount(node), 1); 116 } 117 else 118 { 119 ++*prefCount(node); 120 } 121 } 122 123 @trusted void delRef(ref Node *node) 124 { 125 assert(node !is null); 126 size_t *pref = prefCount(node); 127 debug(CollectionDList) writefln("DList.delRef: Node %s has refcount: %s; will be: %s", 128 node._payload, *pref, *pref - 1); 129 if (*pref == 0) 130 { 131 debug(CollectionDList) writefln("DList.delRef: Deleting node %s", node._payload); 132 allocator.dispose(node); 133 node = null; 134 } 135 else 136 { 137 --*pref; 138 } 139 } 140 141 @trusted auto prefCount(QualNode, this Q)(QualNode node) 142 { 143 assert(node !is null); 144 static if (is(Q == immutable) || is(Q == const)) 145 { 146 return cast(shared size_t*)(&allocator.prefix(cast(void[Node.sizeof])(*node))); 147 } 148 else 149 { 150 return cast(size_t*)(&allocator.prefix(cast(void[Node.sizeof])(*node))); 151 } 152 } 153 154 static string immutableInsert(string stuff) 155 { 156 return "" 157 ~"auto tmpAlloc = Mutable!(MutableAlloc)(allocator, MutableAlloc(allocator));" 158 ~"_ouroborosAllocator = (() @trusted => cast(immutable)(tmpAlloc))();" 159 ~"Node *tmpNode;" 160 ~"Node *tmpHead;" 161 ~"foreach (item; " ~ stuff ~ ")" 162 ~"{" 163 ~"Node *newNode;" 164 ~"() @trusted { newNode =" 165 ~"tmpAlloc.get().make!(Node)(item, null, null);" 166 ~"}();" 167 ~"if (tmpHead is null)" 168 ~"{" 169 ~"tmpHead = tmpNode = newNode;" 170 ~"}" 171 ~"else" 172 ~"{" 173 ~"tmpNode._next = newNode;" 174 ~"newNode._prev = tmpNode;" 175 ~"addRef(newNode._prev);" 176 ~"tmpNode = newNode;" 177 ~"}" 178 ~"}" 179 ~"_head = (() @trusted => cast(immutable Node*)(tmpHead))();"; 180 } 181 182 public: 183 this(A, this Q)(A allocator) 184 if (((is(Q == immutable) || is(Q == const)) && is(A == RCISharedAllocator)) 185 || (!(is(Q == immutable) || is(Q == const)) && is(A == RCIAllocator))) 186 { 187 debug(CollectionDList) 188 { 189 writefln("DList.ctor: begin"); 190 scope(exit) writefln("DList.ctor: end"); 191 } 192 static if (is(Q == immutable) || is(Q == const)) 193 { 194 mixin setSharedAllocator!(allocator); 195 } 196 else 197 { 198 setAllocator(allocator); 199 } 200 } 201 202 this(U, this Q)(U[] values...) 203 if (isImplicitlyConvertible!(U, T)) 204 { 205 static if (is(Q == immutable) || is(Q == const)) 206 { 207 this(processAllocator, values); 208 } 209 else 210 { 211 this(theAllocator, values); 212 } 213 } 214 215 this(A, U, this Q)(A allocator, U[] values...) 216 if ((((is(Q == immutable) || is(Q == const)) && is(A == RCISharedAllocator)) 217 || (!(is(Q == immutable) || is(Q == const)) && is(A == RCIAllocator))) 218 && isImplicitlyConvertible!(U, T)) 219 { 220 debug(CollectionDList) 221 { 222 writefln("DList.ctor: begin"); 223 scope(exit) writefln("DList.ctor: end"); 224 } 225 static if (is(Q == immutable) || is(Q == const)) 226 { 227 mixin setSharedAllocator!(allocator); 228 mixin(immutableInsert("values")); 229 //auto tmpAlloc = Mutable!(MutableAlloc)(allocator, MutableAlloc(allocator)); 230 //_ouroborosAllocator = (() @trusted => cast(immutable)(tmpAlloc))(); 231 //Node *tmpNode; 232 //Node *tmpHead; 233 //foreach (item; values) 234 //{ 235 //Node *newNode; 236 //() @trusted { newNode = 237 //tmpAlloc.get().make!(Node)(item, null, null); 238 //}(); 239 //if (tmpHead is null) 240 //{ 241 //tmpHead = tmpNode = newNode; 242 //} 243 //else 244 //{ 245 //tmpNode._next = newNode; 246 //newNode._prev = tmpNode; 247 //addRef(newNode._prev); 248 //tmpNode = newNode; 249 //} 250 //} 251 //_head = (() @trusted => cast(immutable Node*)(tmpHead))(); 252 } 253 else 254 { 255 setAllocator(allocator); 256 insert(0, values); 257 } 258 } 259 260 this(Stuff, this Q)(Stuff stuff) 261 if (isInputRange!Stuff 262 && isImplicitlyConvertible!(ElementType!Stuff, T) 263 && !is(Stuff == T[])) 264 { 265 static if (is(Q == immutable) || is(Q == const)) 266 { 267 this(processAllocator, stuff); 268 } 269 else 270 { 271 this(theAllocator, stuff); 272 } 273 } 274 275 this(A, Stuff, this Q)(A allocator, Stuff stuff) 276 if ((((is(Q == immutable) || is(Q == const)) && is(A == RCISharedAllocator)) 277 || (!(is(Q == immutable) || is(Q == const)) && is(A == RCIAllocator))) 278 && isInputRange!Stuff 279 && isImplicitlyConvertible!(ElementType!Stuff, T) 280 && !is(Stuff == T[])) 281 { 282 debug(CollectionDList) 283 { 284 writefln("DList.ctor: begin"); 285 scope(exit) writefln("DList.ctor: end"); 286 } 287 static if (is(Q == immutable) || is(Q == const)) 288 { 289 mixin setSharedAllocator!(allocator); 290 mixin(immutableInsert("stuff")); 291 } 292 else 293 { 294 setAllocator(allocator); 295 insert(0, stuff); 296 } 297 } 298 299 this(this) 300 { 301 debug(CollectionDList) 302 { 303 writefln("DList.postblit: begin"); 304 scope(exit) writefln("DList.postblit: end"); 305 } 306 if (_head !is null) 307 { 308 addRef(_head); 309 debug(CollectionDList) writefln("DList.postblit: Node %s has refcount: %s", 310 _head._payload, *prefCount(_head)); 311 } 312 } 313 314 // Immutable ctors 315 private this(NodeQual, OuroQual, this Qualified)(NodeQual _newHead, 316 OuroQual ouroborosAllocator) 317 if (is(typeof(_head) : typeof(_newHead)) 318 && (is(Qualified == immutable) || is(Qualified == const))) 319 { 320 _head = _newHead; 321 _ouroborosAllocator = ouroborosAllocator; 322 if (_head !is null) 323 { 324 addRef(_head); 325 debug(CollectionDList) writefln("DList.ctor immutable: Node %s has " 326 ~ "refcount: %s", _head._payload, *prefCount(_head)); 327 } 328 } 329 330 ~this() 331 { 332 debug(CollectionDList) 333 { 334 writefln("DList.dtor: Begin for instance %s of type %s", 335 cast(size_t)(&this), typeof(this).stringof); 336 scope(exit) writefln("DList.dtor: End for instance %s of type %s", 337 cast(size_t)(&this), typeof(this).stringof); 338 } 339 if (_head !is null) 340 { 341 delRef(_head); 342 if (_head !is null 343 && ((_head._prev !is null) || (_head._next !is null))) 344 { 345 // If it was a single node list, only delRef must be used 346 // in order to avoid premature/double freeing 347 destroyUnused(_head); 348 } 349 } 350 } 351 352 void destroyUnused(Node *startNode) 353 { 354 debug(CollectionDList) 355 { 356 writefln("DList.destoryUnused: begin"); 357 scope(exit) writefln("DList.destoryUnused: end"); 358 } 359 360 if (startNode is null) return; 361 362 Node *tmpNode = startNode; 363 bool isCycle = true; 364 while (tmpNode !is null) 365 { 366 if (((tmpNode._next is null || tmpNode._prev is null) 367 && *prefCount(tmpNode) == 0) 368 || (tmpNode._next !is null && tmpNode._prev !is null 369 && *prefCount(tmpNode) == 1)) 370 { 371 // The last node should always have rc == 0 (only one ref, 372 // from prev._next) 373 // The first node should always have rc == 0 (only one ref, 374 // from next._prev), since we don't take into account 375 // the head ref (that was deleted either by the dtor or by pop) 376 // Nodes within the cycle should always have rc == 1 377 tmpNode = tmpNode._next; 378 } 379 else 380 { 381 isCycle = false; 382 break; 383 } 384 } 385 386 tmpNode = startNode._prev; 387 while (isCycle && tmpNode !is null) 388 { 389 if (((tmpNode._next is null || tmpNode._prev is null) 390 && *prefCount(tmpNode) == 0) 391 || (tmpNode._next !is null && tmpNode._prev !is null 392 && *prefCount(tmpNode) == 1)) 393 { 394 tmpNode = tmpNode._prev; 395 } 396 else 397 { 398 isCycle = false; 399 break; 400 } 401 } 402 403 if (isCycle) 404 { 405 // We can safely deallocate memory 406 tmpNode = startNode._next; 407 while (tmpNode !is null) 408 { 409 Node *oldNode = tmpNode; 410 tmpNode = tmpNode._next; 411 () @trusted { allocator.dispose(oldNode); }(); 412 } 413 tmpNode = startNode; 414 while (tmpNode !is null) 415 { 416 Node *oldNode = tmpNode; 417 tmpNode = tmpNode._prev; 418 () @trusted { allocator.dispose(oldNode); }(); 419 } 420 } 421 } 422 423 bool isUnique(this _)() 424 { 425 debug(CollectionDList) 426 { 427 writefln("DList.isUnique: begin"); 428 scope(exit) writefln("DList.isUnique: end"); 429 } 430 431 if (empty) 432 { 433 return true; 434 } 435 436 Node *tmpNode = (() @trusted => cast(Node*)_head)(); 437 438 // Rewind to the beginning of the list 439 while (tmpNode !is null && tmpNode._prev !is null) 440 { 441 tmpNode = tmpNode._prev; 442 } 443 444 // For a single node list, head should have rc == 0 445 if (tmpNode._next is null && (*prefCount(tmpNode) > 0)) 446 { 447 return false; 448 } 449 450 while (tmpNode !is null) 451 { 452 if (tmpNode._next is null || tmpNode._prev is null) 453 { 454 // The first and last node should have rc == 0 unless the _head 455 // is pointing to them, in which case rc must be 1 456 if (((tmpNode is _head) && (*prefCount(tmpNode) > 1)) 457 || ((tmpNode !is _head) && (*prefCount(tmpNode) > 0))) 458 { 459 return false; 460 } 461 } 462 else if (((tmpNode is _head) && (*prefCount(tmpNode) > 2)) 463 || ((tmpNode !is _head) && (*prefCount(tmpNode) > 1))) 464 { 465 // Any other node should have rc == 1 unless the _head 466 // is pointing to it, in which case rc must be 2 467 return false; 468 } 469 tmpNode = tmpNode._next; 470 } 471 472 return true; 473 } 474 475 bool empty(this _)() 476 { 477 return _head is null; 478 } 479 480 ref auto front(this _)() 481 { 482 assert(!empty, "DList.front: List is empty"); 483 return _head._payload; 484 } 485 486 void popFront() 487 { 488 debug(CollectionDList) 489 { 490 writefln("DList.popFront: begin"); 491 scope(exit) writefln("DList.popFront: end"); 492 } 493 assert(!empty, "DList.popFront: List is empty"); 494 Node *tmpNode = _head; 495 _head = _head._next; 496 if (_head !is null) 497 { 498 addRef(_head); 499 delRef(tmpNode); 500 } 501 else 502 { 503 delRef(tmpNode); 504 if (tmpNode !is null 505 && ((tmpNode._prev !is null) || (tmpNode._next !is null))) 506 { 507 // If it was a single node list, only delRef must be used 508 // in order to avoid premature/double freeing 509 destroyUnused(tmpNode); 510 } 511 } 512 } 513 514 void popPrev() 515 { 516 debug(CollectionDList) 517 { 518 writefln("DList.popPrev: begin"); 519 scope(exit) writefln("DList.popPrev: end"); 520 } 521 assert(!empty, "DList.popPrev: List is empty"); 522 Node *tmpNode = _head; 523 _head = _head._prev; 524 if (_head !is null) { 525 addRef(_head); 526 delRef(tmpNode); 527 } 528 else 529 { 530 delRef(tmpNode); 531 if (tmpNode !is null 532 && ((tmpNode._prev !is null) || (tmpNode._next !is null))) 533 { 534 // If it was a single node list, only delRef must be used 535 // in order to avoid premature/double freeing 536 destroyUnused(tmpNode); 537 } 538 } 539 } 540 541 Qualified tail(this Qualified)() 542 { 543 debug(CollectionDList) 544 { 545 writefln("DList.popFront: begin"); 546 scope(exit) writefln("DList.popFront: end"); 547 } 548 assert(!empty, "DList.popFront: List is empty"); 549 550 static if (is(Qualified == immutable) || is(Qualified == const)) 551 { 552 return typeof(this)(_head._next, _ouroborosAllocator); 553 } 554 else 555 { 556 return .tail(this); 557 } 558 } 559 560 ref Qualified save(this Qualified)() 561 { 562 debug(CollectionDList) 563 { 564 writefln("DList.save: begin"); 565 scope(exit) writefln("DList.save: end"); 566 } 567 return this; 568 } 569 570 typeof(this) dup() 571 { 572 debug(CollectionDList) 573 { 574 writefln("DList.dup: begin"); 575 scope(exit) writefln("DList.dup: end"); 576 } 577 RCIAllocator alloc = getAllocator(); 578 if (alloc.isNull) 579 { 580 alloc = theAllocator; 581 } 582 return typeof(this)(alloc, this); 583 } 584 585 size_t insert(Stuff)(size_t pos, Stuff stuff) 586 if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) 587 { 588 debug(CollectionDList) 589 { 590 writefln("DList.insert: begin"); 591 scope(exit) writefln("DList.insert: end"); 592 } 593 setAllocator(theAllocator); 594 595 size_t result; 596 Node *tmpNode; 597 Node *tmpHead; 598 foreach (item; stuff) 599 { 600 Node *newNode; 601 () @trusted { newNode = allocator.make!(Node)(item, null, null); }(); 602 if (tmpHead is null) 603 { 604 tmpHead = tmpNode = newNode; 605 } 606 else 607 { 608 tmpNode._next = newNode; 609 newNode._prev = tmpNode; 610 addRef(newNode._prev); 611 tmpNode = newNode; 612 } 613 ++result; 614 } 615 616 if (!tmpNode) 617 { 618 return 0; 619 } 620 621 if (!_head) assert(pos == 0); 622 623 size_t initPos = pos; 624 Node *needle = _head; 625 while (pos && needle._next !is null) 626 { 627 needle = needle._next; 628 --pos; 629 } 630 631 // Check if we need to insert at the back of the list 632 if (initPos != 0 && needle._next is null && pos >= 1) 633 { 634 // We need to insert at the back of the list 635 assert(pos == 1, "Index out of range"); 636 needle._next = tmpHead; 637 tmpHead._prev = needle; 638 addRef(needle); 639 return result; 640 } 641 assert(pos == 0, "Index out of range"); 642 643 tmpNode._next = needle; 644 if (needle !is null) 645 { 646 addRef(needle); 647 if (needle._prev !is null) 648 { 649 tmpHead._prev = needle._prev; 650 needle._prev._next = tmpHead; 651 // Inc ref only when tmpHead will be the new head of the list 652 if (initPos == 0) 653 { 654 addRef(tmpHead); 655 } 656 657 // Delete extra ref, since we already added the ref earlier 658 // through tmpNode._next 659 delRef(needle); 660 } 661 if (initPos == 0) 662 { 663 // Pass the ref to the new head 664 delRef(needle); 665 } 666 assert(needle !is null); 667 needle._prev = tmpNode; 668 if (tmpHead == tmpNode) 669 { 670 addRef(tmpHead); 671 } 672 else 673 { 674 addRef(needle._prev); 675 } 676 } 677 678 if (initPos == 0) 679 { 680 _head = tmpHead; 681 } 682 return result; 683 } 684 685 size_t insert(Stuff)(size_t pos, Stuff[] stuff...) 686 if (isImplicitlyConvertible!(Stuff, T)) 687 { 688 return insert(pos, stuff); 689 } 690 691 size_t insertBack(Stuff)(Stuff stuff) 692 if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) 693 { 694 debug(CollectionDList) 695 { 696 writefln("DList.insertBack: begin"); 697 scope(exit) writefln("DList.insertBack: end"); 698 } 699 setAllocator(theAllocator); 700 701 size_t result; 702 Node *tmpNode; 703 Node *tmpHead; 704 foreach (item; stuff) 705 { 706 Node *newNode; 707 () @trusted { newNode = allocator.make!(Node)(item, null, null); }(); 708 if (tmpHead is null) 709 { 710 tmpHead = tmpNode = newNode; 711 } 712 else 713 { 714 tmpNode._next = newNode; 715 newNode._prev = tmpNode; 716 addRef(newNode._prev); 717 tmpNode = newNode; 718 } 719 ++result; 720 } 721 722 if (!tmpNode) 723 { 724 return 0; 725 } 726 727 if (_head is null) 728 { 729 _head = tmpHead; 730 } 731 else 732 { 733 Node *endNode; 734 for (endNode = _head; endNode._next !is null; endNode = endNode._next) { } 735 endNode._next = tmpHead; 736 // don't addRef(tmpHead) since the ref will pass from tmpHead to 737 // endNode._next when tmpHead's scope ends 738 tmpHead._prev = endNode; 739 addRef(endNode); 740 } 741 742 return result; 743 } 744 745 size_t insertBack(Stuff)(Stuff[] stuff...) 746 if (isImplicitlyConvertible!(Stuff, T)) 747 { 748 return insertBack(stuff); 749 } 750 751 auto ref opBinary(string op, U)(auto ref U rhs) 752 if (op == "~" && 753 (is (U == typeof(this)) 754 || is (U : T) 755 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 756 )) 757 { 758 debug(CollectionDList) 759 { 760 writefln("DList.opBinary!~: begin"); 761 scope(exit) writefln("DList.opBinary!~: end"); 762 } 763 764 RCIAllocator alloc = getAllocator(); 765 if (alloc.isNull) 766 { 767 static if (is(U == typeof(this))) 768 { 769 alloc = rhs.getAllocator(); 770 } 771 else 772 { 773 alloc = null; 774 } 775 if (alloc.isNull) 776 { 777 alloc = theAllocator; 778 } 779 } 780 781 typeof(this) newList = typeof(this)(alloc, rhs); 782 newList.insert(0, this); 783 return newList; 784 } 785 786 auto ref opAssign()(auto ref typeof(this) rhs) 787 { 788 debug(CollectionDList) 789 { 790 writefln("DList.opAssign: begin"); 791 scope(exit) writefln("DList.opAssign: end"); 792 } 793 794 if (rhs._head !is null && _head is rhs._head) 795 { 796 return this; 797 } 798 799 if (rhs._head !is null) 800 { 801 rhs.addRef(rhs._head); 802 debug(CollectionDList) writefln("DList.opAssign: Node %s has refcount: %s", 803 rhs._head._payload, *prefCount(rhs._head)); 804 } 805 806 if (_head !is null) 807 { 808 Node *tmpNode = _head; 809 delRef(tmpNode); 810 if (tmpNode !is null 811 && ((tmpNode._prev !is null) || (tmpNode._next !is null))) 812 { 813 // If it was a single node list, only delRef must be used 814 // in order to avoid premature/double freeing 815 destroyUnused(tmpNode); 816 } 817 } 818 _head = rhs._head; 819 _ouroborosAllocator = rhs._ouroborosAllocator; 820 821 return this; 822 } 823 824 auto ref opOpAssign(string op, U)(auto ref U rhs) 825 if (op == "~" && 826 (is (U == typeof(this)) 827 || is (U : T) 828 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 829 )) 830 { 831 debug(CollectionDList) 832 { 833 writefln("DList.opOpAssign!~: %s begin", typeof(this).stringof); 834 scope(exit) writefln("DList.opOpAssign!~: %s end", typeof(this).stringof); 835 } 836 837 insertBack(rhs); 838 return this; 839 } 840 841 void remove() 842 { 843 debug(CollectionDList) 844 { 845 writefln("DList.remove: begin"); 846 scope(exit) writefln("DList.remove: end"); 847 } 848 assert(!empty, "DList.remove: List is empty"); 849 850 Node *tmpNode = _head; 851 _head = _head._next; 852 if (_head !is null) 853 { 854 //addRef(_head); 855 _head._prev = tmpNode._prev; 856 delRef(tmpNode); // Remove tmpNode._next._prev ref 857 tmpNode._next = null; 858 //delRef(_head); 859 if (tmpNode._prev !is null) 860 { 861 addRef(_head); 862 tmpNode._prev._next = _head; 863 delRef(tmpNode); // Remove tmpNode._prev._next ref 864 tmpNode._prev = null; 865 } 866 } 867 else if (tmpNode._prev !is null) 868 { 869 _head = tmpNode._prev; 870 //addRef(_head); 871 tmpNode._prev = null; 872 //delRef(_head); 873 _head._next = null; 874 delRef(tmpNode); 875 } 876 delRef(tmpNode); // Remove old head ref 877 if (tmpNode !is null 878 && ((tmpNode._prev !is null) || (tmpNode._next !is null))) 879 { 880 // If it was a single node list, only delRef must be used 881 // in order to avoid premature/double freeing 882 destroyUnused(tmpNode); 883 } 884 } 885 886 debug(CollectionDList) void printRefCount(Node *sn = null) 887 //void printRefCount(Node *sn = null) 888 { 889 import std.stdio; 890 writefln("DList.printRefCount: begin"); 891 scope(exit) writefln("DList.printRefCount: end"); 892 893 Node *tmpNode; 894 if (sn is null) 895 tmpNode = _head; 896 else 897 tmpNode = sn; 898 899 while (tmpNode !is null && tmpNode._prev !is null) 900 { 901 // Rewind to the beginning of the list 902 tmpNode = tmpNode._prev; 903 } 904 while (tmpNode !is null) 905 { 906 writefln("DList.printRefCount: Node %s has ref count %s", 907 tmpNode._payload, *prefCount(tmpNode)); 908 tmpNode = tmpNode._next; 909 } 910 } 911 } 912 913 version (unittest) private @trusted void testInit(RCIAllocator allocator) 914 { 915 import std.algorithm.comparison : equal; 916 917 DList!int dl = DList!int(allocator); 918 assert(dl.empty); 919 assert(dl.isUnique); 920 int[] empty; 921 assert(equal(dl, empty)); 922 923 DList!int dl2 = DList!int(allocator, 1); 924 assert(equal(dl2, [1])); 925 926 DList!int dl3 = DList!int(allocator, 1, 2); 927 assert(equal(dl3, [1, 2])); 928 929 DList!int dl4 = DList!int(allocator, [1]); 930 assert(equal(dl4, [1])); 931 932 DList!int dl5 = DList!int(allocator, [1, 2]); 933 assert(equal(dl5, [1, 2])); 934 } 935 936 @trusted unittest 937 { 938 import std.conv; 939 SCAlloc statsCollectorAlloc; 940 auto _allocator = allocatorObject(&statsCollectorAlloc); 941 942 () @safe { 943 testInit(_allocator); 944 }(); 945 auto bytesUsed = statsCollectorAlloc.bytesUsed; 946 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 947 ~ to!string(bytesUsed) ~ " bytes"); 948 } 949 950 version (unittest) private @trusted void testInsert(RCIAllocator allocator) 951 { 952 import std.algorithm.comparison : equal; 953 import std.range.primitives : walkLength; 954 955 DList!int dl = DList!int(allocator, 1); 956 size_t pos = 0; 957 dl.insert(pos, 2); 958 assert(equal(dl, [2, 1])); 959 960 DList!int dl2 = DList!int(allocator, 1); 961 dl2.insert(pos, 2, 3); 962 assert(equal(dl2, [2, 3, 1])); 963 964 DList!int dl3 = DList!int(allocator, 1, 2); 965 dl3.insert(pos, 3); 966 assert(equal(dl3, [3, 1, 2])); 967 968 DList!int dl4 = DList!int(allocator, 1, 2); 969 dl4.insert(pos, 3, 4); 970 assert(equal(dl4, [3, 4, 1, 2])); 971 972 DList!int dl5 = DList!int(allocator, 1, 2); 973 dl5.popFront(); 974 dl5.insert(pos, 3); 975 assert(equal(dl5, [3, 2])); 976 dl5.popPrev(); 977 assert(equal(dl5, [1, 3, 2])); 978 979 DList!int dl6 = DList!int(allocator, 1, 2); 980 dl6.popFront(); 981 dl6.insert(pos, 3, 4); 982 assert(equal(dl6, [3, 4, 2])); 983 dl6.popPrev(); 984 assert(equal(dl6, [1, 3, 4, 2])); 985 dl6.insertBack(5); 986 assert(equal(dl6, [1, 3, 4, 2, 5])); 987 dl6.insertBack(6, 7); 988 assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7])); 989 dl6.insertBack([8]); 990 assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8])); 991 dl6.insertBack([9, 10]); 992 assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10])); 993 int[] empty; 994 dl6.insertBack(empty); 995 assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10])); 996 dl6.insert(pos, empty); 997 assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10])); 998 999 DList!int dl7 = DList!int(allocator, 1); 1000 assert(equal(dl7, [1])); 1001 dl7.insert(pos, 2); 1002 assert(equal(dl7, [2, 1])); 1003 pos = walkLength(dl7); 1004 dl7.insert(pos, 3); 1005 assert(equal(dl7, [2, 1, 3])); 1006 dl7.insert(pos, 4); 1007 assert(equal(dl7, [2, 1, 4, 3])); 1008 } 1009 1010 @trusted unittest 1011 { 1012 import std.conv; 1013 SCAlloc statsCollectorAlloc; 1014 auto _allocator = allocatorObject(&statsCollectorAlloc); 1015 1016 () @safe { 1017 testInsert(_allocator); 1018 }(); 1019 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1020 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1021 ~ to!string(bytesUsed) ~ " bytes"); 1022 } 1023 1024 version (unittest) private @trusted void testRemove(RCIAllocator allocator) 1025 { 1026 import std.algorithm.comparison : equal; 1027 1028 DList!int dl = DList!int(allocator, 1); 1029 size_t pos = 0; 1030 dl.remove(); 1031 assert(dl.empty); 1032 assert(dl.isUnique); 1033 assert(!dl._ouroborosAllocator.isNull); 1034 1035 dl.insert(pos, 2); 1036 auto dl2 = dl; 1037 auto dl3 = dl; 1038 assert(!dl.isUnique); 1039 1040 dl.popFront(); 1041 assert(dl.empty); 1042 assert(!dl._ouroborosAllocator.isNull); 1043 1044 dl2.popPrev(); 1045 assert(dl2.empty); 1046 assert(dl3.isUnique); 1047 1048 auto dl4 = dl3; 1049 assert(!dl3.isUnique); 1050 dl4.remove(); 1051 assert(dl4.empty); 1052 assert(!dl3.empty); 1053 assert(dl3.isUnique); 1054 } 1055 1056 @trusted unittest 1057 { 1058 import std.conv; 1059 SCAlloc statsCollectorAlloc; 1060 auto _allocator = allocatorObject(&statsCollectorAlloc); 1061 1062 () @safe { 1063 testRemove(_allocator); 1064 }(); 1065 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1066 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1067 ~ to!string(bytesUsed) ~ " bytes"); 1068 } 1069 1070 version (unittest) private @trusted void testCopyAndRef(RCIAllocator allocator) 1071 { 1072 import std.algorithm.comparison : equal; 1073 1074 auto dlFromList = DList!int(allocator, 1, 2, 3); 1075 auto dlFromRange = DList!int(allocator, dlFromList); 1076 assert(equal(dlFromList, dlFromRange)); 1077 1078 dlFromList.popFront(); 1079 assert(equal(dlFromList, [2, 3])); 1080 assert(equal(dlFromRange, [1, 2, 3])); 1081 1082 DList!int dlInsFromRange = DList!int(allocator); 1083 size_t pos = 0; 1084 dlInsFromRange.insert(pos, dlFromList); 1085 dlFromList.popFront(); 1086 assert(equal(dlFromList, [3])); 1087 assert(equal(dlInsFromRange, [2, 3])); 1088 1089 DList!int dlInsBackFromRange = DList!int(allocator); 1090 dlInsBackFromRange.insert(pos, dlFromList); 1091 dlFromList.popFront(); 1092 assert(dlFromList.empty); 1093 assert(equal(dlInsBackFromRange, [3])); 1094 1095 auto dlFromRef = dlInsFromRange; 1096 auto dlFromDup = dlInsFromRange.dup; 1097 assert(dlInsFromRange.front == 2); 1098 dlFromRef.front = 5; 1099 assert(dlInsFromRange.front == 5); 1100 assert(dlFromDup.front == 2); 1101 } 1102 1103 @trusted unittest 1104 { 1105 import std.conv; 1106 SCAlloc statsCollectorAlloc; 1107 auto _allocator = allocatorObject(&statsCollectorAlloc); 1108 1109 () @safe { 1110 testCopyAndRef(_allocator); 1111 }(); 1112 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1113 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1114 ~ to!string(bytesUsed) ~ " bytes"); 1115 } 1116 1117 @trusted unittest 1118 { 1119 import std.algorithm.comparison : equal; 1120 1121 SCAlloc statsCollectorAlloc; 1122 auto _allocator = allocatorObject(&statsCollectorAlloc); 1123 1124 DList!int dl = DList!int(_allocator, 1, 2, 3); 1125 auto before = statsCollectorAlloc.bytesUsed; 1126 { 1127 DList!int dl2 = dl; 1128 dl2.popFront(); 1129 assert(equal(dl2, [2, 3])); 1130 } 1131 assert(before == statsCollectorAlloc.bytesUsed); 1132 assert(equal(dl, [1, 2, 3])); 1133 dl.tail(); 1134 } 1135 1136 version(unittest) private @trusted void testImmutability(RCIAllocator allocator) 1137 { 1138 auto s = immutable DList!(int)(allocator, 1, 2, 3); 1139 auto s2 = s; 1140 auto s3 = s2.save(); 1141 1142 assert(s2.front == 1); 1143 static assert(!__traits(compiles, s2.front = 4)); 1144 static assert(!__traits(compiles, s2.popFront())); 1145 1146 auto s4 = s2.tail; 1147 assert(s4.front == 2); 1148 static assert(!__traits(compiles, s4 = s4.tail)); 1149 } 1150 1151 version(unittest) private @trusted void testConstness(RCIAllocator allocator) 1152 { 1153 auto s = const DList!(int)(allocator, 1, 2, 3); 1154 auto s2 = s; 1155 auto s3 = s2.save(); 1156 1157 assert(s2.front == 1); 1158 static assert(!__traits(compiles, s2.front = 4)); 1159 static assert(!__traits(compiles, s2.popFront())); 1160 1161 auto s4 = s2.tail; 1162 assert(s4.front == 2); 1163 static assert(!__traits(compiles, s4 = s4.tail)); 1164 } 1165 1166 @trusted unittest 1167 { 1168 import std.conv; 1169 SCAlloc statsCollectorAlloc; 1170 auto _allocator = allocatorObject(&statsCollectorAlloc); 1171 1172 () @safe { 1173 testConstness(_allocator); 1174 testImmutability(_allocator); 1175 }(); 1176 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1177 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1178 ~ to!string(bytesUsed) ~ " bytes"); 1179 } 1180 1181 version(unittest) private @trusted void testConcatAndAppend(RCIAllocator allocator) 1182 { 1183 import std.algorithm.comparison : equal; 1184 1185 auto dl = DList!(int)(allocator, 1, 2, 3); 1186 DList!(int) dl2 = DList!int(allocator); 1187 1188 auto dl3 = dl ~ dl2; 1189 assert(equal(dl3, [1, 2, 3])); 1190 1191 auto dl4 = dl3; 1192 dl3 = dl3 ~ 4; 1193 assert(equal(dl3, [1, 2, 3, 4])); 1194 dl3 = dl3 ~ [5]; 1195 assert(equal(dl3, [1, 2, 3, 4, 5])); 1196 assert(equal(dl4, [1, 2, 3])); 1197 1198 dl4 = dl3; 1199 dl3 ~= 6; 1200 assert(equal(dl3, [1, 2, 3, 4, 5, 6])); 1201 dl3 ~= [7]; 1202 assert(equal(dl3, [1, 2, 3, 4, 5, 6, 7])); 1203 assert(equal(dl4, [1, 2, 3, 4, 5, 6, 7])); 1204 1205 dl3 ~= dl3; 1206 assert(equal(dl3, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7])); 1207 assert(equal(dl4, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7])); 1208 1209 DList!int dl5 = DList!int(allocator); 1210 dl5 ~= [1, 2, 3]; 1211 assert(equal(dl5, [1, 2, 3])); 1212 } 1213 1214 @trusted unittest 1215 { 1216 import std.conv; 1217 SCAlloc statsCollectorAlloc; 1218 auto _allocator = allocatorObject(&statsCollectorAlloc); 1219 1220 () @safe { 1221 testConcatAndAppend(_allocator); 1222 }(); 1223 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1224 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1225 ~ to!string(bytesUsed) ~ " bytes"); 1226 } 1227 1228 version(unittest) private @trusted void testAssign(RCIAllocator allocator) 1229 { 1230 import std.algorithm.comparison : equal; 1231 1232 auto dl = DList!int(allocator, 1, 2, 3); 1233 assert(equal(dl, [1, 2, 3])); 1234 { 1235 auto dl2 = DList!int(allocator, 4, 5, 6); 1236 dl = dl2; 1237 assert(equal(dl, dl2)); 1238 } 1239 assert(equal(dl, [4, 5, 6])); 1240 dl.popPrev(); 1241 assert(dl.empty); 1242 } 1243 1244 @trusted unittest 1245 { 1246 import std.conv; 1247 SCAlloc statsCollectorAlloc; 1248 auto _allocator = allocatorObject(&statsCollectorAlloc); 1249 1250 () @safe { 1251 testAssign(_allocator); 1252 }(); 1253 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1254 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1255 ~ to!string(bytesUsed) ~ " bytes"); 1256 } 1257 1258 version(unittest) private @trusted void testWithStruct(RCIAllocator allocator) 1259 { 1260 import std.algorithm.comparison : equal; 1261 1262 auto list = DList!int(allocator, 1, 2, 3); 1263 { 1264 auto listOfLists = DList!(DList!int)(allocator, list); 1265 size_t pos = 0; 1266 assert(equal(listOfLists.front, [1, 2, 3])); 1267 listOfLists.front.front = 2; 1268 assert(equal(listOfLists.front, [2, 2, 3])); 1269 static assert(!__traits(compiles, listOfLists.insert(pos, 1))); 1270 1271 auto immListOfLists = immutable DList!(DList!int)(allocator, list); 1272 assert(immListOfLists.front.front == 2); 1273 static assert(!__traits(compiles, immListOfLists.front.front = 2)); 1274 } 1275 assert(equal(list, [2, 2, 3])); 1276 } 1277 1278 @trusted unittest 1279 { 1280 import std.conv; 1281 SCAlloc statsCollectorAlloc; 1282 auto _allocator = allocatorObject(&statsCollectorAlloc); 1283 1284 () @safe { 1285 testWithStruct(_allocator); 1286 }(); 1287 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1288 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1289 ~ to!string(bytesUsed) ~ " bytes"); 1290 } 1291 1292 version(unittest) private @trusted void testWithClass(RCIAllocator allocator) 1293 { 1294 class MyClass 1295 { 1296 int x; 1297 this(int x) { this.x = x; } 1298 } 1299 1300 MyClass c = new MyClass(10); 1301 { 1302 auto dl = DList!MyClass(allocator, c); 1303 assert(dl.front.x == 10); 1304 assert(dl.front is c); 1305 dl.front.x = 20; 1306 } 1307 assert(c.x == 20); 1308 } 1309 1310 @trusted unittest 1311 { 1312 import std.conv; 1313 SCAlloc statsCollectorAlloc; 1314 auto _allocator = allocatorObject(&statsCollectorAlloc); 1315 1316 () @safe { 1317 testWithClass(_allocator); 1318 }(); 1319 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1320 assert(bytesUsed == 0, "DList ref count leaks memory; leaked " 1321 ~ to!string(bytesUsed) ~ " bytes"); 1322 }