1 /**
2    RAII arrays
3  */
4 module automem.unique_array;
5 
6 import automem.traits: isAllocator;
7 import stdx.allocator: theAllocator;
8 
9 
10 /**
11    A unique array similar to C++'s std::unique_ptr<T> when T is an array
12  */
13 struct UniqueArray(Type, Allocator = typeof(theAllocator)) if(isAllocator!Allocator) {
14 
15     import std.traits: hasMember, isScalarType;
16     import std.range: isInputRange;
17 
18     enum isSingleton = hasMember!(Allocator, "instance");
19     enum isTheAllocator = is(Allocator == typeof(theAllocator));
20     enum isGlobal = isSingleton || isTheAllocator;
21 
22     static if(isGlobal) {
23 
24         /**
25            The allocator is global, so no need to pass it in to the
26            constructor
27         */
28 
29         this(size_t size) {
30             makeObjects(size);
31         }
32 
33         this(size_t size, Type init) {
34             makeObjects(size, init);
35         }
36 
37         this(R)(R range) if(isInputRange!R) {
38             makeObjects(range);
39         }
40 
41 
42     } else {
43 
44         /**
45            Non-singleton allocator, must be passed in
46          */
47 
48         this(Allocator allocator) {
49             _allocator = allocator;
50         }
51 
52         this(Allocator allocator, size_t size) {
53             _allocator = allocator;
54             makeObjects(size);
55         }
56 
57         this(Allocator allocator, size_t size, Type init) {
58             _allocator = allocator;
59             makeObjects(size, init);
60         }
61 
62         this(R)(Allocator allocator, R range) if(isInputRange!R) {
63             _allocator = allocator;
64             makeObjects(range);
65         }
66     }
67 
68 
69     this(T)(UniqueArray!(T, Allocator) other) if(is(T: Type[])) {
70         moveFrom(other);
71     }
72 
73     @disable this(this);
74 
75     ~this() {
76         deleteObjects;
77     }
78 
79     /**
80        Releases ownership and transfers it to the returned
81        Unique object.
82      */
83     UniqueArray unique() {
84         import std.algorithm: move;
85         UniqueArray u;
86         move(this, u);
87         assert(_objects.length == 0 && _objects.ptr is null);
88         return u;
89     }
90     alias move = unique;
91 
92     /**
93        "Truthiness" cast
94      */
95     bool opCast(T)() const if(is(T == bool)) {
96         return _objects.ptr !is null;
97     }
98 
99     void opAssign(T)(UniqueArray!(T, Allocator) other) if(is(T: Type[])) {
100         deleteObject;
101         moveFrom(other);
102     }
103 
104     ref inout(Type) opIndex(long i) inout nothrow {
105         return _objects[i];
106     }
107 
108     const(Type)[] opSlice(long i, long j) const nothrow {
109         return _objects[i .. j];
110     }
111 
112     const(Type)[] opSlice() const nothrow {
113         return _objects[0 .. length];
114     }
115 
116     long opDollar() const nothrow {
117         return length;
118     }
119 
120     @property long length() const nothrow {
121         return _length;
122     }
123 
124     @property void length(long size) {
125 
126         import stdx.allocator: expandArray, shrinkArray;
127 
128         if(_objects is null) {
129             makeObjects(size);
130         } else if(size == length) {
131             return;
132         } else if(size <= _capacity && size > length) {
133             foreach(ref obj; _objects[_length .. size])
134                 obj = obj.init;
135             _length = size;
136         } else if(size < length) {
137             _length = size;
138         } else {
139             if(size > length) {
140                 _allocator.expandArray(_objects, size - length);
141                 setLength;
142             } else
143                 assert(0);
144         }
145     }
146 
147     /**
148        Dereference. const  since this otherwise could be used to try
149        and append to the array, which would not be nice
150      */
151     const(Type[]) opUnary(string s)() const if(s == "*") {
152         return this[];
153     }
154 
155     /**
156        Append to the array
157      */
158     UniqueArray opBinary(string s)(UniqueArray other) if(s == "~") {
159         this ~= other.unique;
160         return this.unique;
161     }
162 
163     /// Append to the array
164     void opOpAssign(string op)(Type other) if(op == "~") {
165         length(length + 1);
166         _objects[$ - 1] = other;
167     }
168 
169     /// Append to the array
170     void opOpAssign(string op)(Type[] other) if(op == "~") {
171         const originalLength = length;
172         length(originalLength + other.length);
173         _objects[originalLength .. length] = other[];
174     }
175 
176     /// Append to the array
177     void opOpAssign(string op)(UniqueArray other) if(op == "~") {
178         this ~= other._objects;
179     }
180 
181     /// Assign from a slice.
182     void opAssign(Type[] other) {
183         length = other.length;
184         _objects[0 .. length] = other[0 .. length];
185     }
186 
187     /**
188        Reserves memory to prevent too many allocations
189      */
190     void reserve(in long size) {
191         import stdx.allocator: expandArray;
192 
193         if(_objects is null) {
194             const oldLength = length;
195             makeObjects(size); // length = capacity here
196             _length = oldLength;
197             return;
198         }
199 
200         if(size < _capacity) {
201             if(size < _length) _length = size;
202             return;
203         }
204 
205         _capacity = size;
206         _allocator.expandArray(_objects, _capacity);
207     }
208 
209     /**
210        Returns a pointer to the underlying data. @system
211      */
212     inout(Type)* ptr() inout {
213         return _objects.ptr;
214     }
215 
216     static if(isGlobal) {
217         UniqueArray dup() const {
218             return UniqueArray(_objects);
219         }
220     } else static if(isScalarType!Allocator && is(typeof(() { auto a = Allocator.init; auto b = a; }))) {
221         UniqueArray dup() const {
222             return UniqueArray(_allocator, _objects);
223         }
224     } else {
225         UniqueArray dup() {
226             return UniqueArray(_allocator, _objects);
227         }
228     }
229 
230 private:
231 
232     Type[] _objects;
233     long _length;
234     long _capacity;
235 
236     static if(isSingleton)
237         alias _allocator = Allocator.instance;
238     else static if(isTheAllocator)
239         alias _allocator = theAllocator;
240     else
241         Allocator _allocator;
242 
243     void makeObjects(size_t size) {
244         import stdx.allocator: makeArray;
245         _objects = _allocator.makeArray!Type(size);
246         setLength;
247     }
248 
249     void makeObjects(size_t size, Type init) {
250         import stdx.allocator: makeArray;
251         _objects = _allocator.makeArray!Type(size, init);
252         setLength;
253 
254     }
255 
256     void makeObjects(R)(R range) if(isInputRange!R) {
257         import stdx.allocator: makeArray;
258         _objects = _allocator.makeArray!Type(range);
259         setLength;
260     }
261 
262     void setLength() {
263         _capacity = _length = _objects.length;
264     }
265 
266     void deleteObjects() {
267         import stdx.allocator: dispose;
268         import std.traits: isPointer;
269 
270         static if(isPointer!Allocator)
271             assert((_objects.length == 0 && _objects.ptr is null) || _allocator !is null);
272 
273         if(_objects.ptr !is null) _allocator.dispose(_objects);
274         _length = 0;
275     }
276 
277     void moveFrom(T)(ref UniqueArray!(T, Allocator) other) if(is(T: Type[])) {
278         import std.algorithm: swap;
279         _object = other._object;
280         other._object = null;
281 
282         swap(_length, other._length);
283         swap(_capacity, other._capacity);
284 
285         static if(!isGlobal) {
286             import std.algorithm: move;
287             _allocator = other._allocator.move;
288         }
289     }
290 }