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