1 module memutils.vector; 2 3 import std.algorithm : swap, initializeAll; 4 import std.range : empty; 5 import std.traits; 6 import std.c.string; 7 import std.range : isInputRange, isForwardRange, isRandomAccessRange, ElementType, refRange, RefRange, hasLength; 8 import core.exception : RangeError; 9 import std.exception : enforce; 10 import memutils.allocators; 11 import memutils.helpers; 12 import memutils.utils; 13 import memutils.refcounted; 14 15 public alias SecureArray(T) = Array!(T, SecureMem); 16 17 template Array(T, ALLOC = ThreadMem) 18 if (!is (T == RefCounted!(Vector!(T, ALLOC), ALLOC))) 19 { 20 alias Array = RefCounted!(Vector!(T, ALLOC), ALLOC); 21 } 22 23 public alias SecureVector(T) = Vector!(T, SecureMem); 24 // TODO: Remove implicit string casting for Vector!ubyte! Encourage use of Vector!char [].idup instead. 25 26 /// An array that uses a custom allocator. 27 struct Vector(T, ALLOC = ThreadMem) 28 { 29 @disable this(this); 30 31 void opAssign()(auto ref Vector!(T, ALLOC) other) { 32 if (other.ptr !is this.ptr) 33 this.swap(other); 34 } 35 36 void opAssign()(ref T[] other) { 37 this[] = other[]; 38 } 39 40 // Payload cannot be copied 41 private struct Payload 42 { 43 size_t _capacity; 44 T[] _payload; 45 46 // Convenience constructor 47 this()(auto ref T[] p) 48 { 49 if (p.length == 0) return; 50 51 length = p.length; 52 53 static if (isImplicitlyConvertible!(T, T)) { 54 if (_payload.ptr is p.ptr && _payload.length == p.length) { 55 p = null; 56 } 57 else { 58 _payload[] = p[0 .. $]; // todo: use emplace? 59 } 60 } 61 else 62 { 63 memmove(_payload.ptr, p.ptr, T.sizeof*p.length); 64 } 65 } 66 67 // Destructor releases array memory 68 ~this() const 69 { 70 if (_capacity == 0 || _payload.ptr is null) 71 return; 72 T[] data = cast(T[]) _payload.ptr[0 .. _capacity]; 73 freeArray!(T, ALLOC)(data, _payload.length); // calls destructors and frees memory 74 } 75 76 void opAssign(Payload rhs) 77 { 78 assert(false); 79 } 80 81 // Duplicate data 82 // @property Payload dup() 83 // { 84 // Payload result; 85 // result._payload = _payload.dup; 86 // // Conservatively assume initial capacity == length 87 // result._capacity = result._payload.length; 88 // return result; 89 // } 90 91 // length 92 @property size_t length() const 93 { 94 return _payload.length; 95 } 96 97 // length 98 @property void length(size_t newLength) 99 { 100 if (length > 0 && length >= newLength) 101 { 102 // shorten 103 static if (hasElaborateDestructor!T) { 104 foreach (ref e; _payload.ptr[newLength .. _payload.length]) { 105 static if (is(T == struct) && !isPointer!T) // call destructors but not for indirections... 106 .destroy(e); 107 } 108 109 // Zero out unused capacity to prevent gc from seeing 110 // false pointers 111 static if (hasIndirections!T) 112 memset(_payload.ptr + newLength, 0, (_payload.length - newLength) * T.sizeof); 113 } 114 _payload = _payload.ptr[0 .. newLength]; 115 return; 116 } 117 118 if (newLength > 0) { 119 // enlarge 120 auto startEmplace = length; 121 reserve(newLength); 122 _payload = _payload.ptr[0 .. newLength]; 123 static if (!isImplicitlyConvertible!(T, T)) { 124 T t = T(); 125 foreach (size_t i; startEmplace .. length) 126 memmove(_payload.ptr + i, &t, T.sizeof); 127 128 } else 129 initializeAll(_payload.ptr[startEmplace .. length]); 130 } 131 } 132 133 // capacity 134 @property size_t capacity() const 135 { 136 return _capacity; 137 } 138 139 // reserve 140 void reserve(size_t elements) 141 { 142 if (elements <= capacity) return; 143 // TODO: allow vector to become smaller? 144 145 TRACE("Reserve ", length, " => ", elements, " elements."); 146 147 if (_capacity > 0) { 148 size_t len = _payload.length; 149 _payload = _payload.ptr[0 .. _capacity]; 150 _payload = reallocArray!(T, ALLOC)(_payload, elements)[0 .. len]; 151 } 152 else if (elements > 0) { 153 _payload = allocArray!(T, ALLOC)(elements)[0 .. _payload.length]; 154 } 155 _capacity = elements; 156 } 157 158 static if (is(T == char)) 159 size_t pushBack(Stuff)(Stuff stuff) 160 if (is(Stuff == char[]) || is(Stuff == string)) 161 { 162 TRACE("Vector.append @disabled this(this)"); 163 if (_capacity <= length + stuff.length) 164 { 165 reserve(1 + (length + stuff.length) * 3 / 2); 166 } 167 assert(capacity >= length + stuff.length && _payload.ptr); 168 169 memmove(_payload.ptr + _payload.length, stuff.ptr, stuff.length); 170 _payload = _payload.ptr[0 .. _payload.length + stuff.length]; 171 172 return 1; 173 } 174 175 size_t pushBack(Stuff)(auto ref Stuff stuff) 176 if (!isImplicitlyConvertible!(T, T) && is(T == Stuff)) 177 { 178 TRACE("Vector.append @disabled this(this)"); 179 if (_capacity == length) 180 { 181 reserve(1 + capacity * 3 / 2); 182 } 183 assert(capacity > length && _payload.ptr); 184 185 T* t = &stuff; 186 187 memmove(_payload.ptr + _payload.length, t, T.sizeof); 188 memset(t, 0, T.sizeof); 189 _payload = _payload.ptr[0 .. _payload.length + 1]; 190 191 return 1; 192 } 193 194 // Insert one item 195 size_t pushBack(Stuff)(auto ref Stuff stuff) 196 if (isImplicitlyConvertible!(T, T) && isImplicitlyConvertible!(Stuff, T)) 197 { 198 TRACE("Vector.append"); 199 logTrace("Capacity: ", _capacity, " length: ", length); 200 if (_capacity == length) 201 { 202 reserve(1 + capacity * 3 / 2); 203 } 204 assert(capacity > length && _payload.ptr, "Payload pointer " ~ _payload.ptr.to!string ~ "'s capacity: " ~ capacity.to!string ~ " must be more than " ~ length.to!string); 205 emplace(_payload.ptr + _payload.length, stuff); 206 _payload = _payload.ptr[0 .. _payload.length + 1]; 207 return 1; 208 } 209 210 /// Insert a range of items 211 size_t pushBack(Stuff)(auto ref Stuff stuff) 212 if (isInputRange!Stuff && (isImplicitlyConvertible!(ElementType!Stuff, T) || is(T == ElementType!Stuff))) 213 { 214 TRACE("Vector.append 2"); 215 static if (hasLength!Stuff) 216 { 217 immutable oldLength = length; 218 reserve(oldLength + stuff.length); 219 } 220 size_t result; 221 foreach (ref item; stuff) 222 { 223 pushBack(item); 224 ++result; 225 } 226 static if (hasLength!Stuff) 227 { 228 assert(length == oldLength + stuff.length); 229 } 230 return result; 231 } 232 } 233 234 private alias Data = Payload; 235 private Data _data; 236 237 this(size_t elms) { 238 resize(elms); 239 } 240 241 /** 242 Constructor taking a number of items 243 */ 244 this(U)(U[] values...) 245 if (isImplicitlyConvertible!(U, T)) 246 { 247 // TODO: This doesn't work with refcounted 248 _data = Data(cast(T[])values); 249 } 250 251 /** 252 Constructor taking an input range 253 */ 254 this(Stuff)(Stuff stuff) 255 if (isInputRange!Stuff && isImplicitlyConvertible!(UnConst!(ElementType!Stuff), T) && !is(Stuff == T[])) 256 { 257 insertBack(stuff); 258 } 259 260 /** 261 * Move Constructor 262 */ 263 this()(auto ref typeof(this) other) { 264 if (this.ptr !is other.ptr) 265 this.swap(other); 266 else { 267 typeof(this) ini; 268 other.swap(ini); 269 } 270 } 271 272 /** 273 Duplicates the container. The elements themselves are not transitively 274 duplicated. 275 276 Complexity: $(BIGOH n). 277 */ 278 @property Vector!(T, ALLOC) dup() const 279 { 280 static if (__traits(compiles, { T a; T b; a = b; } ())) { 281 auto ret = Vector!(T, ALLOC)(cast(T[])_data._payload); 282 return ret.move; 283 } 284 else static if (__traits(hasMember, T, "dup")) // Element is @disable this(this) but has dup() 285 { 286 Vector!(T, ALLOC) vec = Vector!(T, ALLOC)(length); 287 // swap each element with a duplicate 288 foreach (size_t i, ref el; _data._payload) { 289 T t = el.dup; 290 memmove(vec._data._payload.ptr + i, &t, T.sizeof); 291 memset(&t, 0, T.sizeof); 292 } 293 return vec.move(); 294 } else static assert(false, "Cannot dup() the element: " ~ T.stringof); 295 } 296 297 /// ditto 298 @property RefCounted!(Vector!(T, ALLOC), ALLOC) dupr() const 299 { 300 return RefCounted!(Vector!(T, ALLOC), ALLOC)(cast(T[])_data._payload); 301 } 302 303 void swap(ref Vector!(T, ALLOC) other) { 304 import std.algorithm : swap; 305 .swap(_data._payload, other._data._payload); 306 .swap(_data._capacity, other._data._capacity); 307 } 308 309 @property Vector!(T, ALLOC) move() { 310 return Vector!(T, ALLOC)(this); 311 } 312 313 /** 314 Property returning $(D true) if and only if the container has no 315 elements. 316 317 Complexity: $(BIGOH 1) 318 */ 319 @property bool empty() const 320 { 321 return _data._payload.empty; 322 } 323 324 /** 325 Returns the number of elements in the container. 326 327 Complexity: $(BIGOH 1). 328 */ 329 @property size_t length() const 330 { 331 return _data._payload.length; 332 } 333 334 /// ditto 335 size_t opDollar() const 336 { 337 return length; 338 } 339 340 @property T* ptr() inout { 341 return cast(T*) _data._payload.ptr; 342 } 343 344 @property T* end() inout { 345 return this.ptr + this.length; 346 } 347 348 /** 349 Returns the maximum number of elements the container can store without 350 (a) allocating memory, (b) invalidating iterators upon insertion. 351 352 Complexity: $(BIGOH 1) 353 */ 354 @property size_t capacity() const 355 { 356 return _data._capacity; 357 } 358 359 /* 360 @property auto range() { 361 return refRange(&_data._payload); 362 } 363 */ 364 365 /** 366 Ensures sufficient capacity to accommodate $(D e) elements. 367 368 Postcondition: $(D capacity >= e) 369 370 Complexity: $(BIGOH 1) 371 */ 372 void reserve(size_t elements) 373 { 374 _data.reserve(elements); 375 } 376 377 /** 378 Returns an array that can be translated to a range using ($D refRange). 379 380 Complexity: $(BIGOH 1) 381 */ 382 auto opSlice() inout 383 { 384 //static if (is(T[] == ubyte[])) 385 // return cast(string) _data._payload; 386 //else 387 return *cast(T[]*)&_data._payload; 388 } 389 390 /** 391 Returns an array of the container from index $(D a) up to (excluding) index $(D b). 392 393 Precondition: $(D a <= b && b <= length) 394 395 Complexity: $(BIGOH 1) 396 */ 397 T[] opSlice(size_t i, size_t j) const 398 { 399 version (assert) if (i > j || j > length) throw new RangeError(); 400 return (cast(T[])_data._payload)[i .. j]; 401 } 402 403 /** 404 Forward to $(D opSlice().front) and $(D opSlice().back), respectively. 405 406 Precondition: $(D !empty) 407 408 Complexity: $(BIGOH 1) 409 */ 410 @property ref T front() 411 { 412 return _data._payload[0]; 413 } 414 415 /// ditto 416 @property ref T back() 417 { 418 return _data._payload[$ - 1]; 419 } 420 421 /** 422 Indexing operators yield or modify the value at a specified index. 423 424 Precondition: $(D i < length) 425 426 Complexity: $(BIGOH 1) 427 */ 428 ref T opIndex(size_t i) const 429 { 430 return *cast(T*)&_data._payload[i]; 431 } 432 433 void opIndexAssign(U)(auto ref U val, size_t i) 434 { 435 static if (__traits(compiles, {_data._payload[i] = cast(T) val; }())) 436 _data._payload[i] = cast(T) val; 437 else { // swap 438 memmove(_data._payload.ptr + i, &val, U.sizeof); 439 memset(&val, 0, U.sizeof); 440 } 441 } 442 /** 443 Slicing operations execute an operation on an entire slice. 444 445 Precondition: $(D i < j && j < length) 446 447 Complexity: $(BIGOH slice.length) 448 */ 449 void opSliceAssign(Stuff)(auto ref Stuff value) 450 { 451 static if (isRandomAccessRange!Stuff) 452 { 453 _data.length = value.length; 454 _data._payload.ptr[0 .. value.length] = value[0 .. $]; 455 } else static if (is(UnConst!Stuff == Vector!(T, ALLOC))) { 456 _data.length = value._data.length; 457 _data._payload[] = value._data._payload[]; 458 } 459 else static if (isImplicitlyConvertible!(T, ElementType!Stuff)) { 460 _data.length = value.length; 461 _data._payload[] = cast(T[]) value; 462 } else static assert(false, "Can't convert " ~ Stuff.stringof ~ " to " ~ T.stringof ~ "[]"); 463 } 464 465 /// ditto 466 void opSliceAssign(Stuff)(Stuff value, size_t i, size_t j) 467 { 468 auto slice = _data._payload; 469 slice[i .. j] = value; 470 } 471 472 /// ditto 473 void opSliceUnary(string op)() 474 if(op == "++" || op == "--") 475 { 476 mixin(op~"_data._payload[];"); 477 } 478 479 /// ditto 480 void opSliceUnary(string op)(size_t i, size_t j) 481 if(op == "++" || op == "--") 482 { 483 mixin(op~"slice[i .. j];"); 484 } 485 486 /// ditto 487 void opSliceOpAssign(string op)(T value) 488 { 489 mixin("_data._payload[] "~op~"= value;"); 490 } 491 492 /// ditto 493 void opSliceOpAssign(string op)(T value, size_t i, size_t j) 494 { 495 mixin("slice[i .. j] "~op~"= value;"); 496 } 497 498 /** 499 Returns a new container that's the concatenation of $(D this) and its 500 argument. $(D opBinaryRight) is only defined if $(D Stuff) does not 501 define $(D opBinary). 502 503 Complexity: $(BIGOH n + m), where m is the number of elements in $(D 504 stuff) 505 */ 506 auto opBinary(string op, Stuff)(Stuff stuff) 507 if (op == "~") 508 { 509 TRACE("Appending stuff"); 510 RefCounted!(Vector!(T, ALLOC), ALLOC) result; 511 // @@@BUG@@ result ~= this[] doesn't work 512 auto r = this[]; 513 result ~= r; 514 assert(result.length == length); 515 result ~= stuff[]; 516 return result; 517 } 518 519 void opOpAssign(string op, U)(auto ref U input) 520 if (op == "^") 521 { 522 if (this.length < input.length) 523 this.resize(input.length); 524 525 pure static void xorBuf(T)(T* output, const(T)* input, size_t length) 526 { 527 while (length >= 8) 528 { 529 output[0 .. 8] ^= input[0 .. 8]; 530 531 output += 8; input += 8; length -= 8; 532 } 533 534 output[0 .. length] ^= input[0 .. length]; 535 } 536 537 xorBuf(this.ptr, input.ptr, input.length); 538 } 539 540 /** 541 Forwards to $(D pushBack(stuff)). 542 */ 543 void opOpAssign(string op, Stuff)(auto ref Stuff stuff) 544 if (op == "~") 545 { 546 static if (is (Stuff == RefCounted!(typeof(this)))) { 547 insertBack(cast(T[]) stuff[]); 548 } 549 else static if (is (Stuff == typeof(this))) { 550 insertBack(cast(T[]) stuff[]); 551 } 552 else 553 { 554 insertBack(stuff); 555 } 556 } 557 558 /** 559 Removes all contents from the container. The container decides how $(D 560 capacity) is affected. 561 562 Postcondition: $(D empty) 563 564 Complexity: $(BIGOH n) 565 */ 566 void clear() 567 { 568 TRACE("Vector.clear()"); 569 _data.length = 0; 570 } 571 572 573 /** 574 Sets the number of elements in the container to $(D newSize). If $(D 575 newSize) is greater than $(D length), the added elements are added to 576 unspecified positions in the container and initialized with $(D 577 T.init). 578 579 Complexity: $(BIGOH abs(n - newLength)) 580 581 Postcondition: $(D length == newLength) 582 */ 583 @property void length(size_t newLength) 584 { 585 _data.length = newLength; 586 } 587 588 void resize(size_t newLength) 589 { 590 this.length = newLength; 591 } 592 593 import std.traits : isNumeric; 594 595 int opCmp(Alloc)(auto const ref Vector!(T, Alloc) other) const 596 { 597 if (this[] == other[]) 598 return 0; 599 else if (this[] < other[]) 600 return -1; 601 else 602 return 0; 603 } 604 605 size_t pushBack(Stuff...)(Stuff stuff) 606 if (!isNumeric!Stuff || !is ( T == ubyte )) 607 { 608 return insertBack(stuff); 609 } 610 611 size_t pushBack(Stuff...)(Stuff stuff) 612 if (isNumeric!Stuff && is(T == ubyte)) 613 { 614 return insertBack(cast(T) stuff); 615 } 616 617 size_t insert(Stuff...)(Stuff stuff) { 618 return insertBack(stuff); 619 } 620 621 /** 622 Inserts $(D value) to the front or back of the container. $(D stuff) 623 can be a value convertible to $(D T) or a range of objects convertible 624 to $(D T). The stable version behaves the same, but guarantees that 625 ranges iterating over the container are never invalidated. 626 627 Returns: The number of elements inserted 628 629 Complexity: $(BIGOH m * log(n)), where $(D m) is the number of 630 elements in $(D stuff) 631 */ 632 size_t insertBack(Stuff)(auto ref Stuff stuff) 633 { 634 import std.traits : isImplicitlyConvertible; 635 static if (isImplicitlyConvertible!(Stuff, T[])) 636 return _data.pushBack(cast(T[])stuff); 637 else static if (isSomeString!(Stuff) && !isImplicitlyConvertible!(Stuff, T)) { 638 return _data.pushBack(cast(T[])stuff); 639 } 640 else static if (isSomeString!(Stuff) && isImplicitlyConvertible!(Stuff, T)) { 641 return _data.pushBack(cast(T)stuff); 642 } 643 else static if (isInputRange!(Stuff) && isImplicitlyConvertible!(ForeachType!Stuff, T)) { 644 size_t i; 645 foreach (ref el; stuff) { 646 _data.pushBack(el); 647 i++; 648 } 649 return i; 650 } 651 else 652 return _data.pushBack(cast(T) stuff); 653 } 654 655 /** 656 Removes the value at the back of the container. The stable version 657 behaves the same, but guarantees that ranges iterating over the 658 container are never invalidated. 659 660 Precondition: $(D !empty) 661 662 Complexity: $(BIGOH log(n)). 663 */ 664 void removeBack() 665 { 666 enforce(!empty); 667 static if (hasElaborateDestructor!T) 668 .destroy(_data._payload[$ - 1]); 669 670 _data._payload = _data._payload[0 .. $ - 1]; 671 } 672 673 void removeFront() { 674 675 enforce(!empty); 676 static if (hasElaborateDestructor!T) 677 .destroy(_data._payload[0]); 678 679 _data._payload = _data._payload[1 .. $]; 680 } 681 682 /** 683 Removes $(D howMany) values at the front or back of the 684 container. Unlike the unparameterized versions above, these functions 685 do not throw if they could not remove $(D howMany) elements. Instead, 686 if $(D howMany > n), all elements are removed. The returned value is 687 the effective number of elements removed. The stable version behaves 688 the same, but guarantees that ranges iterating over the container are 689 never invalidated. 690 691 Returns: The number of elements removed 692 693 Complexity: $(BIGOH howMany). 694 */ 695 size_t removeBack(size_t howMany) 696 { 697 if (howMany > length) howMany = length; 698 static if (hasElaborateDestructor!T) 699 foreach (ref e; _data._payload[$ - howMany .. $]) 700 .destroy(e); 701 702 _data._payload = _data._payload[0 .. $ - howMany]; 703 return howMany; 704 } 705 706 /** 707 Inserts $(D stuff) before position i. 708 709 Returns: The number of values inserted. 710 711 Complexity: $(BIGOH n + m), where $(D m) is the length of $(D stuff) 712 */ 713 void insertBefore(Stuff)(size_t i, Stuff stuff) 714 if (isImplicitlyConvertible!(Stuff, T)) 715 { 716 enforce(i <= length); 717 reserve(length + 1); 718 719 // Move elements over by one slot 720 memmove(_data._payload.ptr + i + 1, 721 _data._payload.ptr + i, 722 T.sizeof * (length - i)); 723 emplace(_data._payload.ptr + i, stuff); 724 _data._payload = _data._payload.ptr[0 .. _data._payload.length + 1]; 725 } 726 727 /// ditto 728 size_t insertBefore(Stuff)(size_t i, Stuff stuff) 729 if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) 730 { 731 enforce(i <= length); 732 static if (isForwardRange!Stuff) 733 { 734 // Can find the length in advance 735 auto extra = walkLength(stuff); 736 if (!extra) return 0; 737 reserve(length + extra); 738 // Move elements over by extra slots 739 memmove(_data._payload.ptr + i + extra, 740 _data._payload.ptr + i, 741 T.sizeof * (length - i)); 742 foreach (p; _data._payload.ptr + i .. 743 _data._payload.ptr + i + extra) 744 { 745 emplace(p, stuff.front); 746 stuff.popFront(); 747 } 748 _data._payload = _data._payload.ptr[0 .. _data._payload.length + extra]; 749 return extra; 750 } 751 else 752 { 753 enforce(_data); 754 immutable offset = i; 755 enforce(offset <= length); 756 auto result = pushBack(stuff); 757 bringToFront(this[offset .. length - result], 758 this[length - result .. length]); 759 return result; 760 } 761 } 762 763 /// ditto 764 size_t insertAfter(Stuff)(size_t i, Stuff stuff) 765 { 766 enforce(r._outer._data is _data); 767 // TODO: optimize 768 immutable offset = i; 769 enforce(offset <= length); 770 auto result = pushBack(stuff); 771 bringToFront(this[offset .. length - result], 772 this[length - result .. length]); 773 return result; 774 } 775 776 bool opEquals()(auto const ref RefCounted!(Vector!(T, ALLOC), ALLOC) other_) const { 777 import memutils.constants : logTrace; 778 if (other_.empty && empty()) 779 return true; 780 else if (other_.empty) 781 return false; 782 if (other_.length != length) 783 return false; 784 // TODO: use the true types 785 return _data._payload[0 .. length] == other_._data._payload[0 .. length]; 786 } 787 788 bool opEquals()(auto const ref Vector!(T, ALLOC) other_) const { 789 if (other_.empty && empty()) 790 return true; 791 else if (other_.empty) 792 return false; 793 if (other_.length != length) 794 return false; 795 796 return _data._payload[0 .. length] == other_._data._payload[0 .. length]; 797 } 798 799 bool opEquals()(auto const ref T[] other) { 800 logTrace("other: ", other, " this: ", _data._payload); 801 return other == _data._payload; 802 } 803 804 } 805 806 auto array(T)(T[] val) 807 { 808 return Array!(Unqual!T)(val); 809 } 810 811 auto vector(T)(T[] val) 812 { 813 return Vector!(Unqual!T)(val); 814 } 815 816 void TRACE(T...)(T t) { 817 //import std.stdio : writeln; 818 //writeln(t); 819 }