1 module automem.unique_array; 2 3 import automem.traits: isAllocator; 4 import automem.test_utils: TestUtils; 5 import std.experimental.allocator: theAllocator; 6 7 version(unittest) { 8 import unit_threaded; 9 import test_allocator: TestAllocator; 10 } 11 12 mixin TestUtils; 13 14 struct UniqueArray(Type, Allocator = typeof(theAllocator)) if(isAllocator!Allocator) { 15 16 import std.traits: hasMember, isScalarType; 17 import std.range: isInputRange; 18 19 enum isSingleton = hasMember!(Allocator, "instance"); 20 enum isTheAllocator = is(Allocator == typeof(theAllocator)); 21 enum isGlobal = isSingleton || isTheAllocator; 22 23 static if(isGlobal) { 24 25 /** 26 The allocator is global, so no need to pass it in to the 27 constructor 28 */ 29 30 this(size_t size) { 31 makeObjects(size); 32 } 33 34 this(size_t size, Type init) { 35 makeObjects(size, init); 36 } 37 38 this(R)(R range) if(isInputRange!R) { 39 makeObjects(range); 40 } 41 42 43 } else { 44 45 /** 46 Non-singleton allocator, must be passed in 47 */ 48 49 this(Allocator allocator) { 50 _allocator = allocator; 51 } 52 53 this(Allocator allocator, size_t size) { 54 _allocator = allocator; 55 makeObjects(size); 56 } 57 58 this(Allocator allocator, size_t size, Type init) { 59 _allocator = allocator; 60 makeObjects(size, init); 61 } 62 63 this(R)(Allocator allocator, R range) if(isInputRange!R) { 64 _allocator = allocator; 65 makeObjects(range); 66 } 67 } 68 69 70 this(T)(UniqueArray!(T, Allocator) other) if(is(T: Type[])) { 71 moveFrom(other); 72 } 73 74 @disable this(this); 75 76 ~this() { 77 deleteObjects; 78 } 79 80 /** 81 Releases ownership and transfers it to the returned 82 Unique object. 83 */ 84 UniqueArray unique() { 85 import std.algorithm: move; 86 UniqueArray u; 87 move(this, u); 88 assert(_objects.length == 0 && _objects.ptr is null); 89 return u; 90 } 91 alias move = unique; 92 93 /** 94 "Truthiness" cast 95 */ 96 bool opCast(T)() const if(is(T == bool)) { 97 return _objects.ptr !is null; 98 } 99 100 void opAssign(T)(UniqueArray!(T, Allocator) other) if(is(T: Type[])) { 101 deleteObject; 102 moveFrom(other); 103 } 104 105 ref inout(Type) opIndex(long i) inout nothrow { 106 return _objects[i]; 107 } 108 109 const(Type)[] opSlice(long i, long j) const nothrow { 110 return _objects[i .. j]; 111 } 112 113 const(Type)[] opSlice() const nothrow { 114 return _objects[0 .. length]; 115 } 116 117 long opDollar() const nothrow { 118 return length; 119 } 120 121 @property long length() const nothrow { 122 return _length; 123 } 124 125 @property void length(long size) { 126 127 import std.experimental.allocator: expandArray, shrinkArray; 128 129 if(_objects is null) { 130 makeObjects(size); 131 } else if(size == length) { 132 return; 133 } else if(size <= _capacity && size > length) { 134 foreach(ref obj; _objects[_length .. size]) 135 obj = obj.init; 136 _length = size; 137 } else if(size < length) { 138 _length = size; 139 } else { 140 if(size > length) { 141 _allocator.expandArray(_objects, size - length); 142 setLength; 143 } else { 144 _allocator.shrinkArray(_objects, length - size); 145 setLength; 146 } 147 } 148 } 149 150 /** 151 Dereference. const since this otherwise could be used to try 152 and append to the array, which would not be nice 153 */ 154 const(Type[]) opUnary(string s)() const if(s == "*") { 155 return this[]; 156 } 157 158 UniqueArray opBinary(string s)(UniqueArray other) if(s == "~") { 159 this ~= other.unique; 160 return this.unique; 161 } 162 163 void opOpAssign(string op)(Type other) if(op == "~") { 164 length(length + 1); 165 _objects[$ - 1] = other; 166 } 167 168 void opOpAssign(string op)(Type[] other) if(op == "~") { 169 const originalLength = length; 170 length(originalLength + other.length); 171 _objects[originalLength .. length] = other[]; 172 } 173 174 void opOpAssign(string op)(UniqueArray other) if(op == "~") { 175 this ~= other._objects; 176 } 177 178 void opAssign(Type[] other) { 179 length = other.length; 180 _objects[0 .. length] = other[0 .. length]; 181 } 182 183 /** 184 Reserves memory to prevent too many allocations 185 */ 186 void reserve(in long size) { 187 import std.experimental.allocator: expandArray; 188 189 if(_objects is null) { 190 const oldLength = length; 191 makeObjects(size); // length = capacity here 192 _length = oldLength; 193 return; 194 } 195 196 if(size < _capacity) { 197 if(size < _length) _length = size; 198 return; 199 } 200 201 _capacity = size; 202 _allocator.expandArray(_objects, _capacity); 203 } 204 205 /** 206 Returns a pointer to the underlying data. @system 207 */ 208 inout(Type)* ptr() inout { 209 return _objects.ptr; 210 } 211 212 static if(isGlobal) { 213 UniqueArray dup() const { 214 return UniqueArray(_objects); 215 } 216 } else static if(isScalarType!Allocator && is(typeof(() { auto a = Allocator.init; auto b = a; }))) { 217 UniqueArray dup() const { 218 return UniqueArray(_allocator, _objects); 219 } 220 } else { 221 UniqueArray dup() { 222 return UniqueArray(_allocator, _objects); 223 } 224 } 225 226 private: 227 228 Type[] _objects; 229 long _length; 230 long _capacity; 231 232 static if(isSingleton) 233 alias _allocator = Allocator.instance; 234 else static if(isTheAllocator) 235 alias _allocator = theAllocator; 236 else 237 Allocator _allocator; 238 239 void makeObjects(size_t size) { 240 import std.experimental.allocator: makeArray; 241 _objects = _allocator.makeArray!Type(size); 242 setLength; 243 } 244 245 void makeObjects(size_t size, Type init) { 246 import std.experimental.allocator: makeArray; 247 _objects = _allocator.makeArray!Type(size, init); 248 setLength; 249 250 } 251 252 void makeObjects(R)(R range) if(isInputRange!R) { 253 import std.experimental.allocator: makeArray; 254 _objects = _allocator.makeArray!Type(range); 255 setLength; 256 } 257 258 void setLength() { 259 _capacity = _length = _objects.length; 260 } 261 262 void deleteObjects() { 263 import std.experimental.allocator: dispose; 264 import std.traits: isPointer; 265 266 static if(isPointer!Allocator) 267 assert((_objects.length == 0 && _objects.ptr is null) || _allocator !is null); 268 269 if(_objects.ptr !is null) _allocator.dispose(_objects); 270 _length = 0; 271 } 272 273 void moveFrom(T)(ref UniqueArray!(T, Allocator) other) if(is(T: Type[])) { 274 import std.algorithm: swap; 275 _object = other._object; 276 other._object = null; 277 278 swap(_length, other._length); 279 swap(_capacity, other._capacity); 280 281 static if(!isGlobal) { 282 import std.algorithm: move; 283 _allocator = other._allocator.move; 284 } 285 } 286 } 287 288 289 @("default TestAllocator") 290 @system unittest { 291 defaultTest!TestAllocator; 292 } 293 294 295 @("default Mallocator") 296 @system unittest { 297 import std.experimental.allocator.mallocator: Mallocator; 298 defaultTest!Mallocator; 299 } 300 301 version(unittest) { 302 303 void defaultTest(T)() { 304 import std.algorithm: move; 305 306 mixin AllocatorAlias!T; 307 308 auto ptr = makeUniqueArray!(Struct, Allocator)(allocator, 3); 309 ptr.length.shouldEqual(3); 310 311 ptr[2].twice.shouldEqual(0); 312 ptr[2] = Struct(5); 313 ptr[2].twice.shouldEqual(10); 314 315 ptr[1..$].shouldEqual([Struct(), Struct(5)]); 316 317 typeof(ptr) ptr2 = ptr.move; 318 319 ptr.length.shouldEqual(0); 320 (cast(bool)ptr).shouldBeFalse; 321 ptr2.length.shouldEqual(3); 322 (cast(bool)ptr2).shouldBeTrue; 323 324 // not copyable 325 static assert(!__traits(compiles, ptr2 = ptr1)); 326 327 auto ptr3 = ptr2.unique; 328 ptr3.length.shouldEqual(3); 329 ptr3[].shouldEqual([Struct(), Struct(), Struct(5)]); 330 (*ptr3).shouldEqual([Struct(), Struct(), Struct(5)]); 331 332 ptr3 ~= Struct(10); 333 ptr3[].shouldEqual([Struct(), Struct(), Struct(5), Struct(10)]); 334 335 ptr3 ~= [Struct(11), Struct(12)]; 336 ptr3[].shouldEqual([Struct(), Struct(), Struct(5), Struct(10), Struct(11), Struct(12)]); 337 338 ptr3.length = 3; 339 ptr3[].shouldEqual([Struct(), Struct(), Struct(5)]); 340 341 ptr3.length = 4; 342 ptr3[].shouldEqual([Struct(), Struct(), Struct(5), Struct()]); 343 344 ptr3.length = 1; 345 346 ptr3 ~= makeUniqueArray!(Struct, Allocator)(allocator, 1); 347 348 ptr3[].shouldEqual([Struct(), Struct()]); 349 350 auto ptr4 = makeUniqueArray!(Struct, Allocator)(allocator, 1); 351 352 ptr3 ~= ptr4.unique; 353 ptr3[].shouldEqual([Struct(), Struct(), Struct()]); 354 355 ptr3 = [Struct(7), Struct(9)]; 356 ptr3[].shouldEqual([Struct(7), Struct(9)]); 357 } 358 } 359 360 @("@nogc") 361 @system @nogc unittest { 362 363 import std.experimental.allocator.mallocator: Mallocator; 364 365 auto arr = UniqueArray!(NoGcStruct, Mallocator)(2); 366 assert(arr.length == 2); 367 368 arr[0] = NoGcStruct(1); 369 arr[1] = NoGcStruct(3); 370 371 { 372 NoGcStruct[2] expected = [NoGcStruct(1), NoGcStruct(3)]; 373 assert(arr[] == expected[]); 374 } 375 376 auto arr2 = UniqueArray!(NoGcStruct, Mallocator)(1); 377 arr ~= arr2.unique; 378 379 { 380 NoGcStruct[3] expected = [NoGcStruct(1), NoGcStruct(3), NoGcStruct()]; 381 assert(arr[] == expected[]); 382 } 383 } 384 385 @("@nogc @safe") 386 @safe @nogc unittest { 387 auto allocator = SafeAllocator(); 388 auto arr = UniqueArray!(NoGcStruct, SafeAllocator)(SafeAllocator(), 6); 389 assert(arr.length == 6); 390 } 391 392 393 @("init TestAllocator") 394 @system unittest { 395 auto allocator = TestAllocator(); 396 auto arr = UniqueArray!(Struct, TestAllocator*)(&allocator, 2, Struct(7)); 397 arr[].shouldEqual([Struct(7), Struct(7)]); 398 } 399 400 @("init Mallocator") 401 @system unittest { 402 import std.experimental.allocator.mallocator: Mallocator; 403 alias allocator = Mallocator.instance; 404 auto arr = UniqueArray!(Struct, Mallocator)(2, Struct(7)); 405 arr[].shouldEqual([Struct(7), Struct(7)]); 406 } 407 408 409 @("range TestAllocator") 410 @system unittest { 411 auto allocator = TestAllocator(); 412 auto arr = UniqueArray!(Struct, TestAllocator*)(&allocator, [Struct(1), Struct(2)]); 413 arr[].shouldEqual([Struct(1), Struct(2)]); 414 } 415 416 @("range Mallocator") 417 @system unittest { 418 import std.experimental.allocator.mallocator: Mallocator; 419 auto arr = UniqueArray!(Struct, Mallocator)([Struct(1), Struct(2)]); 420 arr[].shouldEqual([Struct(1), Struct(2)]); 421 } 422 423 424 @("theAllocator") 425 @system unittest { 426 with(theTestAllocator) { 427 auto arr = UniqueArray!Struct(2); 428 arr[].shouldEqual([Struct(), Struct()]); 429 } 430 } 431 432 @("issue 1 array") 433 @system unittest { 434 import std.experimental.allocator.mallocator; 435 UniqueArray!(int, Mallocator) a; 436 a ~= [0, 1]; 437 } 438 439 @("issue 1 value") 440 @system unittest { 441 import std.experimental.allocator.mallocator; 442 UniqueArray!(int, Mallocator) a; 443 a ~= 7; 444 } 445 446 @("issue 1 UniqueArray") 447 @system unittest { 448 import std.experimental.allocator.mallocator; 449 UniqueArray!(int, Mallocator) a; 450 a ~= UniqueArray!(int, Mallocator)([1, 2, 3]); 451 } 452 453 @("dereference") 454 unittest { 455 import std.experimental.allocator.mallocator; 456 UniqueArray!(int, Mallocator) a; 457 a ~= [0, 1]; 458 (*a).shouldEqual([0, 1]); 459 } 460 461 @("reserve from nothing") 462 @system unittest { 463 auto allocator = TestAllocator(); 464 auto a = UniqueArray!(int, TestAllocator*)(&allocator); 465 a.reserve(10); //allocates here 466 a ~= [1, 2, 3]; // should not allocate 467 a ~= [4, 5, 6, 7, 8, 9]; //should not allocate 468 a[].shouldEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]); 469 allocator.numAllocations.shouldEqual(1); 470 } 471 472 @("reserve from existing expand") 473 @system unittest { 474 auto allocator = TestAllocator(); 475 auto a = UniqueArray!(int, TestAllocator*)(&allocator, [1, 2]); //allocates here 476 a.reserve(10); //allocates here 477 a ~= [3, 4]; // should not allocate 478 a ~= [5, 6, 7, 8, 9]; //should not allocate 479 a[].shouldEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]); 480 allocator.numAllocations.shouldEqual(2); 481 } 482 483 @("reserve from existing reduce") 484 @system unittest { 485 auto allocator = TestAllocator(); 486 auto a = UniqueArray!(int, TestAllocator*)(&allocator, [1, 2, 3, 4, 5]); //allocates here 487 a.reserve(2); // should not allocate, changes length to 2 488 a ~= [5, 6]; // should not allocate 489 a[].shouldEqual([1, 2, 5, 6]); 490 allocator.numAllocations.shouldEqual(1); 491 } 492 493 @("Append 2 arrays") 494 @system unittest { 495 auto allocator = TestAllocator(); 496 auto a = UniqueArray!(int, TestAllocator*)(&allocator, [1, 2, 3]) ~ 497 UniqueArray!(int, TestAllocator*)(&allocator, [4, 5]); 498 a[].shouldEqual([1, 2, 3, 4, 5]); 499 } 500 501 @("ptr") 502 @system unittest { 503 auto allocator = TestAllocator(); 504 auto a = UniqueArray!(int, TestAllocator*)(&allocator, [1, 2, 3, 4, 5]); 505 auto ptr = a.ptr; 506 ++ptr; 507 (*ptr).shouldEqual(2); 508 } 509 510 @("dup TestAllocator") 511 @system unittest { 512 auto allocator = TestAllocator(); 513 auto a = UniqueArray!(int, TestAllocator*)(&allocator, [1, 2, 3, 4, 5]); 514 auto b = a.dup; 515 allocator.numAllocations.shouldEqual(2); 516 b[].shouldEqual([1, 2, 3, 4, 5]); 517 } 518 519 @("dup Mallocator") 520 @system unittest { 521 import std.experimental.allocator.mallocator: Mallocator; 522 auto a = UniqueArray!(int, Mallocator)([1, 2, 3, 4, 5]); 523 auto b = a.dup; 524 b[].shouldEqual([1, 2, 3, 4, 5]); 525 } 526 527 @("dup TestAllocator indirections") 528 @system unittest { 529 auto allocator = TestAllocator(); 530 struct String { string s; } 531 auto a = UniqueArray!(String, TestAllocator*)(&allocator, [String("foo"), String("bar")]); 532 auto b = a.dup; 533 a[0] = String("quux"); 534 a[1] = String("toto"); 535 allocator.numAllocations.shouldEqual(2); 536 a[].shouldEqual([String("quux"), String("toto")]); 537 b[].shouldEqual([String("foo"), String("bar")]); 538 } 539 540 541 version(unittest) { 542 543 mixin template AllocatorAlias(T) { 544 import std.traits: hasMember; 545 546 enum isGlobal = hasMember!(T, "instance"); 547 548 static if(isGlobal) { 549 alias allocator = T.instance; 550 alias Allocator = T; 551 } else { 552 auto allocator = T(); 553 alias Allocator = T*; 554 } 555 } 556 557 558 auto makeUniqueArray(T, A1, A2, Args...)(ref A2 allocator, Args args) { 559 560 import std.traits: isPointer, hasMember; 561 562 enum isGlobal = hasMember!(A1, "instance"); 563 564 static if(isGlobal) 565 return UniqueArray!(T, A1)(args); 566 else static if(isPointer!A1) 567 return UniqueArray!(T, A1)(&allocator, args); 568 else 569 return UniqueArray!(T, A1)(allocator, args); 570 } 571 572 }