1 /** 2 Dynamic Arrays 3 4 Copyright: 5 Copyright © 2023-2025, Kitsunebi Games 6 Copyright © 2023-2025, Inochi2D Project 7 8 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 Authors: Luna Nielsen 10 */ 11 module nulib.collections.vector; 12 import nulib.collections.internal.marray; 13 import numem.core.hooks; 14 import numem.core.traits; 15 import numem; 16 17 /** 18 A vector which owns the memory it contains. 19 */ 20 alias vector(T) = VectorImpl!(T, true); 21 22 /** 23 A vector which does not own the memory it contains. 24 */ 25 alias weak_vector(T) = VectorImpl!(T, false); 26 27 /** 28 A @nogc dynamic array. 29 */ 30 struct VectorImpl(T, bool ownsMemory = true) { 31 @nogc: 32 private: 33 alias SelfType = typeof(this); 34 ManagedArray!(T, ownsMemory) memory; 35 36 public: 37 alias value this; 38 39 /** 40 Gets the internal memory slice. 41 */ 42 @property T[] value() @trusted nothrow pure { return memory; } 43 44 /** 45 The length of the vector 46 */ 47 @property size_t length() const @safe nothrow { return memory.length; } 48 49 /** 50 The capacity of the vector. 51 */ 52 @property size_t capacity() const @safe nothrow { return memory.capacity; } 53 54 /** 55 Whether the vector is empty. 56 */ 57 @property bool empty() const @safe nothrow { return memory.length == 0; } 58 59 /** 60 The length of the vector, in bytes. 61 */ 62 @property size_t usage() @safe nothrow { return memory.length*T.sizeof; } 63 64 /** 65 Gets a pointer to the first element of the 66 vector. 67 */ 68 @property T* ptr() @system nothrow { return memory.ptr; } 69 70 /** 71 Gets a pointer to the first element of the vector. 72 73 If the vector is empty, the first element will be null. 74 */ 75 @property T* front() @system nothrow { return empty ? null : &memory[0]; } 76 77 /** 78 Gets a pointer to the last element of the vector. 79 80 If the vector is empty, the last element will be null. 81 */ 82 @property T* back() @system nothrow { return empty ? null : &memory[$-1]; } 83 84 ~this() { 85 86 // This essentially frees the memory. 87 memory.reserve(0); 88 memory.capacity = 0; 89 } 90 91 /** 92 Creates a new vector from a slice. 93 94 Params: 95 rhs = slice to copy or move data from. 96 97 Notes: 98 Elements will either be copied or moved out of the 99 source slice. Pointers will only be weakly referenced. 100 If said pointers were allocated with the GC, they may be 101 collected! 102 */ 103 this(T[] rhs) @system { 104 if (__ctfe) { 105 memory.memory = rhs; 106 memory.capacity = rhs.length; 107 } else { 108 static if (hasElaborateMove!T) { 109 memory.resize(rhs.length); 110 nogc_move(memory, rhs); 111 } else { 112 memory.resize(rhs.length); 113 nogc_copy(memory, rhs); 114 } 115 } 116 } 117 118 /** 119 Copy-constructor 120 121 Params: 122 rhs = slice to copy or move data from. 123 */ 124 this(ref return scope inout(SelfType) rhs) @trusted { 125 if (__ctfe) { 126 this = rhs; 127 } else { 128 static if (hasElaborateMove!T) { 129 memory.resize(rhs.length); 130 nogc_move(memory.memory, rhs); 131 } else { 132 memory.resize(rhs.length); 133 nogc_copy(memory.memory, cast(SelfType)rhs); 134 } 135 } 136 } 137 138 /** 139 Constructs a new vector with $(D reserved) amount of 140 elements worth of memory reserved. 141 142 Params: 143 reserved = How many elements of memory to reserve. 144 */ 145 this(size_t reserved) { 146 if (__ctfe) { } else { 147 this.reserve(reserved); 148 } 149 } 150 151 /** 152 Take ownership of the memory owned by the vector. 153 154 Returns: 155 The memory which was owned by the vector, 156 the vector is reset in the process. 157 */ 158 T[] take() { 159 return memory.take(); 160 } 161 162 /** 163 Flips the endianness of the vector's contents. 164 165 Note: 166 This is no-op for 8-bit elements. 167 168 Returns: 169 The vector instance. 170 */ 171 auto ref flipEndian() { 172 memory.flipEndian(); 173 return this; 174 } 175 176 /** 177 Reverses the contents of the vector 178 179 Returns: 180 The vector instance. 181 */ 182 auto ref reverse() { 183 memory.reverse(); 184 return this; 185 } 186 187 /** 188 Clears the vector, removing all elements from it. 189 */ 190 void clear() @safe { 191 memory.reserve(0); 192 } 193 194 /** 195 Attempts to reserve memory in the vector. 196 197 Reserve can *only* grow the allocation; not shrink it. 198 */ 199 auto ref reserve(size_t newSize) { 200 if (newSize > memory.capacity) 201 memory.reserve(newSize); 202 203 return this; 204 } 205 206 /** 207 Resizes the vector. 208 */ 209 auto ref resize(size_t newSize) { 210 memory.resize(newSize); 211 return this; 212 } 213 214 /** 215 Pops the front element of the vector. 216 */ 217 void popFront() { 218 if (!empty) { 219 memory.deleteRange(memory[0..1]); 220 } 221 } 222 223 /** 224 Pops the back element of the vector. 225 */ 226 void popBack() { 227 if (!empty) { 228 if (memory.length == 1) { 229 this.clear(); 230 return; 231 } 232 233 memory.deleteRange(memory[$-1..$]); 234 } 235 } 236 237 /** 238 Removes an element from the vector matching the given element. 239 */ 240 void remove(T element) { 241 foreach_reverse(i; 0..memory.length) { 242 243 // NOTE: DRuntime's opEquals implementation is not nogc 244 // as such we need to check whether we can do an equals comparison 245 // in a nogc context. 246 // Otherwise, we try calling opEquals directly, if that fails we try 247 // the `is` operator, and finally if that fails we assert at compile-time. 248 static if (is(typeof(() @nogc { return T.init == T.init; }))) { 249 if (memory[i] == element) { 250 this.removeAt(i); 251 return; 252 } 253 } else static if (is(typeof(() @nogc { return T.init.opEquals(T.init); }))) { 254 if (memory[i].opEquals(element)) { 255 this.removeAt(i); 256 return; 257 } 258 } else static if (is(typeof(() @nogc { return T.init is T.init; }))) { 259 if (memory[i] is element) { 260 this.removeAt(i); 261 return; 262 } 263 } else static assert(0, "Failed to find a nogc comparison method!"); 264 } 265 } 266 267 /** 268 Removes the element at the given index. 269 */ 270 void removeAt(size_t i) { 271 if (i >= 0 && i < memory.length) { 272 memory.deleteRange(memory[i .. i+1]); 273 } 274 } 275 276 /** 277 Removes the elements at the given index with the given 278 element count. 279 */ 280 void removeAt(size_t i, size_t count) { 281 if (i >= 0 && i+count < memory.length) { 282 memory.deleteRange(memory[i .. i+count]); 283 } 284 } 285 286 /** 287 Inserts a value into the vector. 288 289 Params: 290 value = The value to insert. 291 offset = The offset to insert the value at. 292 */ 293 void insert(T value, size_t offset) { 294 assert(offset < memory.length, "Offset is past the end of the vector"); 295 296 // Resize 297 size_t ogLength = memory.length; 298 memory.resize(memory.length+1); 299 300 // Move & Insert 301 memory.moveRange(memory[offset+1..ogLength+1], memory[offset..ogLength]); 302 static if (hasElaborateMove!T) { 303 memory.memory[offset] = value.move(); 304 } else { 305 memory.memory[offset] = value; 306 } 307 } 308 309 /** 310 Inserts a value into the vector. 311 312 Params: 313 values = The values to insert. 314 offset = The offset to insert the values at. 315 */ 316 void insert(T[] values, size_t offset) { 317 assert(offset < memory.length, "Offset is past the end of the vector"); 318 319 // Resize 320 size_t ogLength = memory.length; 321 memory.resize(memory.length+values.length); 322 323 // Move & Insert 324 memory.moveRange(memory[offset+values.length..ogLength+values.length], memory[offset..ogLength]); 325 memory.moveRange(memory[offset..offset+values.length], values); 326 } 327 328 /** 329 Append a $(D T) to the vector. 330 331 Params: 332 value = The value to append. 333 */ 334 void opOpAssign(string op = "~")(auto ref T value) @trusted { 335 memory.resize(length+1); 336 337 static if (hasElaborateMove!T) { 338 memory.memory[$-1] = value.move(); 339 } else { 340 memory.memory[$-1] = value; 341 } 342 } 343 344 /** 345 Append a range to the vector. 346 347 Params: 348 other = the other range to append. 349 */ 350 void opOpAssign(string op = "~")(T[] other) @trusted { 351 size_t start = memory.length; 352 353 // Self-intersecting move. 354 if (memory.isMovingIntoSelf(other)) { 355 other = rangeCopy(other); 356 357 memory.resize(memory.length+other.length); 358 memory.moveRange(memory[start..$], other[0..$]); 359 other = other.nu_resize(0); 360 } 361 362 // Basic move. 363 memory.resize(memory.length+other.length); 364 memory.moveRange(memory[start..$], other[0..$]); 365 } 366 } 367 368 @("vector-of-strings") 369 unittest { 370 import nulib.string : nstring; 371 vector!nstring strs; 372 strs ~= nstring("Hello, world!"); 373 strs ~= nstring("Hello, world!"); 374 strs ~= nstring("Hello, world!"); 375 strs ~= nstring("Hello, world!"); 376 strs ~= nstring("Hello, world!"); 377 strs ~= nstring("Hello, world!"); 378 379 strs ~= strs; 380 381 import std.stdio : writeln; 382 foreach(ref str; strs) { 383 assert(str == "Hello, world!"); 384 } 385 } 386 387 @(".reverse()") 388 unittest { 389 vector!int numbers = [1, 2, 3, 4]; 390 assert(numbers.reverse() == [4, 3, 2, 1]); 391 } 392 393 @(".flipEndian()") 394 unittest { 395 vector!ushort numbers = [0xFF00, 0x00FF]; 396 assert(numbers.flipEndian() == [0x00FF, 0xFF00]); 397 } 398 399 @(".flipEndian().reverse()") 400 unittest { 401 vector!ushort numbers = [0xFF00, 0x0FF0, 0x00FF]; 402 assert(numbers.flipEndian().reverse() == [0xFF00, 0xF00F, 0x00FF]); 403 } 404 405 @(".removeAt(index)") 406 unittest { 407 vector!int numbers = [1, 0, 2, 3, 4]; 408 409 numbers.removeAt(1); 410 assert(numbers == [1, 2, 3, 4]); 411 } 412 413 @(".removeAt(index, count)") 414 unittest { 415 vector!int numbers = [0, 1, 2, 3, 4]; 416 417 numbers.removeAt(1, 2); 418 assert(numbers == [0, 3, 4]); 419 } 420 421 @(".remove(element)") 422 unittest { 423 vector!int numbers = [0, 1, 255, 2, 3, 4]; 424 425 numbers.remove(255); 426 assert(numbers == [0, 1, 2, 3, 4]); 427 } 428 429 @(".insert(element)") 430 unittest { 431 vector!int numbers = [0, 1, 2, 3, 4]; 432 433 numbers.insert(255, 2); 434 assert(numbers == [0, 1, 255, 2, 3, 4]); 435 } 436 437 @(".insert(elements)") 438 unittest { 439 vector!int numbers = [0, 1, 2, 3, 4]; 440 441 numbers.insert([255, 255, 255], 2); 442 assert(numbers == [0, 1, 255, 255, 255, 2, 3, 4]); 443 } 444 445 @(".flipEndian() nothrow struct") 446 unittest { 447 static 448 struct Test { 449 ulong a; 450 ~this() @nogc nothrow { } 451 } 452 453 Test testA = Test(0xF00FF00FF00FF00F); 454 Test testB = Test(0x0FF00FF00FF00FF0); 455 vector!Test testStructs = [testA, testB]; 456 assert(testStructs.flipEndian() == [testB, testA]); 457 }