1 module nxt.storage; 2 3 /// Large array storage. 4 static struct Large(E, bool useGCallocation) 5 { 6 E* ptr; 7 size_t length; 8 9 import core.exception : onOutOfMemoryError; 10 static if (useGCallocation) 11 { 12 import core.memory : GC; 13 } 14 else 15 { 16 import core.memory : malloc = pureMalloc, realloc = pureRealloc; 17 } 18 19 pure nothrow: 20 21 static if (useGCallocation) 22 { 23 this(size_t n) 24 { 25 length = n; 26 ptr = cast(E*)GC.malloc(E.sizeof * length); 27 if (length >= 1 && ptr is null) 28 { 29 onOutOfMemoryError(); 30 } 31 } 32 void resize(size_t n) 33 { 34 length = n; 35 ptr = cast(E*)GC.realloc(ptr, E.sizeof * length); 36 if (length >= 1 && ptr is null) 37 { 38 onOutOfMemoryError(); 39 } 40 } 41 void clear() 42 { 43 GC.free(ptr); 44 debug ptr = null; 45 } 46 } 47 else 48 { 49 @nogc: 50 this(size_t n) 51 { 52 length = n; 53 ptr = cast(E*)malloc(E.sizeof * length); 54 if (length >= 1 && ptr is null) 55 { 56 onOutOfMemoryError(); 57 } 58 } 59 void resize(size_t n) 60 { 61 length = n; 62 ptr = cast(E*)realloc(ptr, E.sizeof * length); 63 if (length >= 1 && ptr is null) 64 { 65 onOutOfMemoryError(); 66 } 67 } 68 void clear() 69 { 70 import nxt.qcmeman : free; 71 free(ptr); 72 debug ptr = null; 73 } 74 } 75 } 76 77 /// Small array storage. 78 alias Small(E, size_t n) = E[n]; 79 80 /// Small-size-optimized (SSO) array store. 81 static struct Store(E, bool useGCallocation = false) 82 { 83 /** Fixed number elements that fit into small variant storage. */ 84 enum smallLength = Large!(E, useGCallocation).sizeof / E.sizeof; 85 86 /** Maximum number elements that fit into large variant storage. */ 87 enum maxLargeLength = size_t.max >> 8; 88 89 /// Destruct. 90 ~this() nothrow @trusted @nogc 91 { 92 if (isLarge) { large.clear; } 93 } 94 95 /// Get currently length at `ptr`. 96 size_t length() const @trusted pure nothrow @nogc 97 { 98 return isLarge ? large.length : smallLength; 99 } 100 101 /// Returns: `true` iff is small packed. 102 bool isSmall() const @safe pure nothrow @nogc { return !isLarge; } 103 104 private: 105 106 /// Reserve length to `n` elements starting at `ptr`. 107 size_t reserve(size_t n) pure nothrow @trusted 108 { 109 if (isLarge) // currently large 110 { 111 if (n > smallLength) // large => large 112 large.resize(n); 113 else // large => small 114 { 115 // large => tmp 116 117 // temporary storage for small 118 debug { typeof(small) tmp; } 119 else { typeof(small) tmp = void; } 120 121 tmp[0 .. n] = large.ptr[0 .. n]; // large to temporary 122 tmp[n .. $] = 0; // zero remaining 123 124 // empty large 125 large.clear(); 126 127 // tmp => small 128 small[] = tmp[0 .. smallLength]; 129 130 isLarge = false; 131 } 132 } 133 else // currently small 134 { 135 if (n > smallLength) // small => large 136 { 137 typeof(small) tmp = small; // temporary storage for small 138 139 import core.lifetime : emplace; 140 emplace(&large, n); 141 142 large.ptr[0 .. length] = tmp[0 .. length]; // temporary to large 143 144 isLarge = true; // tag as large 145 } 146 else {} // small => small 147 } 148 return length; 149 } 150 151 /// Get pointer. 152 auto ptr() pure nothrow @nogc 153 { 154 import nxt.container.traits : ContainerElementType; 155 alias ET = ContainerElementType!(typeof(this), E); 156 return isLarge ? cast(ET*)large.ptr : cast(ET*)&small; 157 } 158 159 /// Get slice. 160 auto ref slice() pure nothrow @nogc 161 { 162 return ptr[0 .. length]; 163 } 164 165 union 166 { 167 Small!(E, smallLength) small; // small variant 168 Large!(E, useGCallocation) large; // large variant 169 } 170 bool isLarge; // TODO: make part of union as in rcstring.d 171 } 172 173 /// Test `Store`. 174 static void storeTester(E, bool useGCallocation)() 175 { 176 Store!(E, useGCallocation) si; 177 178 assert(si.ptr !is null); 179 assert(si.slice.ptr !is null); 180 assert(si.slice.length != 0); 181 assert(si.length == si.smallLength); 182 183 si.reserve(si.smallLength); // max small 184 assert(si.length == si.smallLength); 185 assert(si.isSmall); 186 187 si.reserve(si.smallLength + 1); // small to large 188 assert(si.length == si.smallLength + 1); 189 assert(si.isLarge); 190 191 si.reserve(si.smallLength * 8); // small to large 192 assert(si.length == si.smallLength * 8); 193 assert(si.isLarge); 194 195 si.reserve(si.smallLength); // max small 196 assert(si.length == si.smallLength); 197 assert(si.isSmall); 198 199 si.reserve(0); 200 assert(si.length == si.smallLength); 201 assert(si.isSmall); 202 203 si.reserve(si.smallLength + 1); 204 assert(si.length == si.smallLength + 1); 205 assert(si.isLarge); 206 207 si.reserve(si.smallLength); 208 assert(si.length == si.smallLength); 209 assert(si.isSmall); 210 211 si.reserve(si.smallLength - 1); 212 assert(si.length == si.smallLength); 213 assert(si.isSmall); 214 } 215 216 pure nothrow @nogc unittest 217 { 218 import std.meta : AliasSeq; 219 foreach (E; AliasSeq!(char, byte, short, int)) 220 { 221 storeTester!(E, false); 222 } 223 } 224 225 pure nothrow unittest 226 { 227 import std.meta : AliasSeq; 228 foreach (E; AliasSeq!(char, byte, short, int)) 229 { 230 storeTester!(E, true); 231 } 232 }