1 /// Copyright: Copyright (c) 2017-2019 Andrey Penechko. 2 /// License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 3 /// Authors: Andrey Penechko. 4 module vox.utils.array; 5 6 // Optimal for 1, 2, 4 byte items. 7 // Best with POT sized items 8 // Can store inline up to 8 bytes 9 struct Array(T) 10 { 11 import vox.utils : isPowerOfTwo, nextPOT, divCeil, min, max, writefln, ArrayArena, format; 12 13 // Can be 0 14 enum uint NUM_INLINE_ITEMS = size_t.sizeof / T.sizeof; 15 enum uint MIN_EXTERNAL_BYTES = max(ArrayArena.MIN_BLOCK_BYTES, nextPOT((NUM_INLINE_ITEMS + 1) * T.sizeof)); 16 17 private uint _length; 18 private uint _capacity = NUM_INLINE_ITEMS; 19 20 union 21 { 22 // Used when length <= NUM_INLINE_ITEMS 23 private T[NUM_INLINE_ITEMS] inlineItems; 24 25 // Used when length > NUM_INLINE_ITEMS 26 private T* externalArray; 27 } 28 29 bool empty() { return _length == 0; } 30 uint length() { return _length; } 31 uint opDollar() { return _length; } 32 uint capacity() { return _capacity; } 33 ref T front() { return this[0]; } 34 ref T back() { return this[$-1]; } 35 void clear() { _length = 0; } 36 37 ref T opIndex(size_t index) 38 { 39 assert(index < _capacity, format("opIndex(%s), capacity %s", index, _capacity)); 40 static if (NUM_INLINE_ITEMS > 0) { 41 if (_capacity == NUM_INLINE_ITEMS) return inlineItems[index]; 42 } 43 44 return externalArray[index]; 45 } 46 47 Array!T dup(ref ArrayArena arena) 48 { 49 Array!T copy = this; 50 51 static if (NUM_INLINE_ITEMS > 0) { 52 if (_capacity == NUM_INLINE_ITEMS) return copy; 53 } 54 55 size_t byteCapacity = nextPOT(_capacity * T.sizeof); 56 57 // When we have empty array with NUM_INLINE_ITEMS == 0 and no allocated external array 58 if (byteCapacity == 0) return copy; 59 60 ubyte[] block = (cast(ubyte*)externalArray)[0..byteCapacity]; 61 62 ubyte[] newBlock = arena.allocBlock(block.length); 63 newBlock[] = block; 64 copy.externalArray = cast(T*)newBlock.ptr; 65 return copy; 66 } 67 68 void voidPut(ref ArrayArena arena, uint howMany) 69 { 70 if (_length + howMany > _capacity) extend(arena, howMany); 71 _length += howMany; 72 } 73 74 void put(ref ArrayArena arena, T[] items...) 75 { 76 if (_length + items.length > _capacity) extend(arena, cast(uint)items.length); 77 78 _length += items.length; 79 this[_length-items.length..$][] = items; 80 } 81 82 void putFront(ref ArrayArena arena, T item) 83 { 84 putAt(arena, 0, item); 85 } 86 87 // shifts items to the right 88 void putAt(ref ArrayArena arena, size_t at, T[] items...) 89 { 90 replaceAt(arena, at, 0, items); 91 } 92 93 void replaceAt(ref ArrayArena arena, size_t at, size_t numItemsToRemove, T[] itemsToInsert) 94 { 95 assert(at + numItemsToRemove <= _length); 96 97 size_t numItemsToInsert = itemsToInsert.length; 98 99 replaceAtVoid(arena, at, numItemsToRemove, numItemsToInsert); 100 this[at..at+numItemsToInsert][] = itemsToInsert; 101 } 102 103 void replaceAtVoid(ref ArrayArena arena, size_t at, size_t numItemsToRemove, size_t numItemsToInsert) 104 { 105 assert(at + numItemsToRemove <= _length); 106 107 if (numItemsToInsert == numItemsToRemove) 108 { 109 // no resize or moves needed 110 } 111 else 112 { 113 ptrdiff_t delta = numItemsToInsert - numItemsToRemove; 114 115 if (_length + delta > _capacity) extend(arena, cast(uint)delta); 116 117 scope(exit) _length += delta; 118 119 size_t start = at + numItemsToRemove; 120 size_t numItemsToMove = _length - start; 121 T* ptr = externalArray + start; 122 123 static if (NUM_INLINE_ITEMS > 0) { 124 if (_capacity == NUM_INLINE_ITEMS) ptr = inlineItems.ptr + start; 125 } 126 127 import core.stdc.string : memmove; 128 memmove(ptr + delta, ptr, numItemsToMove * T.sizeof); 129 } 130 } 131 132 void unput(size_t numItems) 133 { 134 _length = cast(uint)(_length - numItems); 135 } 136 137 void reserve(ref ArrayArena arena, uint howMany) 138 { 139 if (_length + howMany > _capacity) extend(arena, howMany); 140 } 141 142 // returns memory to arena and zeroes the length 143 void free(ref ArrayArena arena) { 144 scope(exit) { 145 externalArray = null; 146 _length = 0; 147 _capacity = NUM_INLINE_ITEMS; 148 } 149 static if (NUM_INLINE_ITEMS > 0) { 150 if (_capacity == NUM_INLINE_ITEMS) return; // no-op 151 } 152 153 size_t byteCapacity = nextPOT(_capacity * T.sizeof); 154 ubyte[] oldBlock = (cast(ubyte*)externalArray)[0..byteCapacity]; 155 arena.freeBlock(oldBlock); 156 } 157 158 // extend the storage 159 private void extend(ref ArrayArena arena, uint items) 160 { 161 uint byteCapacityNeeded = cast(uint)nextPOT((_length + items) * T.sizeof); 162 //writefln("extend %s", _capacity); 163 if (_capacity == NUM_INLINE_ITEMS) { 164 ubyte[] newBlock = arena.allocBlock(max(byteCapacityNeeded, MIN_EXTERNAL_BYTES)); 165 static if (NUM_INLINE_ITEMS > 0) { 166 ubyte[] oldBlock = cast(ubyte[])inlineItems[]; 167 newBlock[0..oldBlock.length] = oldBlock; 168 } 169 externalArray = cast(T*)newBlock.ptr; 170 _capacity = cast(uint)(newBlock.length / T.sizeof); 171 //writefln(" 1 cap %s", _capacity); 172 return; 173 } 174 175 size_t byteCapacity = nextPOT(_capacity * T.sizeof); 176 ubyte[] block = (cast(ubyte*)externalArray)[0..byteCapacity]; 177 resizeSmallArray(arena, block, byteCapacityNeeded); 178 externalArray = cast(T*)block.ptr; 179 _capacity = cast(uint)(block.length / T.sizeof); 180 //writefln(" 2 cap %s", _capacity); 181 } 182 183 // Doubles the size of block 184 private void resizeSmallArray(ref ArrayArena arena, ref ubyte[] oldBlock, size_t newLength) { 185 assert(isPowerOfTwo(oldBlock.length)); 186 assert(oldBlock.length >= ArrayArena.MIN_BLOCK_BYTES); 187 assert(newLength >= ArrayArena.MIN_BLOCK_BYTES, "too small"); 188 189 ubyte[] newBlock = arena.allocBlock(newLength); 190 newBlock[0..oldBlock.length] = oldBlock; 191 arena.freeBlock(oldBlock); 192 oldBlock = newBlock; 193 } 194 195 inout(T)[] opSlice() inout 196 { 197 static if (NUM_INLINE_ITEMS > 0) { 198 if (_capacity == NUM_INLINE_ITEMS) return inlineItems.ptr[0.._length]; 199 } 200 return externalArray[0.._length]; 201 } 202 203 inout(T)[] opSlice(size_t from, size_t to) inout 204 { 205 return this[][from..to]; 206 } 207 208 void removeInPlace(size_t at) 209 { 210 if (at+1 != _length) 211 { 212 this[at] = this[_length-1]; 213 } 214 --_length; 215 } 216 217 void removeByShift(size_t at, size_t numToRemove = 1) 218 { 219 size_t to = at; 220 size_t from = at + numToRemove; 221 while(from < _length) 222 { 223 this[to] = this[from]; 224 ++to; 225 ++from; 226 } 227 _length -= numToRemove; 228 } 229 230 void toString(scope void delegate(const(char)[]) sink) const { 231 import std.format : formattedWrite; 232 sink("["); 233 size_t i; 234 foreach(const ref T item; opSlice()) { 235 if (i > 0) sink(", "); 236 sink.formattedWrite("%s", item); 237 ++i; 238 } 239 sink("]"); 240 } 241 } 242 243 struct InvertedArray(T) 244 { 245 import vox.utils : isPowerOfTwo, nextPOT, divCeil, min, max, writefln, ArrayArena, format; 246 import std.range : retro; 247 Array!(T) array; 248 249 bool empty() { return array._length == 0; } 250 uint length() { return array._length; } 251 uint opDollar() { return array._length; } 252 uint capacity() { return array._capacity; } 253 ref T front() { return array[$-1]; } 254 ref T back() { return array[0]; } 255 void clear() { array._length = 0; } 256 257 ref T opIndex(size_t index) 258 { 259 assert(index < array._capacity, format("opIndex(%s), capacity %s", index, array._capacity)); 260 static if (array.NUM_INLINE_ITEMS > 0) { 261 if (array._capacity == array.NUM_INLINE_ITEMS) return array.inlineItems[array._length - index - 1]; 262 } 263 264 return array.externalArray[array._length - index - 1]; 265 } 266 267 InvertedArray!T dup(ref ArrayArena arena) { 268 InvertedArray!T copy = this; 269 copy.array = copy.array.dup(arena); 270 return copy; 271 } 272 273 void put(ref ArrayArena arena, T item) { 274 array.putAt(arena, 0, item); 275 } 276 277 // shifts items to the right 278 void putAt(ref ArrayArena arena, size_t at, T[] items...) { 279 array.replaceAt(arena, array._length - at, 0, items); 280 } 281 282 void replaceAt(A)(ref ArrayArena arena, size_t at, size_t numItemsToRemove, A itemsToInsert) { 283 assert(at + numItemsToRemove <= array._length); 284 285 size_t numItemsToInsert = itemsToInsert.length; 286 287 array.replaceAtVoid(arena, array._length-at-numItemsToRemove, numItemsToRemove, numItemsToInsert); 288 foreach(i; 0..numItemsToInsert) 289 this[at+numItemsToInsert-i-1] = itemsToInsert[i]; 290 } 291 292 void unput(size_t numItems) { 293 array.removeByShift(0, numItems); 294 } 295 296 void reserve(ref ArrayArena arena, uint howMany) { 297 if (array._length + howMany > array._capacity) array.extend(arena, howMany); 298 } 299 300 // returns memory to arena and zeroes the length 301 void free(ref ArrayArena arena) { 302 array.free(arena); 303 } 304 305 auto opSlice() { 306 static if (array.NUM_INLINE_ITEMS > 0) { 307 if (array._capacity == array.NUM_INLINE_ITEMS) return array.inlineItems.ptr[0..array._length].retro; 308 } 309 return array.externalArray[0..array._length].retro; 310 } 311 312 auto opSlice(size_t from, size_t to) { 313 return this[][from..to]; 314 } 315 316 void removeByShift(size_t at, size_t numToRemove = 1) { 317 array.removeByShift(array._length - at - numToRemove, numToRemove); 318 } 319 320 void toString(scope void delegate(const(char)[]) sink) { 321 import std.format : formattedWrite; 322 sink("["); 323 size_t i; 324 foreach(ref T item; opSlice()) { 325 if (i > 0) sink(", "); 326 sink.formattedWrite("%s", item); 327 ++i; 328 } 329 sink("]"); 330 } 331 } 332