1 /** 2 Dynamic arrays with deterministic memory usage 3 akin to C++'s std::vector or Rust's std::vec::Vec 4 */ 5 module automem.vector; 6 7 8 import automem.traits: isAllocator, isGlobal; 9 import std.range.primitives: isInputRange; 10 import stdx.allocator: theAllocator; 11 import stdx.allocator.mallocator: Mallocator; 12 13 /** 14 Create a vector from a variadic list of elements, inferring the type of 15 the elements and the allocator 16 */ 17 auto vector(A = typeof(theAllocator), E) 18 (E[] elements...) 19 if(isAllocator!A && isGlobal!A) 20 { 21 return Vector!(E, A)(elements); 22 } 23 24 /// ditto 25 auto vector(A = typeof(theAllocator), E) 26 (A allocator, E[] elements...) 27 if(isAllocator!A && !isGlobal!A) 28 { 29 return Vector!(E, A)(allocator, elements); 30 } 31 32 /** 33 Create a vector from an input range, inferring the type of the elements 34 and the allocator. 35 */ 36 auto vector(A = typeof(theAllocator), R) 37 (R range) 38 if(isAllocator!A && isGlobal!A && isInputRange!R) 39 { 40 import std.range.primitives: ElementType; 41 return Vector!(ElementType!R, A)(range); 42 } 43 44 45 /// ditto 46 auto vector(A = typeof(theAllocator), R) 47 (A allocator, R range) 48 if(isAllocator!A && !isGlobal!A && isInputRange!R) 49 { 50 import std.range.primitives: ElementType; 51 return Vector!(ElementType!R, A)(range); 52 } 53 54 /** 55 A dynamic array with deterministic memory usage 56 akin to C++'s std::vector or Rust's std::vec::Vec 57 */ 58 struct Vector(E, Allocator = typeof(theAllocator)) if(isAllocator!Allocator) { 59 60 import automem.traits: isGlobal, isSingleton, isTheAllocator; 61 import std.traits: Unqual; 62 63 static if(isGlobal!Allocator) { 64 65 this(E[] elements...) { 66 fromElements(elements); 67 } 68 69 this(R)(R range) if(isInputRangeOf!(R, E)) { 70 this = range; 71 } 72 73 } else { 74 75 this(Allocator allocator, E[] elements...) { 76 _allocator = allocator; 77 fromElements(elements); 78 } 79 80 this(R)(Allocator allocator, R range) if(isInputRangeOf!(R, E)) { 81 _allocator = allocator; 82 this = range; 83 } 84 } 85 86 this(this) scope { 87 auto oldElements = _elements; 88 _elements = createVector(_elements.length); 89 _elements[0 .. length] = oldElements[0 .. length]; 90 } 91 92 ~this() scope { 93 import stdx.allocator: dispose; 94 () @trusted { _allocator.dispose(_elements); }(); 95 } 96 97 /// Returns the first element 98 inout(E) front() inout { 99 return _elements[0]; 100 } 101 102 /// Returns the last element 103 inout(E) back() inout { 104 return _elements[length - 1]; 105 } 106 107 /// When implemented, pops the front element off 108 void popFront() { 109 throw new Exception("Not implemented yet"); 110 } 111 112 /// Pops the last element off 113 void popBack() { 114 --_length; 115 } 116 117 /// If the vector is empty 118 bool empty() const { 119 return length == 0; 120 } 121 122 /// The current length of the vector 123 @property long length() const { 124 return _length; 125 } 126 127 /// Set the length of the vector 128 @property void length(long newLength) { 129 if(capacity < newLength) reserve(newLength); 130 _length = newLength; 131 } 132 133 /// The current memory capacity of the vector 134 long capacity() const { 135 return _elements.length; 136 } 137 138 /// Clears the vector, resulting in an empty one 139 void clear() { 140 _length = 0; 141 } 142 143 /// Reserve memory to avoid allocations when appending 144 void reserve(long newLength) { 145 expandMemory(newLength); 146 } 147 148 /// Shrink to fit the current length. Returns if shrunk. 149 bool shrink() scope { 150 return shrink(length); 151 } 152 153 /// Shrink to fit the new length given. Returns if shrunk. 154 bool shrink(long newLength) scope { 155 import stdx.allocator: shrinkArray; 156 157 const delta = capacity - newLength; 158 const shrunk = () @trusted { return _allocator.shrinkArray(_elements, delta); }(); 159 _length = newLength; 160 161 return shrunk; 162 } 163 164 /// Access the ith element. Can throw RangeError. 165 ref inout(E) opIndex(long i) inout { 166 if(i < 0 || i >= length) 167 throw boundsException; 168 return _elements[i]; 169 } 170 171 /// Returns a new vector after appending to the given vector. 172 Vector opBinary(string s, T)(auto ref T other) const if(s == "~" && is(Unqual!T == Vector)) { 173 import std.range: chain; 174 return Vector(chain(this[], other[])); 175 } 176 177 /// Assigns from a range. 178 void opAssign(R)(R range) scope if(isForwardRangeOf!(R, E)) { 179 import std.range.primitives: walkLength, save; 180 181 expand(range.save.walkLength); 182 183 long i = 0; 184 foreach(element; range) 185 _elements[i++] = element; 186 } 187 188 /// Append to the vector 189 void opOpAssign(string op) 190 (E other) 191 scope 192 if(op == "~") 193 { 194 expand(length + 1); 195 _elements[length - 1] = other; 196 } 197 198 /// Append to the vector 199 void opOpAssign(string op, R) 200 (R range) 201 scope 202 if(op == "~" && isForwardRangeOf!(R, E)) 203 { 204 import std.range.primitives: walkLength, save; 205 206 long index = length; 207 expand(length + range.save.walkLength); 208 209 foreach(element; range) 210 _elements[index++] = element; 211 } 212 213 /// Returns a slice 214 scope auto opSlice(this This)() { 215 return _elements[0 .. length]; 216 } 217 218 /// Returns a slice 219 scope auto opSlice(this This)(long start, long end) { 220 if(start < 0 || start >= length) 221 throw boundsException; 222 223 if(end < 0 || end >= length) 224 throw boundsException; 225 226 return _elements[start .. end]; 227 } 228 229 long opDollar() const { 230 return length; 231 } 232 233 /// Assign all elements to the given value 234 void opSliceAssign(E value) { 235 _elements[] = value; 236 } 237 238 /// Assign all elements in the given range to the given value 239 void opSliceAssign(E value, long start, long end) { 240 if(start < 0 || start >= length) 241 throw boundsException; 242 243 if(end < 0 || end >= length) 244 throw boundsException; 245 246 _elements[start .. end] = value; 247 } 248 249 /// Assign all elements using the given operation and the given value 250 void opSliceOpAssign(string op)(E value) scope { 251 foreach(ref elt; _elements) 252 mixin(`elt ` ~ op ~ `= value;`); 253 } 254 255 /// Assign all elements in the given range using the given operation and the given value 256 void opSliceOpAssign(string op)(E value, long start, long end) scope { 257 if(start < 0 || start >= length) 258 throw boundsException; 259 260 if(end < 0 || end >= length) 261 throw boundsException; 262 263 foreach(ref elt; _elements[start .. end]) 264 mixin(`elt ` ~ op ~ `= value;`); 265 } 266 267 private: 268 269 E[] _elements; 270 long _length; 271 272 static if(isSingleton!Allocator) 273 alias _allocator = Allocator.instance; 274 else static if(isTheAllocator!Allocator) 275 alias _allocator = theAllocator; 276 else 277 Allocator _allocator; 278 279 E[] createVector(long length) { 280 import stdx.allocator: makeArray; 281 return () @trusted { return _allocator.makeArray!E(length); }(); 282 } 283 284 void fromElements(E[] elements) { 285 _elements = createVector(elements.length); 286 _elements[] = elements[]; 287 _length = elements.length; 288 } 289 290 void expand(long newLength) scope { 291 expandMemory(newLength); 292 _length = newLength; 293 } 294 295 void expandMemory(long newLength) scope { 296 import stdx.allocator: expandArray; 297 298 if(newLength > capacity) { 299 if(length == 0) 300 _elements = createVector(newLength); 301 else { 302 const newCapacity = (newLength * 3) / 2; 303 const delta = newCapacity - capacity; 304 () @trusted { _allocator.expandArray(_elements, delta); }(); 305 } 306 } 307 } 308 } 309 310 private static immutable boundsException = new BoundsException("Out of bounds index"); 311 312 class BoundsException: Exception { 313 import std.exception: basicExceptionCtors; 314 315 mixin basicExceptionCtors; 316 } 317 318 private template isInputRangeOf(R, E) { 319 import std.range.primitives: isInputRange, ElementType; 320 import std.traits: Unqual; 321 322 enum isInputRangeOf = isInputRange!R && is(Unqual!(ElementType!R) == E); 323 } 324 325 private template isForwardRangeOf(R, E) { 326 import std.range.primitives: isForwardRange, ElementType; 327 import std.traits: Unqual; 328 329 enum isForwardRangeOf = isForwardRange!R && is(Unqual!(ElementType!R) == E); 330 }