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 }