1 /** 2 A type-safe growable buffer designed for low overhead 3 */ 4 module dgt.array; 5 import core.stdc.stdlib : malloc, realloc, free; 6 import dgt.io; 7 8 /** 9 A growable buffer that attempts to keep as many operations amortized as possible 10 */ 11 struct Array(T) 12 { 13 14 private void* backingBuffer = null; 15 16 @disable this(); 17 18 @nogc @trusted nothrow public: 19 /** 20 Create a buffer with a given initial capacity 21 22 When a buffer runs out of capacity it has to reallocate, which is much slower than a normal add 23 */ 24 this(in size_t initialCapacity) 25 { 26 ensureCapacity(initialCapacity); 27 } 28 29 /** 30 Create a buffer from a fixed-size array of elements 31 32 This is useful for initializing with an array literal 33 */ 34 this(scope T[] array) 35 { 36 ensureCapacity(array.length); 37 foreach(item; array) 38 add(item); 39 } 40 41 /** 42 Resizes the the array to be a given size manually 43 44 Not generally a good idea unless you have a specific reason 45 */ 46 @system void ensureCapacity(in size_t newCapacity) 47 { 48 void* old = backingBuffer; 49 backingBuffer = realloc(backingBuffer, size_t.sizeof * 2 + T.sizeof * newCapacity); 50 *capacity = newCapacity; 51 if (old == null) *count = 0; 52 } 53 54 /** 55 Appends a value to the end of the array 56 57 If more memory is required, the array is resized 58 */ 59 void add(scope T val) 60 { 61 if (*count >= *capacity) 62 ensureCapacity(*capacity * 2); 63 buffer[*count] = val; 64 *count += 1; 65 } 66 67 /** 68 Add many values in a compile-time list 69 */ 70 void addAll(A...)(scope A a) 71 { 72 foreach(val; a) 73 { 74 add(val); 75 } 76 } 77 78 /* 79 Free the memory associated with the Array 80 */ 81 void destroy() 82 { 83 free(backingBuffer); 84 } 85 86 int opApply(int delegate(T) @nogc nothrow dg) const 87 { 88 for(size_t i = 0; i < length; i++) 89 dg(buffer[i]); 90 return 0; 91 } 92 93 void print() const 94 { 95 dgt.io.print("Array!", T.stringof, "["); 96 for(size_t i = 0; i < length; i++) 97 { 98 dgt.io.print(this[i]); 99 if(i != length - 1) 100 { 101 dgt.io.print(", "); 102 } 103 } 104 dgt.io.print("]"); 105 } 106 107 pure: 108 /** 109 Remove an element at an index by swapping it with the last element 110 */ 111 void remove(in size_t index) 112 { 113 buffer[index] = buffer[*count]; 114 *count -= 1; 115 } 116 117 ref T opIndex(in size_t index) const 118 { 119 assert(index < *count); 120 return buffer[index]; 121 } 122 123 ref T opIndexAssign(scope T value, in size_t index) 124 { 125 assert(index < *count); 126 return buffer[index] = value; 127 } 128 129 /** 130 Reset the element count without changing the size of the allocated chunk 131 */ 132 void clear() 133 { 134 *count = 0; 135 } 136 137 /** 138 Get the pointer to the elements stored in the buffer 139 */ 140 @property T* ptr() const { return buffer; } 141 /** 142 Find the number of valid elements currently in the array 143 */ 144 @property size_t length() const { return *count; } 145 ///Get the Array data as a D array 146 @property T[] array() const { return buffer[0..length]; } 147 148 private: 149 private @property size_t* count() const { return cast(size_t*) backingBuffer; } 150 private @property size_t* capacity() const { return count + 1; } 151 private @property T* buffer() const { return cast(T*)(capacity + 1); } 152 } 153 154 @nogc nothrow: 155 unittest 156 { 157 auto x = Array!int(4); 158 for(int i = 0; i < 17; i++) 159 { 160 x.add(i); 161 } 162 assert(x[0] == 0); 163 assert(x[16] == 16); 164 x.addAll(1, 2, 3, 4); 165 assert(x[x.length - 1] == 4); 166 assert(x.ptr[0] == 0); 167 x[0] = 5; 168 assert(x[0] == 5); 169 size_t length = x.length; 170 x.remove(0); 171 assert(x.length == length - 1); 172 int first = x[0]; 173 auto other = Array!int(1); 174 foreach(val; x) 175 { 176 other.add(val); 177 } 178 assert(x.length == other.length); 179 assert(other[5] == x[5]); 180 println(x); 181 x.clear(); 182 assert(x.length == 0); 183 auto array = x.array; 184 assert(array.length == x.length); 185 for(size_t i = 0; i < array.length; i++) 186 assert(array[i] == x[i]); 187 x.destroy(); 188 }