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