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 void reserve(size_t n) pure nothrow @trusted 108 { 109 if (isLarge) // currently large 110 { 111 if (n > smallLength) // large => large 112 { 113 large.resize(n); 114 } 115 else // large => small 116 { 117 // large => tmp 118 119 // temporary storage for small 120 debug { typeof(small) tmp; } 121 else { typeof(small) tmp = void; } 122 123 tmp[0 .. n] = large.ptr[0 .. n]; // large to temporary 124 tmp[n .. $] = 0; // zero remaining 125 126 // empty large 127 large.clear(); 128 129 // tmp => small 130 small[] = tmp[0 .. smallLength]; 131 132 isLarge = false; 133 } 134 } 135 else // currently small 136 { 137 if (n > smallLength) // small => large 138 { 139 typeof(small) tmp = small; // temporary storage for small 140 141 import core.lifetime : emplace; 142 emplace(&large, n); 143 144 large.ptr[0 .. length] = tmp[0 .. length]; // temporary to large 145 146 isLarge = true; // tag as large 147 } 148 else {} // small => small 149 } 150 } 151 152 /// Get pointer. 153 auto ptr() pure nothrow @nogc 154 { 155 import nxt.container_traits : ContainerElementType; 156 alias ET = ContainerElementType!(typeof(this), E); 157 return isLarge ? cast(ET*)large.ptr : cast(ET*)&small; 158 } 159 160 /// Get slice. 161 auto ref slice() pure nothrow @nogc 162 { 163 return ptr[0 .. length]; 164 } 165 166 union 167 { 168 Small!(E, smallLength) small; // small variant 169 Large!(E, useGCallocation) large; // large variant 170 } 171 bool isLarge; // TODO: make part of union as in rcstring.d 172 } 173 174 /// Test `Store`. 175 static void storeTester(E, bool useGCallocation)() 176 { 177 Store!(E, useGCallocation) si; 178 179 assert(si.ptr !is null); 180 assert(si.slice.ptr !is null); 181 assert(si.slice.length != 0); 182 assert(si.length == si.smallLength); 183 184 si.reserve(si.smallLength); // max small 185 assert(si.length == si.smallLength); 186 assert(si.isSmall); 187 188 si.reserve(si.smallLength + 1); // small to large 189 assert(si.length == si.smallLength + 1); 190 assert(si.isLarge); 191 192 si.reserve(si.smallLength * 8); // small to large 193 assert(si.length == si.smallLength * 8); 194 assert(si.isLarge); 195 196 si.reserve(si.smallLength); // max small 197 assert(si.length == si.smallLength); 198 assert(si.isSmall); 199 200 si.reserve(0); 201 assert(si.length == si.smallLength); 202 assert(si.isSmall); 203 204 si.reserve(si.smallLength + 1); 205 assert(si.length == si.smallLength + 1); 206 assert(si.isLarge); 207 208 si.reserve(si.smallLength); 209 assert(si.length == si.smallLength); 210 assert(si.isSmall); 211 212 si.reserve(si.smallLength - 1); 213 assert(si.length == si.smallLength); 214 assert(si.isSmall); 215 } 216 217 pure nothrow @nogc unittest 218 { 219 import std.meta : AliasSeq; 220 foreach (E; AliasSeq!(char, byte, short, int)) 221 { 222 storeTester!(E, false); 223 } 224 } 225 226 pure nothrow unittest 227 { 228 import std.meta : AliasSeq; 229 foreach (E; AliasSeq!(char, byte, short, int)) 230 { 231 storeTester!(E, true); 232 } 233 }