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 pure nothrow @safe @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 	import std.meta : AliasSeq;
218 	foreach (E; AliasSeq!(char, byte, short, int))
219 	{
220 		storeTester!(E, false);
221 	}
222 }
223 
224 pure nothrow unittest {
225 	import std.meta : AliasSeq;
226 	foreach (E; AliasSeq!(char, byte, short, int))
227 	{
228 		storeTester!(E, true);
229 	}
230 }