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 }