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 }