1 /****************************************************************************** 2 3 Copyright: 4 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 5 All rights reserved. 6 7 License: 8 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 9 Alternatively, this file may be distributed under the terms of the Tango 10 3-Clause BSD License (see LICENSE_BSD.txt for details). 11 12 *******************************************************************************/ 13 14 module ocean.util.container.queue.LinkedListQueue; 15 16 import ocean.meta.types.Qualifiers; 17 18 import core.memory; 19 import ocean.util.container.Container; 20 import ocean.core.Test; 21 import ocean.util.container.queue.model.ITypedQueue; 22 import ocean.core.Array; 23 public import ocean.util.container.queue.model.ITypedQueue: push, pop; 24 25 26 /****************************************************************************** 27 28 A typed-queue based on a linked list. (Internally, the queue is composed of 29 instances of a struct which contains a value (of type T) and a pointer to 30 next item.) 31 32 The linked list implementation allows the following extensions to ITypedQueue: 33 34 * The ability to find a specified value in the queue: contains(). 35 * The ability to remove one or all instances of a specified value from 36 the queue: remove(). 37 38 The items in the linked list are allocated and deallocated by Malloc allocation 39 manager, to avoid the GC from not so efficiently going over the linked list 40 (see explanation at LinkedListQueue.heap below). 41 42 Params: 43 T = Type of values stored in the linked list queue 44 gc_tracking_policy = defines the GC tracking policy for T (see 45 GCTrackingPolicy struct below) 46 47 *******************************************************************************/ 48 49 public class LinkedListQueue ( T, alias gc_tracking_policy = GCTrackingPolicy.refTypesOnly ) : 50 ITypedQueue!( T ) 51 { 52 /************************************************************************** 53 54 Should the values in queue be added to GC as roots, thus 55 preventing the GC from collecting these values. 56 57 Since the items in the queue are allocated by the Malloc allocation 58 manager, they are not scanned by the GC. This means that whatever the items 59 reference - the values, are not considered as being referenced by anything 60 (unless the values are referenced by some other "GC scanned" object). 61 This will result in GC collecting these values, which will have very bad 62 ramifications as we're still referencing them in the queue. 63 64 So we add the values to GC as roots, thus preventing the GC from collecting 65 them, depending on the value of 'root_values' 66 67 ***************************************************************************/ 68 69 protected static bool root_values; 70 71 /************************************************************************** 72 73 Static constructor. 74 75 Sets 'root_values' according to the gc_tracking_policy. 76 77 ***************************************************************************/ 78 79 public static this ( ) 80 { 81 typeof(this).root_values = gc_tracking_policy!(T); 82 } 83 84 85 /************************************************************************** 86 87 Type of items in queue - a struct of value and pointer to next item 88 89 ***************************************************************************/ 90 91 protected struct QueueItem 92 { 93 /********************************************************************** 94 95 Pointer to next item in queue 96 97 ***********************************************************************/ 98 99 public QueueItem* next; 100 101 102 /********************************************************************** 103 104 The value stored in this item 105 106 ***********************************************************************/ 107 108 public T value; 109 110 111 /********************************************************************** 112 113 Finds a QueueItem which contains the given value 114 115 Params: 116 find_value = value to find 117 118 Returns: 119 pointer to the first QueueItem which contains the value, null if 120 not found 121 122 ***********************************************************************/ 123 124 public QueueItem* find ( T find_value ) 125 { 126 for ( auto p = &this; p; p = p.next ) 127 if ( find_value == p.value ) 128 return p; 129 return null; 130 } 131 } 132 133 134 /************************************************************************** 135 136 Number of items in the queue 137 138 ***************************************************************************/ 139 140 protected size_t count; 141 142 143 /************************************************************************** 144 145 Pointer to first (and oldest) item in queue 146 147 ***************************************************************************/ 148 149 protected QueueItem* head; 150 151 152 /************************************************************************** 153 154 Pointer to last item in queue 155 156 ***************************************************************************/ 157 158 protected QueueItem* tail; 159 160 161 /************************************************************************** 162 163 Malloc allocation manager, to allocate and deallocate items in queue. 164 We use the malloc allocation manager as to keep the linked list away 165 from the reach of the GC, for efficiency reasons. 166 The reason for this is: 167 The GC collector scans the memory in order to identify the root objects, 168 and then collect them. The scanning process, roughly speaking, is done in 169 such a way that each time the GC detects a new address it runs another scan. 170 On the entire memory. After each scan the amount of detected addresses is 171 increased until there are mo more undetected addresses, and the scanning 172 stops. 173 The problem with linked list is that the GC will detect a single item in 174 each scan. So GC will run as many scans as the number of items in the 175 linked list. And again, each scan goes over the entire memory. 176 177 ***************************************************************************/ 178 179 protected Container.Malloc!(QueueItem) heap; 180 181 182 /************************************************************************** 183 184 Validate the following: 185 186 1. If length is > 0 then head must not be null 187 2. If length is 1, head and tail must point to the same item 188 3. If length is > 1, head and tail must not point to the same item 189 4. If it's empty then head is null 190 191 ***************************************************************************/ 192 193 invariant ( ) 194 { 195 if ( this.count ) 196 { 197 assert ( this.head, "LinkedListQueue length isn't 0, it's head should not be null!" ); 198 199 if ( this.count == 1 ) 200 assert ( this.head is this.tail, "LinkedListQueue length is 1, it's head and tail should point to the same item!" ); 201 202 if ( this.count > 1 ) 203 assert ( this.head !is this.tail, "LinkedListQueue length is > 1, it's head and tail should not point to the same item!" ); 204 } 205 else 206 { 207 assert ( !this.head, "LinkedListQueue length is 0, it's head should be null!" ); 208 } 209 } 210 211 212 /************************************************************************** 213 214 Returns: 215 true if queue is empty, false otherwise 216 217 ***************************************************************************/ 218 219 public override bool empty ( ) 220 { 221 return this.count == 0; 222 } 223 224 225 /************************************************************************** 226 227 Returns: 228 number of items in the queue 229 230 ***************************************************************************/ 231 232 public override size_t length ( ) 233 { 234 return this.count; 235 } 236 237 238 /************************************************************************** 239 240 Removes all items from the queue in O(n) 241 242 ***************************************************************************/ 243 244 public override void clear ( ) 245 { 246 while ( this.count ) 247 { 248 this.discardTop(); 249 } 250 } 251 252 253 /************************************************************************** 254 255 Pushes an item to the queue. The caller should set the returned item as 256 desired 257 258 Returns: 259 Pointer to the newly pushed item 260 261 ***************************************************************************/ 262 263 public override T* push ( ) 264 { 265 auto new_element = this.newItem(); 266 267 // Just created first item in queue, both head and tail 268 // should point to it 269 if ( this.count == 0 ) 270 { 271 this.head = new_element; 272 this.tail = this.head; 273 } 274 else // Update tail to point to new item 275 { 276 this.tail.next = new_element; 277 this.tail = this.tail.next; 278 } 279 280 this.count++; 281 282 return &new_element.value; 283 } 284 285 286 /************************************************************************** 287 288 Discards the item at the top of the queue. 289 290 ***************************************************************************/ 291 292 public override void discardTop ( ) 293 { 294 if ( this.count == 0 ) 295 return; 296 297 QueueItem* head_to_be_removed = this.head; 298 this.head = this.head.next; 299 this.count--; 300 301 this.deleteItem(head_to_be_removed); 302 } 303 304 305 /************************************************************************** 306 307 Returns: 308 A pointer to the item at the top of the queue, null if the queue is 309 empty 310 311 ***************************************************************************/ 312 313 public override T* top ( ) 314 { 315 if ( this.count == 0 ) 316 return null; 317 318 return &this.head.value; 319 } 320 321 322 /************************************************************************** 323 324 Returns: 325 A pointer to the item at the bottom of the queue, null if the queue 326 is empty 327 328 ***************************************************************************/ 329 330 public T* bottom ( ) 331 { 332 if ( this.count == 0 ) 333 return null; 334 335 return &this.tail.value; 336 } 337 338 339 /************************************************************************** 340 341 Checks whether a value exists in queue in O(n). 342 343 Params: 344 value = value to find 345 346 Returns: 347 true if value exists, false otherwise 348 349 ***************************************************************************/ 350 351 public bool contains ( T value ) 352 { 353 if ( this.count == 0 ) 354 return false; 355 356 return this.head.find(value) !is null; 357 } 358 359 360 /************************************************************************** 361 362 Removes a value from queue, in O(n) 363 364 Params: 365 value = value to remove 366 all = if true then remove all values equal to value, othetrwise only 367 first matching value will be removed 368 369 Returns: 370 number of removed values 371 372 ***************************************************************************/ 373 374 public size_t remove ( T value, bool all = false ) 375 { 376 // iterate over items in queue 377 QueueItem* iterator = this.head; 378 379 // keep pointer to previous item 380 QueueItem* previous = iterator; 381 382 // keep count of how many we removed 383 auto old_count = this.count; 384 385 while( iterator ) 386 { 387 auto next = iterator.next; 388 389 if ( iterator.value == value ) 390 { 391 bool is_tail; 392 bool is_head; 393 394 // head item is to be removed 395 // update head to point to next item 396 if ( iterator is this.head ) 397 { 398 this.head = next; 399 previous = next; 400 is_head = true; 401 } 402 403 // tail item is to be removed 404 // update tail to point to previous item 405 if ( iterator is this.tail ) 406 { 407 this.tail = previous; 408 is_tail = true; 409 410 if ( !is_head ) 411 this.tail.next = null; 412 } 413 414 // "in the middle" item is to be removed 415 // connect the items before and after the removed one 416 if ( !is_tail && !is_head ) 417 { 418 previous.next = next; 419 } 420 421 // remove 422 this.count--; 423 this.deleteItem(iterator); 424 425 // should we look for more matched values? have we reached the end? 426 if ( !all || this.count == 0 ) 427 break; 428 } 429 else 430 { 431 previous = iterator; 432 } 433 434 iterator = next; 435 } 436 437 return old_count - this.count; 438 } 439 440 441 /*************************************************************************** 442 443 Walk through the linked list 444 445 Params: 446 dg = delegate to be called for each value in the list 447 448 Returns: 449 the return value of the last call to the delegate, 450 or zero if no call happened (no elements to walk over) 451 452 ***************************************************************************/ 453 454 public int opApply ( scope int delegate ( ref T value ) dg ) 455 { 456 int result; 457 458 if (!this.empty()) 459 { 460 auto current = this.head; 461 462 do 463 { 464 result = dg(current.value); 465 466 if (result) 467 break; 468 469 current = current.next; 470 } 471 while (current !is null); 472 } 473 474 return result; 475 } 476 477 /************************************************************************** 478 479 Returns: 480 true if the elements of this class are tracked by the GC, false 481 otherwise. 482 483 ***************************************************************************/ 484 485 public static bool isRootedValues () 486 { 487 return typeof(this).root_values; 488 } 489 490 /************************************************************************** 491 492 Deallocate an item. Called upon item removal from queue. 493 494 Params: 495 to_delete = pointer to item in queue to delete 496 497 ***************************************************************************/ 498 499 protected void deleteItem ( QueueItem* to_delete ) 500 { 501 if ( root_values ) 502 GC.removeRange(&to_delete.value); 503 504 this.heap.collect(to_delete); 505 } 506 507 508 /************************************************************************** 509 510 Allocate an item. Called upon item addition to queue. 511 512 Returns: 513 pointer to newly allocated item 514 515 ***************************************************************************/ 516 517 protected QueueItem* newItem ( ) 518 { 519 auto new_element = this.heap.allocate(); 520 new_element.next = null; 521 522 // By adding the value to GC as root we're preventing GC from 523 // collecting it 524 if ( root_values ) 525 GC.addRange(&new_element.value, T.sizeof); 526 527 return new_element; 528 } 529 } 530 531 /****************************************************************************** 532 533 A wrapper around common used policies for adding data allocated by the 534 LinkedListQueue to the GC scan range. 535 536 *******************************************************************************/ 537 538 public struct GCTrackingPolicy 539 { 540 /************************************************************************** 541 542 Checks T parameter and decides accordingly whether T items allocated 543 by the LinkedListQueue will be added to the GC scan range. 544 545 Allocated T elements will be added to the GC scan range only if T is 546 (or contains) reference types (e.g a class, an array or a pointer). 547 Otherwise allocated T elements won't be added to the GC scan range. 548 549 Template params: 550 T = the type to be used 551 552 Returns: 553 true T contains reference items, returns false otherwise. 554 555 ***************************************************************************/ 556 557 static bool refTypesOnly (T) () 558 { 559 return (typeid(T).flags() & 1) == 1; 560 } 561 562 /************************************************************************** 563 564 Disable addition of allocated items to the GC scan range regardless of 565 the used type. 566 567 Template params: 568 T = the type to be used 569 570 Returns: 571 false regardless of the used type 572 573 ***************************************************************************/ 574 575 static bool never (T) () 576 { 577 return false; 578 } 579 580 /************************************************************************** 581 582 Enable addition of allocated items to the GC scan range regardless of 583 the used type. 584 585 Template params: 586 T = the type to be used 587 588 Returns: 589 true regardless of the used type 590 591 ***************************************************************************/ 592 593 static bool always (T) () 594 { 595 return true; 596 } 597 } 598 599 /****************************************************************************** 600 601 Test walking over the linked list 602 603 *******************************************************************************/ 604 605 unittest 606 { 607 auto list = new LinkedListQueue!(int); 608 609 bool iterated = false; 610 foreach (item; list) iterated = true; 611 test!("==")(iterated, false); 612 613 *list.push = 0; 614 *list.push = 1; 615 *list.push = 2; 616 617 size_t count; 618 size_t idx; 619 foreach (item; list) 620 { 621 test!("==")(item, idx++); 622 ++count; 623 } 624 625 test!("==")(count, 3); 626 } 627 628 629 /****************************************************************************** 630 631 Test root_values is determined correctly 632 633 *******************************************************************************/ 634 635 unittest 636 { 637 struct emptyStruct { } 638 struct someStruct { int[] array; } 639 class someClass { } 640 641 // int 642 test((new LinkedListQueue!(int)).root_values == false, "'int' should not be added as GC root"); 643 test((new LinkedListQueue!(int, GCTrackingPolicy.refTypesOnly)).root_values == false, "'int' should not be added as GC root (2)"); 644 645 // empty struct 646 test((new LinkedListQueue!(emptyStruct)).root_values == false, "An empty struct should not be added as GC root"); 647 test((new LinkedListQueue!(emptyStruct, GCTrackingPolicy.refTypesOnly)).root_values == false, "An empty struct should not be added as GC root (2)"); 648 649 // struct that points to GC memory 650 test((new LinkedListQueue!(someStruct)).root_values, "A struct containing an array should be added as GC root!"); 651 test((new LinkedListQueue!(someStruct, GCTrackingPolicy.refTypesOnly)).root_values, "A struct containing an array should be added as GC root (2)!"); 652 653 // pointer to struct 654 test((new LinkedListQueue!(emptyStruct*)).root_values, "A pointer to a struct should be added as GC root!"); 655 test((new LinkedListQueue!(emptyStruct*, GCTrackingPolicy.refTypesOnly)).root_values, "A pointer to a struct should be added as GC root (2)!"); 656 657 // class 658 test((new LinkedListQueue!(someClass)).root_values, "A class should be added as GC root!"); 659 test((new LinkedListQueue!(someClass, GCTrackingPolicy.refTypesOnly)).root_values, "A class should be added as GC root (2)!"); 660 661 // Test forcing a GC policy 662 test((new LinkedListQueue!(someClass, GCTrackingPolicy.never)).root_values == false, "GC scanning should be forcefully disabled for class!"); 663 test((new LinkedListQueue!(int, GCTrackingPolicy.always)).root_values, "GC scanning should be forcefully enabled for ints!"); 664 test((new LinkedListQueue!(emptyStruct, GCTrackingPolicy.always)).root_values, "GC scanning should be forcefully enabled for empty struct!"); 665 } 666 667 668 /****************************************************************************** 669 670 Test ITypedQueue methods 671 672 *******************************************************************************/ 673 674 unittest 675 { 676 class JustSomeClass 677 { 678 protected int just_some_int; 679 680 public this ( int set_just_some_int ) { this.just_some_int = set_just_some_int; } 681 682 override public equals_t opEquals ( Object _another ) 683 { 684 auto another = cast(JustSomeClass) _another; 685 if (another is null) 686 return false; 687 return this.just_some_int == another.just_some_int; 688 } 689 } 690 691 // T = int 692 LinkedListQueue!(int) integersList = new LinkedListQueue!(int)(); 693 694 // T = JustSomeClass 695 LinkedListQueue!(JustSomeClass) classesList = new LinkedListQueue!(JustSomeClass)(); 696 697 static immutable int size = 100; 698 int[] int_array; 699 JustSomeClass[] class_array; 700 701 for(int i = 0; i < size; i++) 702 { 703 int_array ~= i; 704 class_array ~= new JustSomeClass(i); 705 } 706 707 testInterfaceMethods(integersList, int_array); 708 testInterfaceMethods(classesList, class_array); 709 } 710 711 712 /****************************************************************************** 713 714 Test LinkedListQueue methods 715 716 *******************************************************************************/ 717 718 unittest 719 { 720 /************************************************************************** 721 722 A class to test LinkedListQueue. 723 724 The 'invariant' section validates the exact content of the 725 LinkedListQueue. And in case of validation failure the name of the 726 particular test is printed. 727 728 ***************************************************************************/ 729 730 class TestQueue 731 { 732 /********************************************************************** 733 734 The tested LinkedListQueue 735 736 ***********************************************************************/ 737 738 protected LinkedListQueue!(int) int_queue; 739 740 741 /********************************************************************** 742 743 The values expected to be in intQueue, and their expected order 744 745 ***********************************************************************/ 746 747 protected int[] expected_values; 748 749 750 /********************************************************************** 751 752 The name of the particular test, to be printed in case of test 753 failure 754 755 ***********************************************************************/ 756 757 protected istring name; 758 759 760 /********************************************************************** 761 762 Constructor 763 764 Params: 765 in_name = The name of the particular test 766 767 ***********************************************************************/ 768 769 public this (istring in_name) 770 { 771 this.int_queue = new LinkedListQueue!(int)(); 772 this.name = in_name; 773 } 774 775 776 /********************************************************************** 777 778 Invarinat to validate the contents in int_queue and expected_values 779 are the same. 780 781 ***********************************************************************/ 782 783 invariant ( ) 784 { 785 auto _this = cast(TestQueue) this; 786 787 test( 788 _this.int_queue.length() == _this.expected_values.length, 789 name ~ ": length should be the same" 790 ); 791 792 if ( _this.expected_values.length == 0 ) 793 { 794 test(_this.int_queue.empty(), name ~ ": queue should be mepty"); 795 test(_this.int_queue.top() == null, 796 name ~ ": queue is empty. Top should have returned null"); 797 test(_this.int_queue.bottom() == null, 798 name ~ ": queue is empty. Bottom should have returned null"); 799 } 800 801 LinkedListQueue!(int).QueueItem* iterator = _this.int_queue.head; 802 803 // compare all values 804 foreach( value; _this.expected_values) 805 { 806 test(iterator.value == value, 807 name ~ ": value incorrect"); 808 iterator = iterator.next; 809 } 810 } 811 812 813 /********************************************************************** 814 815 Push values into LinkedListQueue. 816 817 Params: 818 values = values to push 819 820 ***********************************************************************/ 821 822 public void push ( int[] values ) 823 { 824 foreach( value; values ) 825 { 826 .push(this.int_queue, value); 827 this.expected_values ~= value; 828 test!("==")(*this.int_queue.bottom, value); 829 } 830 } 831 832 833 /********************************************************************** 834 835 Removes a value from LinkedListQueue. 836 837 Params: 838 value = value to remove 839 expected_to_remove = number of items expected to be removed 840 all = should all instances of value be removed 841 842 ***********************************************************************/ 843 844 public void remove ( int value, int expected_to_remove = 1, bool all = true ) 845 { 846 bool continue_remove = true; 847 848 if ( expected_to_remove ) 849 test(this.int_queue.contains(value), 850 name ~ ": value should have been found in LinkedListQueue"); 851 852 test(this.int_queue.remove(value, all) == expected_to_remove, 853 name ~ ": Removed wrong number of items from LinkedListQueue"); 854 test(!this.int_queue.contains(value), 855 name ~ ": value should NOT have been found in LinkedListQueue"); 856 857 if ( all ) 858 { 859 int[] result, match; 860 match ~= value; 861 this.expected_values = ocean.core.array.Transformation.remove( 862 this.expected_values, match, result); 863 } 864 else 865 { 866 foreach ( i, item; this.expected_values ) 867 { 868 if ( item == value ) 869 { 870 removeShift(this.expected_values, i); 871 break; 872 } 873 } 874 } 875 } 876 } 877 878 879 { 880 // [1] 881 TestQueue testQueue = new TestQueue("Test 'remove' from LinkedListQueue with a single item"); 882 testQueue.push([1]); 883 testQueue.remove(1); 884 } 885 { 886 // [3] 4 887 TestQueue testQueue = new TestQueue("Test 'remove' first item from LinkedListQueue"); 888 testQueue.push([3, 4]); 889 testQueue.remove(3); 890 } 891 { 892 // 5 [6] 893 TestQueue testQueue = new TestQueue("Test 'remove' last item from LinkedListQueue"); 894 testQueue.push([5, 6]); 895 testQueue.remove(6); 896 } 897 { 898 // [7] [8] 899 TestQueue testQueue = new TestQueue("Test 'remove' all items from LinkedListQueue"); 900 testQueue.push([7, 8]); 901 testQueue.remove(7); 902 testQueue.remove(8); 903 } 904 { 905 // 9 [10] 11 906 TestQueue testQueue = new TestQueue("Test 'remove' middle item from LinkedListQueue"); 907 testQueue.push([9, 10, 11]); 908 testQueue.remove(10); 909 } 910 { 911 // 12 [13] [14] 15 912 TestQueue testQueue = new TestQueue("Test 'remove' several middle items from LinkedListQueue"); 913 testQueue.push([12, 13, 14, 15]); 914 testQueue.remove(13); 915 testQueue.remove(14); 916 } 917 { 918 // [16] 17 18 [19] 919 TestQueue testQueue = new TestQueue("Test 'remove' first and last items from LinkedListQueue"); 920 testQueue.push([16, 17, 18, 19]); 921 testQueue.remove(16); 922 testQueue.remove(19); 923 } 924 { 925 // [20] 21 21 [20] 926 TestQueue testQueue = new TestQueue("Test 'remove' repeating values from LinkedListQueue"); 927 testQueue.push([20, 21, 21, 20]); 928 testQueue.remove(20, 2); 929 testQueue.remove(20, 0); 930 } 931 { 932 // remove from an empty queue 933 TestQueue testQueue = new TestQueue("Test 'remove' on an empty LinkedListQueue"); 934 testQueue.remove(1, 0); 935 } 936 }