1 module nxt.container.variant_arrays;
2 
3 // version = nxt_benchmark;
4 
5 /** Typed index (reference) into an element in `VariantArrays`.
6  *
7  * TODO: merge with soa.d?
8  */
9 private struct VariantRef(Size, DefinedTypes...) {
10 	import std.meta : staticIndexOf;
11 
12 	alias Kind = ubyte;			  // kind code
13 
14 	import nxt.bit_traits : bitsNeeded;
15 
16 	/// Used to indicate undefined value.
17 	private struct Undefined {}
18 
19 	import std.meta : AliasSeq;
20 
21 	alias Types = AliasSeq!(Undefined, DefinedTypes);
22 
23 	/// Number of bits needed to represent kind.
24 	private enum kindBits = bitsNeeded!(Types.length);
25 
26 	/** Get number kind of kind type `SomeKind`.
27 		TODO: make private?
28 	 */
29 	enum nrOfKind(SomeKind) = staticIndexOf!(SomeKind, Types); /+ TODO: cast to ubyte if Types.length is <= 256 +/
30 
31 	/// Is `true` iff an index to a `SomeKind`-kind can be stored.
32 	enum canReferenceType(SomeKind) = nrOfKind!SomeKind >= 0;
33 
34 	/// Comparsion works like for integers.
35 	int opCmp(in typeof(this) rhs) const @trusted
36 	{
37 		version (LDC) pragma(inline, true);
38 		if (this._rawWord < rhs._rawWord)
39 			return -1;
40 		if (this._rawWord > rhs._rawWord)
41 			return +1;
42 		return 0;
43 	}
44 
45 	pragma(inline, true):
46 
47 	/// Construct from mutable `that`.
48 	this(in typeof(this) that) {
49 		this._rawWord = that._rawWord;
50 	}
51 	/// Construct from constant `that`.
52 	this(typeof(this) that) {
53 		this._rawWord = that._rawWord;
54 	}
55 
56 	/// Construct.
57 	this(Kind kind, Size index) /+ TODO: can ctor inferred by bitfields? +/
58 	{
59 		this._word.kindNr = kind;
60 		this._word.index = index;
61 	}
62 
63 	/// Construct from raw word representation `rawWord`.
64 	private this(Size rawWord) {
65 		this._rawWord = rawWord;
66 	}
67 
68 	/// Get kindNr.
69 	Kind kindNr() const => _word.kindNr;
70 
71 	/// Get index.
72 	Size index() const => _word.index;
73 
74 	/// Cast to `size_t`.
75 	size_t opCast(T : size_t)() const => _rawWord;
76 
77 	import core.internal.traits : Unqual;
78 
79 	/// Allow cast to unqualified.
80 	U opCast(U : Unqual!(typeof(this)))() const => U(rawWord);
81 
82 	/// The index itself is the hash.
83 	hash_t toHash() const @property => _rawWord;
84 	static assert(hash_t.sizeof == _rawWord.sizeof);
85 
86 	/// Cast to `bool`, meaning 'true' if defined, `false` otherwise.
87 	bool opCast(U : bool)() const => isDefined();
88 
89 	/// Returns: `true` iff is defined.
90 	bool isDefined() const => _rawWord != 0;
91 
92 	/// Returns: `true` iff `this` targets a value of type `SomeKind`.
93 	public bool isA(SomeKind)() const => nrOfKind!(SomeKind) == _word.kindNr;
94 
95 	void toString(Sink)(ref scope Sink sink) const @trusted {
96 		import std.format : formattedWrite;
97 		if (isDefined)
98 			sink.formattedWrite!`%s(%s@%s)`(Unqual!(typeof(this)).stringof, _word.index, _word.kindNr);
99 		else
100 			sink.formattedWrite!`%s(null)`(Unqual!(typeof(this)).stringof);
101 	}
102 
103 	private struct Word {
104 		import nxt.dip_traits : hasPreviewBitfields;
105 		version (LittleEndian) {
106 			static if (hasPreviewBitfields) {
107 				/+ TODO: remove mixins when -preview=bitfields is in stable dmd +/
108 				mixin("Kind kindNr:kindBits;");
109 				mixin("Size index:8*Size.sizeof - kindBits;");
110 			} else {
111 				import std.bitmanip : bitfields;
112 				mixin(bitfields!(Kind, "kindNr", kindBits,
113 								 Size, "index", 8*Size.sizeof - kindBits));
114 			}
115 		} else {
116 			static assert(0, "TODO: BigEndian support");
117 		}
118 	}
119 
120 	private union {
121 		Word _word;
122 		Size _rawWord;			// for comparsion
123 	}
124 
125 	// static assert(this.sizeof == Size.sizeof,
126 	//			   `This should haven't any memory overhead compared to size_t`);
127 }
128 
129 pure nothrow @safe unittest {
130 	alias R = VariantRef!(size_t, int, float);
131 	R r;
132 	static assert(r.canReferenceType!(int));
133 	static assert(r.canReferenceType!(float));
134 	static assert(!r.canReferenceType!(short));
135 
136 	import std.array : Appender;
137 	Appender!(const(R)[]) app;
138 	assert(app.data.length == 0);
139 
140 	const R x;
141 	R mx = x;
142 	assert(x == mx);
143 
144 	/+ TODO: app ~= x; +/
145 
146 	// const y = [R.init, R.init];
147 	/+ TODO: app ~= y; +/
148 }
149 
150 unittest {
151 	import std.conv : to;
152 	alias R = VariantRef!(size_t, int, float);
153 	R r;
154 	assert(r.to!string == R.stringof~`(null)`);
155 }
156 
157 // private mixin template VariantArrayOf(Type)
158 // {
159 //	 import nxt.container.dynamic_array : DynamicArray;
160 //	 DynamicArray!Type store;
161 // }
162 
163 /** Stores set of variants.
164 
165 	Enables lightweight storage of polymorphic objects.
166 
167 	Each element is indexed by a corresponding `VariantRef`.
168  */
169 struct VariantArrays(Types...) { /+ TODO: Change to AliasSeq TypesTuple, Allocatar = GCAllocator +/
170 	alias Size = size_t;
171 	alias Ref = VariantRef!(Size, Types);
172 
173 	import nxt.container.dynamic_array : DynamicArray;
174 	import std.experimental.allocator.mallocator : Mallocator;
175 
176 	/// Returns: array type (as a string) of `Type`.
177 	private static immutable(string) arrayTypeStringOfIndex(uint typeIndex)() {
178 		pragma(inline, true);
179 		return `DynamicArray!(Types[` ~ typeIndex.stringof ~ `], Mallocator)`; /+ TODO: Make Mallocator a parameter +/
180 	}
181 
182 	/** Returns: array instance (as a strinng) storing `SomeKind`.
183 	 * TODO: make this a template mixin
184 	 */
185 	private static immutable(string) arrayInstanceString(SomeKind)()
186 	if (Ref.canReferenceType!SomeKind) {
187 		pragma(inline, true);
188 		return `_store` ~ Ref.nrOfKind!(SomeKind).stringof; // previously `SomeKind.mangleof`
189 	}
190 
191 	/// Make reference to type `SomeKind` at offset `index`.
192 	static Ref makeRef(SomeKind)(Ref.Size index)
193 	if (Ref.canReferenceType!SomeKind) {
194 		pragma(inline, true);
195 		return Ref(Ref.nrOfKind!SomeKind, index);
196 	}
197 
198 	/** Insert `value` at back.
199 	 */
200 	Ref insertBack(SomeKind)(SomeKind value) /+ TODO: add array type overload +/
201 	if (Ref.canReferenceType!SomeKind) {
202 		mixin(`alias arrayInstance = ` ~ arrayInstanceString!SomeKind ~ `;`);
203 		const currentIndex = arrayInstance.length;
204 		arrayInstance.insertBackMove(value);
205 		return Ref(Ref.nrOfKind!SomeKind, currentIndex);
206 	}
207 	alias put = insertBack;	 // polymorphic `OutputRange` support
208 
209 	/** Move (emplace) `value` into back.
210 	 */
211 	Ref insertBackMove(SomeKind)(ref SomeKind value) /+ TODO: add array type overload +/
212 	if (Ref.canReferenceType!SomeKind) {
213 		version (DigitalMars) pragma(inline, false); // DMD cannot inline
214 		mixin(`alias arrayInstance = ` ~ arrayInstanceString!SomeKind ~ `;`);
215 		const currentIndex = arrayInstance.length;
216 		arrayInstance.insertBackMove(value);
217 		return Ref(Ref.nrOfKind!SomeKind, currentIndex);
218 	}
219 
220 	/// ditto
221 	void opOpAssign(string op, SomeKind)(SomeKind value) /+ TODO: add array type overload +/
222 	if (op == "~" &&
223 		Ref.canReferenceType!SomeKind) {
224 		pragma(inline, true);
225 		insertBackMove(value);  // move enables uncopyable types
226 	}
227 
228 	/// Get reference to element of type `SomeKind` at `index`.
229 	ref inout(SomeKind) at(SomeKind)(in size_t index) inout return scope
230 	if (Ref.canReferenceType!SomeKind) {
231 		pragma(inline, true);
232 		mixin(`return ` ~ arrayInstanceString!SomeKind ~ `[index];`);
233 	}
234 
235 	/// Get reference to element of type `SomeKind` at `ref_`.
236 	scope ref inout(SomeKind) at(SomeKind)(in Ref ref_) inout return
237 	if (Ref.canReferenceType!SomeKind) {
238 		pragma(inline, true);
239 		assert(Ref.nrOfKind!SomeKind == ref_.kindNr,
240 			   "Ref is not of expected template type " ~ SomeKind.stringof);
241 		mixin(`return ` ~ arrayInstanceString!SomeKind ~ `[ref_.index];`);
242 	}
243 
244 	/// Peek at element of type `SomeKind` at `ref_`.
245 	inout(SomeKind)* peek(SomeKind)(in Ref ref_) inout return @system
246 	if (Ref.canReferenceType!SomeKind) {
247 		pragma(inline, true);
248 		if (Ref.nrOfKind!SomeKind == ref_._word.kindNr)
249 			return &at!SomeKind(ref_._word.index);
250 		else
251 			return null;
252 	}
253 
254 	/// Constant access to all elements of type `SomeKind`.
255 	inout(SomeKind)[] allOf(SomeKind)() inout return scope
256 	if (Ref.canReferenceType!SomeKind) {
257 		pragma(inline, true);
258 		mixin(`return ` ~ arrayInstanceString!SomeKind ~ `[];`);
259 	}
260 
261 	/// Reserve space for `newCapacity` elements of type `SomeKind`.
262 	size_t reserve(SomeKind)(in size_t newCapacity)
263 	if (Ref.canReferenceType!SomeKind) {
264 		pragma(inline, true);
265 		mixin(`alias arrayInstance = ` ~ arrayInstanceString!SomeKind ~ `;`);
266 		return arrayInstance.reserve(newCapacity);
267 	}
268 
269 	/** Returns: length of store. */
270 	@property size_t length() const
271 	{
272 		pragma(inline, true);
273 		typeof(return) lengthSum = 0;
274 		foreach (Type; Types)
275 			mixin(`lengthSum += ` ~ arrayInstanceString!Type ~ `.length;`);
276 		return lengthSum;
277 	}
278 
279 	/** Check if empty. */
280 	bool empty() const @property
281 	{
282 		pragma(inline, true);
283 		return length == 0;
284 	}
285 
286 private:
287 	// static foreach (const typeIndex, Type; Types)
288 	// {
289 	//	 /+ TODO: is it better to use?: mixin VariantArrayOf!(Type); +/
290 	//	 mixin(arrayTypeStringOfIndex!typeIndex ~ ` ` ~ arrayInstanceString!Type ~ `;`);
291 	// }
292 	mixin({
293 		string s = "";
294 		foreach (const typeIndex, Type; Types)
295 			s ~= arrayTypeStringOfIndex!typeIndex ~ ` ` ~ arrayInstanceString!Type ~ `;`;
296 		return s;
297 	}());
298 }
299 
300 /** Minimalistic fixed-length (static) array of (`capacity`) number of elements
301  * of type `E` where length only fits in an `ubyte` for compact packing.
302  */
303 version (unittest)
304 private struct MinimalStaticArray(E, ubyte capacity) {
305 	this(in E[] es) {
306 		assert(es.length <= capacity,
307 			   "Length of input parameter `es` is larger than capacity "
308 			   ~ capacity.stringof);
309 		_es[0 .. es.length] = es;
310 		_length = cast(typeof(_length))es.length;
311 	}
312 
313 private:
314 	E[capacity] _es;
315 	typeof(capacity) _length;
316 }
317 
318 pure nothrow @safe @nogc unittest {
319 	const ch7 = MinimalStaticArray!(char, 7)(`1234567`);
320 	assert(ch7._es[] == `1234567`);
321 }
322 
323 ///
324 pure nothrow @safe @nogc unittest {
325 	alias Chars(uint capacity) = MinimalStaticArray!(char, capacity);
326 	alias Chars7 = Chars!7;
327 	alias Chars15 = Chars!15;
328 	alias VA = VariantArrays!(ulong,
329 							  Chars7,
330 							  Chars15);
331 
332 	VA data;
333 	assert(data.length == 0);
334 	assert(data.empty);
335 
336 	const i0 = data.put(ulong(13));
337 	assert(cast(size_t)i0 == 1);
338 
339 	assert(i0.isA!ulong);
340 	assert(data.at!ulong(0) == ulong(13));
341 	assert(data.length == 1);
342 	assert(!data.empty);
343 	assert(data.allOf!ulong == [ulong(13)].s);
344 
345 	const i1 = data.put(Chars7(`1234567`));
346 	assert(cast(size_t)i1 == 2);
347 
348 	// same order as in `Types`
349 	assert(i0 < i1);
350 
351 	assert(i1.isA!(Chars7));
352 	assert(data.at!(Chars7)(0) == Chars7(`1234567`));
353 	assert(data.allOf!(Chars7) == [Chars7(`1234567`)].s);
354 	assert(data.length == 2);
355 
356 	const i2 = data.put(Chars15(`123`));
357 	assert(cast(size_t)i2 == 3);
358 
359 	// same order as in `Types`
360 	assert(i0 < i2);
361 	assert(i1 < i2);
362 
363 	assert(i2.isA!(Chars15));
364 	assert(data.at!(Chars15)(0) == Chars15(`123`));
365 	/+ TODO: assert(data.allOf!(Chars15) == [Chars15(`123`)].s); +/
366 	assert(data.length == 3);
367 
368 	const i3 = data.put(Chars15(`1234`));
369 	assert(cast(size_t)i3 == 7);
370 
371 	// same order as in `Types`
372 	assert(i0 < i3);
373 	assert(i1 < i3);
374 	assert(i2 < i3);			// same type, i2 added before i3
375 
376 	assert(i3.isA!(Chars15));
377 	assert(data.at!(Chars15)(1) == Chars15(`1234`));
378 	assert(data.allOf!(Chars15) == [Chars15(`123`), Chars15(`1234`)].s);
379 	assert(data.length == 4);
380 }
381 
382 // version = extraTests;
383 
384 version (extraTests) {
385 static private:
386 	alias S = VariantArrays!(Rel1, Rel2, Int);
387 
388 	// relations
389 	struct Rel1 { S.Ref[1] args; }
390 	struct Rel2 { S.Ref[2] args; }
391 
392 	struct Int { int value; }
393 }
394 
395 ///
396 version (extraTests)
397 pure nothrow @safe @nogc unittest {
398 	S s;
399 
400 	const S.Ref top = s.put(Rel1(s.put(Rel1(s.put(Rel2([s.put(Int(42)),
401 														s.put(Int(43))]))))));
402 	assert(top);
403 	assert(s.allOf!Rel1.length == 2);
404 	assert(s.allOf!Rel2.length == 1);
405 	assert(s.allOf!Int.length == 2);
406 	assert(s.length == 5);
407 }
408 
409 /// put and peek
410 version (extraTests)
411 @system pure nothrow @nogc unittest {
412 	S s;
413 	const n = 10;
414 	foreach (const i; 0 .. n) {
415 		S.Ref lone = s.put(Int(i));
416 		Int* lonePtr = s.peek!Int(lone);
417 		assert(lonePtr);
418 		assert(*lonePtr == Int(i));
419 	}
420 	assert(s.length == 10);
421 }
422 
423 version (unittest) {
424 	import nxt.array_help : s;
425 }
426 
427 version (nxt_benchmark)
428 unittest {
429 	import std.stdio : writeln;
430 	import std.datetime : MonoTime;
431 	import std.meta : AliasSeq;
432 	alias E = uint;
433 	immutable n = 5_000_000;
434 	foreach (A; AliasSeq!(VariantArrays!(E))) {
435 		A a;
436 		immutable before = MonoTime.currTime();
437 		foreach (uint i; 0 .. n)
438 			a ~= i;
439 		immutable after = MonoTime.currTime();
440 		writeln("Added ", n, " integer nodes into ", A.stringof, " in ", after - before);
441 	}
442 
443 }
444 
445 /** Store array of `E[]` with different lengths compactly.
446     When length of `E` is known at compile-time store it as a static array.
447  */
448 private struct HybridArrayArray(E) { /+ TODO: make useful and then public +/
449 @safe pure:
450 	void insertBack(E[] value) {
451 		switch (value.length) {
452 			/+ TODO: static foreach +/
453 		case 1: _store.insertBack(cast(char[1])value[0 .. 1]); break;
454 		case 2: _store.insertBack(cast(char[2])value[0 .. 2]); break;
455 		case 3: _store.insertBack(cast(char[3])value[0 .. 3]); break;
456 		case 4: _store.insertBack(cast(char[4])value[0 .. 4]); break;
457 		case 5: _store.insertBack(cast(char[5])value[0 .. 5]); break;
458 		case 6: _store.insertBack(cast(char[6])value[0 .. 6]); break;
459 		case 7: _store.insertBack(cast(char[7])value[0 .. 7]); break;
460 		case 8: _store.insertBack(cast(char[8])value[0 .. 8]); break;
461 		default:
462 			_store.insertBack(value);
463 		}
464 	}
465 private:
466 	VariantArrays!(char[1],
467 				   char[2],
468 				   char[3],
469 				   char[4],
470 				   char[5],
471 				   char[6],
472 				   char[7],
473 				   char[8],
474 				   E[]) _store;
475 }
476 
477 ///
478 pure nothrow @safe @nogc unittest {
479 	alias E = char;
480 	HybridArrayArray!(E) ss;
481 	ss.insertBack(E[].init);
482 }