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.toSizeT] = oldElements[0 .. length.toSizeT]; 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).toSizeT]; 105 } 106 107 /// Pops the front element off 108 void popFront() { 109 foreach(i; 0 .. length - 1) 110 _elements[i.toSizeT] = _elements[i.toSizeT + 1]; 111 112 popBack; 113 } 114 115 /// Pops the last element off 116 void popBack() { 117 --_length; 118 } 119 120 /// If the vector is empty 121 bool empty() const { 122 return length == 0; 123 } 124 125 /// The current length of the vector 126 @property long length() const { 127 return _length; 128 } 129 130 /// Set the length of the vector 131 @property void length(long newLength) { 132 if(capacity < newLength) reserve(newLength); 133 _length = newLength; 134 } 135 136 /// The current memory capacity of the vector 137 long capacity() const { 138 return _elements.length; 139 } 140 141 /// Clears the vector, resulting in an empty one 142 void clear() { 143 _length = 0; 144 } 145 146 /// Reserve memory to avoid allocations when appending 147 void reserve(long newLength) { 148 expandMemory(newLength); 149 } 150 151 /// Shrink to fit the current length. Returns if shrunk. 152 bool shrink() scope { 153 return shrink(length); 154 } 155 156 /// Shrink to fit the new length given. Returns if shrunk. 157 bool shrink(long newLength) scope { 158 import stdx.allocator: shrinkArray; 159 160 const delta = capacity - newLength; 161 const shrunk = () @trusted { return _allocator.shrinkArray(_elements, delta.toSizeT); }(); 162 _length = newLength; 163 164 return shrunk; 165 } 166 167 /// Access the ith element. Can throw RangeError. 168 ref inout(E) opIndex(long i) inout { 169 if(i < 0 || i >= length) 170 throw boundsException; 171 return _elements[i.toSizeT]; 172 } 173 174 /// Returns a new vector after appending to the given vector. 175 Vector opBinary(string s, T)(auto ref T other) const if(s == "~" && is(Unqual!T == Vector)) { 176 import std.range: chain; 177 return Vector(chain(this[], other[])); 178 } 179 180 /// Assigns from a range. 181 void opAssign(R)(R range) scope if(isForwardRangeOf!(R, E)) { 182 import std.range.primitives: walkLength, save; 183 184 expand(range.save.walkLength); 185 186 long i = 0; 187 foreach(element; range) 188 _elements[toSizeT(i++)] = element; 189 } 190 191 /// Append to the vector 192 void opOpAssign(string op) 193 (E other) 194 scope 195 if(op == "~") 196 { 197 expand(length + 1); 198 _elements[(length - 1).toSizeT] = other; 199 } 200 201 /// Append to the vector 202 void opOpAssign(string op, R) 203 (R range) 204 scope 205 if(op == "~" && isForwardRangeOf!(R, E)) 206 { 207 import std.range.primitives: walkLength, save; 208 209 long index = length; 210 expand(length + range.save.walkLength); 211 212 foreach(element; range) 213 _elements[toSizeT(index++)] = element; 214 } 215 216 /// Returns a slice 217 scope auto opSlice(this This)() { 218 return _elements[0 .. length.toSizeT]; 219 } 220 221 /// Returns a slice 222 scope auto opSlice(this This)(long start, long end) { 223 if(start < 0 || start >= length) 224 throw boundsException; 225 226 if(end < 0 || end >= length) 227 throw boundsException; 228 229 return _elements[start.toSizeT .. end.toSizeT]; 230 } 231 232 long opDollar() const { 233 return length; 234 } 235 236 /// Assign all elements to the given value 237 void opSliceAssign(E value) { 238 _elements[] = value; 239 } 240 241 /// Assign all elements in the given range to the given value 242 void opSliceAssign(E value, long start, long end) { 243 if(start < 0 || start >= length) 244 throw boundsException; 245 246 if(end < 0 || end >= length) 247 throw boundsException; 248 249 _elements[start.toSizeT .. end.toSizeT] = value; 250 } 251 252 /// Assign all elements using the given operation and the given value 253 void opSliceOpAssign(string op)(E value) scope { 254 foreach(ref elt; _elements) 255 mixin(`elt ` ~ op ~ `= value;`); 256 } 257 258 /// Assign all elements in the given range using the given operation and the given value 259 void opSliceOpAssign(string op)(E value, long start, long end) scope { 260 if(start < 0 || start >= length) 261 throw boundsException; 262 263 if(end < 0 || end >= length) 264 throw boundsException; 265 266 foreach(ref elt; _elements[start.toSizeT .. end.toSizeT]) 267 mixin(`elt ` ~ op ~ `= value;`); 268 } 269 270 private: 271 272 E[] _elements; 273 long _length; 274 275 static if(isSingleton!Allocator) 276 alias _allocator = Allocator.instance; 277 else static if(isTheAllocator!Allocator) 278 alias _allocator = theAllocator; 279 else 280 Allocator _allocator; 281 282 E[] createVector(long length) { 283 import stdx.allocator: makeArray; 284 return () @trusted { return _allocator.makeArray!E(length.toSizeT); }(); 285 } 286 287 void fromElements(E[] elements) { 288 _elements = createVector(elements.length); 289 _elements[] = elements[]; 290 _length = elements.length; 291 } 292 293 void expand(long newLength) scope { 294 expandMemory(newLength); 295 _length = newLength; 296 } 297 298 void expandMemory(long newLength) scope { 299 import stdx.allocator: expandArray; 300 301 if(newLength > capacity) { 302 if(length == 0) 303 _elements = createVector(newLength); 304 else { 305 const newCapacity = (newLength * 3) / 2; 306 const delta = newCapacity - capacity; 307 () @trusted { _allocator.expandArray(_elements, delta.toSizeT); }(); 308 } 309 } 310 } 311 } 312 313 private static immutable boundsException = new BoundsException("Out of bounds index"); 314 315 class BoundsException: Exception { 316 import std.exception: basicExceptionCtors; 317 318 mixin basicExceptionCtors; 319 } 320 321 private template isInputRangeOf(R, E) { 322 import std.range.primitives: isInputRange, ElementType; 323 import std.traits: Unqual; 324 325 enum isInputRangeOf = isInputRange!R && is(Unqual!(ElementType!R) == E); 326 } 327 328 private template isForwardRangeOf(R, E) { 329 import std.range.primitives: isForwardRange, ElementType; 330 import std.traits: Unqual; 331 332 enum isForwardRangeOf = isForwardRange!R && is(Unqual!(ElementType!R) == E); 333 } 334 335 336 private size_t toSizeT(long length) @safe @nogc pure nothrow { 337 static if(size_t.sizeof < long.sizeof) 338 assert(length < cast(long) size_t.max); 339 return cast(size_t) length; 340 }