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 }