1 module nxt.container.variant_arrays;
2 
3 // version = nxt_benchmark;
4 
5 // private mixin template VariantArrayOf(Type)
6 // {
7 //	 import nxt.container.dynamic_array : DynamicArray;
8 //	 DynamicArray!Type store;
9 // }
10 
11 /** Array storage of variants.
12 
13 	Enables lightweight storage of polymorphic objects.
14 
15 	Each element is indexed by a corresponding `VariantRef`.
16  */
17 struct VariantArrays(Types...) { /+ TODO: Change to AliasSeq TypesTuple, Allocatar = GCAllocator +/
18 	alias Size = size_t;
19 	alias Ref = VariantRef!(Size, Types);
20 
21 	import nxt.container.dynamic_array : DynamicArray;
22 	import std.experimental.allocator.mallocator : Mallocator;
23 
24 	/// Returns: array type (as a string) of `Type`.
25 	private static immutable(string) arrayTypeStringOfIndex(uint typeIndex)() {
26 		pragma(inline, true);
27 		return `DynamicArray!(Types[` ~ typeIndex.stringof ~ `], Mallocator)`; /+ TODO: Make Mallocator a parameter +/
28 	}
29 
30 	/** Returns: array instance (as a strinng) storing `SomeKind`.
31 	 * TODO: make this a template mixin
32 	 */
33 	private static immutable(string) arrayInstanceString(SomeKind)()
34 	if (Ref.canReferenceType!SomeKind) {
35 		pragma(inline, true);
36 		return `_store` ~ Ref.nrOfKind!(SomeKind).stringof; // previously `SomeKind.mangleof`
37 	}
38 
39 	/// Make reference to type `SomeKind` at offset `index`.
40 	static Ref makeRef(SomeKind)(Ref.Size index)
41 	if (Ref.canReferenceType!SomeKind) {
42 		pragma(inline, true);
43 		return Ref(Ref.nrOfKind!SomeKind, index);
44 	}
45 
46 	/** Insert `value` at back.
47 	 */
48 	Ref insertBack(SomeKind)(SomeKind value) /+ TODO: add array type overload +/
49 	if (Ref.canReferenceType!SomeKind) {
50 		mixin(`alias arrayInstance = ` ~ arrayInstanceString!SomeKind ~ `;`);
51 		const currentIndex = arrayInstance.length;
52 		arrayInstance.insertBackMove(value);
53 		return Ref(Ref.nrOfKind!SomeKind, currentIndex);
54 	}
55 	alias put = insertBack;	 // polymorphic `OutputRange` support
56 
57 	/** Move (emplace) `value` into back.
58 	 */
59 	Ref insertBackMove(SomeKind)(ref SomeKind value) /+ TODO: add array type overload +/
60 	if (Ref.canReferenceType!SomeKind) {
61 		version (DigitalMars) pragma(inline, false); // DMD cannot inline
62 		mixin(`alias arrayInstance = ` ~ arrayInstanceString!SomeKind ~ `;`);
63 		const currentIndex = arrayInstance.length;
64 		arrayInstance.insertBackMove(value);
65 		return Ref(Ref.nrOfKind!SomeKind, currentIndex);
66 	}
67 
68 	/// ditto
69 	void opOpAssign(string op, SomeKind)(SomeKind value) /+ TODO: add array type overload +/
70 	if (op == "~" &&
71 		Ref.canReferenceType!SomeKind) {
72 		pragma(inline, true);
73 		insertBackMove(value);  // move enables uncopyable types
74 	}
75 
76 	/// Get reference to element of type `SomeKind` at `index`.
77 	ref inout(SomeKind) at(SomeKind)(in size_t index) inout return scope
78 	if (Ref.canReferenceType!SomeKind) {
79 		pragma(inline, true);
80 		mixin(`return ` ~ arrayInstanceString!SomeKind ~ `[index];`);
81 	}
82 
83 	/// Get reference to element of type `SomeKind` at `ref_`.
84 	scope ref inout(SomeKind) at(SomeKind)(in Ref ref_) inout return
85 	if (Ref.canReferenceType!SomeKind) {
86 		pragma(inline, true);
87 		assert(Ref.nrOfKind!SomeKind == ref_.kindNr,
88 			   "Ref is not of expected template type " ~ SomeKind.stringof);
89 		mixin(`return ` ~ arrayInstanceString!SomeKind ~ `[ref_.index];`);
90 	}
91 
92 	/// Peek at element of type `SomeKind` at `ref_`.
93 	inout(SomeKind)* peek(SomeKind)(in Ref ref_) inout return @system
94 	if (Ref.canReferenceType!SomeKind) {
95 		pragma(inline, true);
96 		if (Ref.nrOfKind!SomeKind == ref_._word.kindNr)
97 			return &at!SomeKind(ref_._word.index);
98 		else
99 			return null;
100 	}
101 
102 	/// Constant access to all elements of type `SomeKind`.
103 	inout(SomeKind)[] allOf(SomeKind)() inout return scope
104 	if (Ref.canReferenceType!SomeKind) {
105 		pragma(inline, true);
106 		mixin(`return ` ~ arrayInstanceString!SomeKind ~ `[];`);
107 	}
108 
109 	/// Reserve space for `newCapacity` elements of type `SomeKind`.
110 	size_t reserve(SomeKind)(in size_t newCapacity)
111 	if (Ref.canReferenceType!SomeKind) {
112 		pragma(inline, true);
113 		mixin(`alias arrayInstance = ` ~ arrayInstanceString!SomeKind ~ `;`);
114 		return arrayInstance.reserve(newCapacity);
115 	}
116 
117 	/** Returns: length of store. */
118 	@property size_t length() const
119 	{
120 		pragma(inline, true);
121 		typeof(return) lengthSum = 0;
122 		foreach (Type; Types)
123 			mixin(`lengthSum += ` ~ arrayInstanceString!Type ~ `.length;`);
124 		return lengthSum;
125 	}
126 
127 	/** Check if empty. */
128 	bool empty() const @property
129 	{
130 		pragma(inline, true);
131 		return length == 0;
132 	}
133 
134 private:
135 	// static foreach (const typeIndex, Type; Types)
136 	// {
137 	//	 /+ TODO: is it better to use?: mixin VariantArrayOf!(Type); +/
138 	//	 mixin(arrayTypeStringOfIndex!typeIndex ~ ` ` ~ arrayInstanceString!Type ~ `;`);
139 	// }
140 	mixin({
141 		string s = "";
142 		foreach (const typeIndex, Type; Types)
143 			s ~= arrayTypeStringOfIndex!typeIndex ~ ` ` ~ arrayInstanceString!Type ~ `;`;
144 		return s;
145 	}());
146 }
147 
148 /** Typed index (reference) into an element in `VariantArrays`.
149  *
150  * TODO: merge with soa.d?
151  */
152 private struct VariantRef(Size, DefinedTypes...) {
153 	import std.meta : staticIndexOf;
154 
155 	alias Kind = ubyte;			  // kind code
156 
157 	import nxt.bit_traits : bitsNeeded;
158 
159 	/// Used to indicate undefined value.
160 	private struct Undefined {}
161 
162 	import std.meta : AliasSeq;
163 
164 	alias Types = AliasSeq!(Undefined, DefinedTypes);
165 
166 	/// Number of bits needed to represent kind.
167 	private enum kindBits = bitsNeeded!(Types.length);
168 
169 	/** Get number kind of kind type `SomeKind`.
170 		TODO: make private?
171 	 */
172 	enum nrOfKind(SomeKind) = staticIndexOf!(SomeKind, Types); /+ TODO: cast to ubyte if Types.length is <= 256 +/
173 
174 	/// Is `true` iff an index to a `SomeKind`-kind can be stored.
175 	enum canReferenceType(SomeKind) = nrOfKind!SomeKind >= 0;
176 
177 	/// Comparsion works like for integers.
178 	int opCmp(in typeof(this) rhs) const @trusted
179 	{
180 		version (LDC) pragma(inline, true);
181 		if (this._rawWord < rhs._rawWord)
182 			return -1;
183 		if (this._rawWord > rhs._rawWord)
184 			return +1;
185 		return 0;
186 	}
187 
188 	pragma(inline, true):
189 
190 	/// Construct from mutable `that`.
191 	this(in typeof(this) that) {
192 		this._rawWord = that._rawWord;
193 	}
194 	/// Construct from constant `that`.
195 	this(typeof(this) that) {
196 		this._rawWord = that._rawWord;
197 	}
198 
199 	/// Construct.
200 	this(Kind kind, Size index) /+ TODO: can ctor inferred by bitfields? +/
201 	{
202 		this._word.kindNr = kind;
203 		this._word.index = index;
204 	}
205 
206 	/// Construct from raw word representation `rawWord`.
207 	private this(Size rawWord) {
208 		this._rawWord = rawWord;
209 	}
210 
211 	/// Get kindNr.
212 	Kind kindNr() const => _word.kindNr;
213 
214 	/// Get index.
215 	Size index() const => _word.index;
216 
217 	/// Cast to `size_t`.
218 	size_t opCast(T : size_t)() const => _rawWord;
219 
220 	import core.internal.traits : Unqual;
221 
222 	/// Allow cast to unqualified.
223 	U opCast(U : Unqual!(typeof(this)))() const => U(rawWord);
224 
225 	/// The index itself is the hash.
226 	hash_t toHash() const @property => _rawWord;
227 	static assert(hash_t.sizeof == _rawWord.sizeof);
228 
229 	/// Cast to `bool`, meaning 'true' if defined, `false` otherwise.
230 	bool opCast(U : bool)() const => isDefined();
231 
232 	/// Returns: `true` iff is defined.
233 	bool isDefined() const => _rawWord != 0;
234 
235 	/// Returns: `true` iff `this` targets a value of type `SomeKind`.
236 	public bool isA(SomeKind)() const => nrOfKind!(SomeKind) == _word.kindNr;
237 
238 	void toString(Sink)(ref scope Sink sink) const @trusted {
239 		import std.format : formattedWrite;
240 		if (isDefined)
241 			sink.formattedWrite!`%s(%s@%s)`(Unqual!(typeof(this)).stringof, _word.index, _word.kindNr);
242 		else
243 			sink.formattedWrite!`%s(null)`(Unqual!(typeof(this)).stringof);
244 	}
245 
246 	private struct Word {
247 		import nxt.dip_traits : hasPreviewBitfields;
248 		version (LittleEndian) {
249 			static if (hasPreviewBitfields) {
250 				/+ TODO: remove mixins when -preview=bitfields is in stable dmd +/
251 				mixin("Kind kindNr:kindBits;");
252 				mixin("Size index:8*Size.sizeof - kindBits;");
253 			} else {
254 				import std.bitmanip : bitfields;
255 				mixin(bitfields!(Kind, "kindNr", kindBits,
256 								 Size, "index", 8*Size.sizeof - kindBits));
257 			}
258 		} else {
259 			static assert(0, "TODO: BigEndian support");
260 		}
261 	}
262 
263 	private union {
264 		Word _word;
265 		Size _rawWord;			// for comparsion
266 	}
267 
268 	// static assert(this.sizeof == Size.sizeof,
269 	//			   `This should haven't any memory overhead compared to size_t`);
270 }
271 
272 pure nothrow @safe unittest {
273 	alias R = VariantRef!(size_t, int, float);
274 	R r;
275 	static assert(r.canReferenceType!(int));
276 	static assert(r.canReferenceType!(float));
277 	static assert(!r.canReferenceType!(short));
278 
279 	import std.array : Appender;
280 	Appender!(const(R)[]) app;
281 	assert(app.data.length == 0);
282 
283 	const R x;
284 	R mx = x;
285 	assert(x == mx);
286 
287 	/+ TODO: app ~= x; +/
288 
289 	// const y = [R.init, R.init];
290 	/+ TODO: app ~= y; +/
291 }
292 
293 unittest {
294 	import std.conv : to;
295 	alias R = VariantRef!(size_t, int, float);
296 	R r;
297 	assert(r.to!string == R.stringof~`(null)`);
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 }