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; 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, size_t size) { 50 _allocator = allocator; 51 makeObjects(size); 52 } 53 54 this(Allocator allocator, size_t size, Type init) { 55 _allocator = allocator; 56 makeObjects(size, init); 57 } 58 59 this(R)(Allocator allocator, R range) if(isInputRange!R) { 60 _allocator = allocator; 61 makeObjects(range); 62 } 63 } 64 65 66 this(T)(UniqueArray!(T, Allocator) other) if(is(T: Type[])) { 67 moveFrom(other); 68 } 69 70 @disable this(this); 71 72 ~this() { 73 deleteObjects; 74 } 75 76 /** 77 Releases ownership and transfers it to the returned 78 Unique object. 79 */ 80 UniqueArray unique() { 81 import std.algorithm: move; 82 UniqueArray u; 83 move(this, u); 84 assert(_objects.length == 0 && _objects.ptr is null); 85 return u; 86 } 87 88 /** 89 "Truthiness" cast 90 */ 91 bool opCast(T)() const if(is(T == bool)) { 92 return _objects.ptr !is null; 93 } 94 95 void opAssign(T)(UniqueArray!(T, Allocator) other) if(is(T: Type[])) { 96 deleteObject; 97 moveFrom(other); 98 } 99 100 ref inout(Type) opIndex(long i) inout nothrow { 101 return _objects[i]; 102 } 103 104 const(Type)[] opSlice(long i, long j) const nothrow { 105 return _objects[i .. j]; 106 } 107 108 const(Type)[] opSlice() const nothrow { 109 return _objects[0 .. length]; 110 } 111 112 long opDollar() const nothrow { 113 return length; 114 } 115 116 @property long length() const nothrow { 117 return _objects.length; 118 } 119 120 @property void length(long i) { 121 import std.experimental.allocator: expandArray, shrinkArray; 122 123 if(i > length) 124 _allocator.expandArray(_objects, i - length); 125 else 126 _allocator.shrinkArray(_objects, length - i); 127 } 128 129 /** 130 Dereference. const since this otherwise could be used to try 131 and append to the array, which would not be nice 132 */ 133 ref const(Type[]) opUnary(string s)() const if(s == "*") { 134 return _objects; 135 } 136 137 void opOpAssign(string op)(Type other) if(op == "~") { 138 import std.experimental.allocator: expandArray; 139 140 _allocator.expandArray(_objects, 1); 141 _objects[$ - 1] = other; 142 } 143 144 void opOpAssign(string op)(Type[] other) if(op == "~") { 145 import std.experimental.allocator: expandArray; 146 const originalLength = length; 147 _allocator.expandArray(_objects, other.length); 148 _objects[originalLength .. $] = other[]; 149 } 150 151 void opOpAssign(string op)(UniqueArray other) if(op == "~") { 152 import std.experimental.allocator: expandArray; 153 const originalLength = length; 154 _allocator.expandArray(_objects, other.length); 155 _objects[originalLength .. $] = other[]; 156 } 157 158 void opAssign(Type[] other) { 159 this.length = other.length; 160 _objects[] = other[]; 161 } 162 163 private: 164 165 Type[] _objects; 166 167 static if(isSingleton) 168 alias _allocator = Allocator.instance; 169 else static if(isTheAllocator) 170 alias _allocator = theAllocator; 171 else 172 Allocator _allocator; 173 174 void makeObjects(size_t size) { 175 import std.experimental.allocator: makeArray; 176 version(LDC) 177 _objects = () @trusted { return _allocator.makeArray!Type(size); }(); 178 else 179 _objects = _allocator.makeArray!Type(size); 180 181 } 182 183 void makeObjects(size_t size, Type init) { 184 import std.experimental.allocator: makeArray; 185 _objects = _allocator.makeArray!Type(size, init); 186 } 187 188 void makeObjects(R)(R range) if(isInputRange!R) { 189 import std.experimental.allocator: makeArray; 190 _objects = _allocator.makeArray!Type(range); 191 } 192 193 194 void deleteObjects() { 195 import std.experimental.allocator: dispose; 196 import std.traits: isPointer; 197 198 static if(isPointer!Allocator) 199 assert((_objects.length == 0 && _objects.ptr is null) || _allocator !is null); 200 201 if(_objects.ptr !is null) _allocator.dispose(_objects); 202 } 203 204 void moveFrom(T)(ref UniqueArray!(T, Allocator) other) if(is(T: Type[])) { 205 _object = other._object; 206 other._object = null; 207 208 static if(!isGlobal) { 209 import std.algorithm: move; 210 move(other._allocator, _allocator); 211 } 212 } 213 } 214 215 216 @("default TestAllocator") 217 @system unittest { 218 uniqueArrayTest!TestAllocator; 219 } 220 221 222 @("default Mallocator") 223 @system unittest { 224 import std.experimental.allocator.mallocator: Mallocator; 225 uniqueArrayTest!Mallocator; 226 } 227 228 version(unittest) { 229 230 void uniqueArrayTest(T)() { 231 import std.traits: hasMember; 232 import std.algorithm: move; 233 234 enum isSingleton = hasMember!(T, "instance"); 235 236 static if(isSingleton) { 237 238 alias allocator = T.instance; 239 alias Allocator = T; 240 auto ptr = UniqueArray!(Struct, Allocator)(3); 241 Struct.numStructs += 1; // this ends up at -3 for some reason 242 } else { 243 244 auto allocator = T(); 245 alias Allocator = T*; 246 auto ptr = UniqueArray!(Struct, Allocator)(&allocator, 3); 247 Struct.numStructs += 1; // this ends up at -2 for some reason 248 } 249 250 ptr.length.shouldEqual(3); 251 252 ptr[2].twice.shouldEqual(0); 253 ptr[2] = Struct(5); 254 ptr[2].twice.shouldEqual(10); 255 256 ptr[1..$].shouldEqual([Struct(), Struct(5)]); 257 258 typeof(ptr) ptr2; 259 move(ptr, ptr2); 260 261 ptr.length.shouldEqual(0); 262 (cast(bool)ptr).shouldBeFalse; 263 ptr2.length.shouldEqual(3); 264 (cast(bool)ptr2).shouldBeTrue; 265 266 // not copyable 267 static assert(!__traits(compiles, ptr2 = ptr1)); 268 269 auto ptr3 = ptr2.unique; 270 ptr3.length.shouldEqual(3); 271 ptr3[].shouldEqual([Struct(), Struct(), Struct(5)]); 272 (*ptr3).shouldEqual([Struct(), Struct(), Struct(5)]); 273 274 ptr3 ~= Struct(10); 275 ptr3[].shouldEqual([Struct(), Struct(), Struct(5), Struct(10)]); 276 277 ptr3 ~= [Struct(11), Struct(12)]; 278 ptr3[].shouldEqual([Struct(), Struct(), Struct(5), Struct(10), Struct(11), Struct(12)]); 279 280 ptr3.length = 3; 281 ptr3[].shouldEqual([Struct(), Struct(), Struct(5)]); 282 283 ptr3.length = 4; 284 ptr3[].shouldEqual([Struct(), Struct(), Struct(5), Struct()]); 285 286 ptr3.length = 1; 287 288 static if(isSingleton) 289 ptr3 ~= UniqueArray!(Struct, Allocator)(1); 290 else 291 ptr3 ~= UniqueArray!(Struct, Allocator)(&allocator, 1); 292 293 ptr3[].shouldEqual([Struct(), Struct()]); 294 295 static if(isSingleton) 296 auto ptr4 = UniqueArray!(Struct, Allocator)(1); 297 else 298 auto ptr4 = UniqueArray!(Struct, Allocator)(&allocator, 1); 299 300 ptr3 ~= ptr4.unique; 301 ptr3[].shouldEqual([Struct(), Struct(), Struct()]); 302 303 ptr3 = [Struct(7), Struct(9)]; 304 ptr3[].shouldEqual([Struct(7), Struct(9)]); 305 } 306 } 307 308 @("@nogc") 309 @system @nogc unittest { 310 311 import std.experimental.allocator.mallocator: Mallocator; 312 313 auto arr = UniqueArray!(NoGcStruct, Mallocator)(2); 314 assert(arr.length == 2); 315 316 arr[0] = NoGcStruct(1); 317 arr[1] = NoGcStruct(3); 318 319 { 320 NoGcStruct[2] expected = [NoGcStruct(1), NoGcStruct(3)]; 321 assert(arr[] == expected[]); 322 } 323 324 auto arr2 = UniqueArray!(NoGcStruct, Mallocator)(1); 325 arr ~= arr2.unique; 326 327 { 328 NoGcStruct[3] expected = [NoGcStruct(1), NoGcStruct(3), NoGcStruct()]; 329 assert(arr[] == expected[]); 330 } 331 } 332 333 @("@nogc @safe") 334 @safe @nogc unittest { 335 auto allocator = SafeAllocator(); 336 auto arr = UniqueArray!(NoGcStruct, SafeAllocator)(SafeAllocator(), 6); 337 assert(arr.length == 6); 338 } 339 340 341 @("init TestAllocator") 342 @system unittest { 343 auto allocator = TestAllocator(); 344 auto arr = UniqueArray!(Struct, TestAllocator*)(&allocator, 2, Struct(7)); 345 arr[].shouldEqual([Struct(7), Struct(7)]); 346 } 347 348 @("init Mallocator") 349 @system unittest { 350 import std.experimental.allocator.mallocator: Mallocator; 351 alias allocator = Mallocator.instance; 352 auto arr = UniqueArray!(Struct, Mallocator)(2, Struct(7)); 353 arr[].shouldEqual([Struct(7), Struct(7)]); 354 } 355 356 357 @("range TestAllocator") 358 @system unittest { 359 auto allocator = TestAllocator(); 360 auto arr = UniqueArray!(Struct, TestAllocator*)(&allocator, [Struct(1), Struct(2)]); 361 arr[].shouldEqual([Struct(1), Struct(2)]); 362 } 363 364 @("range Mallocator") 365 @system unittest { 366 import std.experimental.allocator.mallocator: Mallocator; 367 alias allocator = Mallocator.instance; 368 auto arr = UniqueArray!(Struct, Mallocator)([Struct(1), Struct(2)]); 369 arr[].shouldEqual([Struct(1), Struct(2)]); 370 } 371 372 373 @("theAllocator") 374 @system unittest { 375 import std.experimental.allocator: allocatorObject, dispose; 376 377 auto allocator = TestAllocator(); 378 auto oldAllocator = theAllocator; 379 scope(exit) { 380 allocator.dispose(theAllocator); 381 theAllocator = oldAllocator; 382 } 383 theAllocator = allocatorObject(allocator); 384 385 auto arr = UniqueArray!Struct(2); 386 arr[].shouldEqual([Struct(), Struct()]); 387 }