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 }