1 module auxil.model;
2 
3 import std.traits : isInstanceOf;
4 import taggedalgebraic : TaggedAlgebraic, taget = get;
5 import auxil.traits;
6 
7 version(unittest) import unit_threaded : Name;
8 
9 struct FixedAppender(size_t Size)
10 {
11 	void put(char c) pure
12 	{
13 		import std.exception : enforce;
14 
15 		enforce(size < Size);
16 		buffer[size++] = c;
17 	}
18 
19 	void put(scope const(char)[] s) pure
20 	{
21 		import std.exception : enforce;
22 
23 		enforce(size + s.length <= Size);
24 		foreach(c; s)
25 			buffer[size++] = c;
26 	}
27 
28 	@property size_t length() const @safe nothrow @nogc pure
29 	{
30 		return size;
31 	}
32 
33 	string opSlice() return scope pure nothrow @property
34 	{
35 		import std.exception : assumeUnique;
36 		assert(size <= Size);
37 		return buffer[0..size].assumeUnique;
38 	}
39 
40 	void clear() @safe nothrow @nogc pure
41 	{
42 		size = 0;
43 	}
44 
45 private:
46 	char[Size] buffer;
47 	size_t size;
48 }
49 
50 version(unittest) @Name("modelHasCollapsed")
51 @safe
52 unittest
53 {
54 	import unit_threaded : should, be;
55 
56 	static struct Test
57 	{
58 		float f = 7.7;
59 		int i = 8;
60 		string s = "some text";
61 	}
62 
63 	static struct StructWithStruct
64 	{
65 		double d = 8.8;
66 		long l = 999;
67 		Test t;
68 	}
69 
70 	static class TestClass
71 	{
72 
73 	}
74 
75 	static struct StructWithPointerAndClass
76 	{
77 		double* d;
78 		TestClass tc;
79 	}
80 
81 	static struct StructWithNestedClass
82 	{
83 		TestClass tc;
84 	}
85 
86 	// check if Model!T has collapsed member
87 	enum modelHasCollapsed(T) = is(typeof(Model!T.collapsed) == bool);
88 
89 	// Model of plain old data has no collapsed member
90 	assert(!modelHasCollapsed!float);
91 	// Model of structures has collapsed member
92 	assert( modelHasCollapsed!Test );
93 	assert( modelHasCollapsed!StructWithStruct);
94 	// Model of unprocessible structures and classes do not
95 	// exist so they have nothing
96 	assert(!modelHasCollapsed!TestClass);
97 	assert(!modelHasCollapsed!StructWithPointerAndClass);
98 	assert(!modelHasCollapsed!StructWithNestedClass);
99 
100 	import std.traits : FieldNameTuple;
101 	FieldNameTuple!(Model!StructWithStruct).length.should.be == 6;
102 }
103 
104 private import std.range : isRandomAccessRange;
105 private import std.traits : isSomeString, isStaticArray, isAssociativeArray;
106 private enum dataHasStaticArrayModel(T) = isStaticArray!T;
107 private enum dataHasAssociativeArrayModel(T) = isAssociativeArray!T;
108 private enum dataHasRandomAccessRangeModel(T) = isRandomAccessRange!T && !isSomeString!T && !dataHasTaggedAlgebraicModel!T;
109 private enum dataHasAggregateModel(T) = (is(T == struct) || is(T == union)) && !dataHasRandomAccessRangeModel!T && !dataHasTaggedAlgebraicModel!T;
110 private enum dataHasTaggedAlgebraicModel(T) = is(T == struct) && isInstanceOf!(TaggedAlgebraic, T);
111 
112 mixin template State()
113 {
114 	enum Spacing = 1;
115 	double size = 0, header_size = 0;
116 	int _placeholder = 1 << Field.Collapsed | 
117 	                   1 << Field.Enabled;
118 
119 	private enum Field { Collapsed, Enabled, }
120 
121 	@property void collapsed(bool v)
122 	{
123 		if (collapsed != v)
124 		{
125 			if (v)
126 				_placeholder |=   1 << Field.Collapsed;
127 			else
128 				_placeholder &= ~(1 << Field.Collapsed);
129 		}
130 	}
131 	@property bool collapsed() const { return (_placeholder & (1 << Field.Collapsed)) != 0; }
132 
133 	@property void enabled(bool v)
134 	{
135 		if (enabled != v)
136 		{
137 			if (v)
138 				_placeholder |=   1 << Field.Enabled;
139 			else
140 				_placeholder &= ~(1 << Field.Enabled);
141 		}
142 	}
143 	@property bool enabled() const { return (_placeholder & (1 << Field.Enabled)) != 0; }
144 }
145 
146 template Model(alias A)
147 {
148 	import std.typecons : Nullable;
149 	import std.datetime : Duration;
150 
151 	static if (dataHasStaticArrayModel!(TypeOf!A))
152 		alias Model = StaticArrayModel!A;
153 	else static if (dataHasRandomAccessRangeModel!(TypeOf!A))
154 		alias Model = RaRModel!A;
155 	else static if (dataHasAssociativeArrayModel!(TypeOf!A))
156 		alias Model = AssocArrayModel!A;
157 	else static if (dataHasTaggedAlgebraicModel!(TypeOf!A))
158 		alias Model = TaggedAlgebraicModel!A;
159 	else static if (dataHasAggregateModel!(TypeOf!A) && hasRenderedAs!A)
160 		alias Model = RenderedAsAggregateModel!A;
161 	else static if (dataHasAggregateModel!(TypeOf!A) && hasRenderedAsMember!(TypeOf!A))
162 		alias Model = RenderedAsMemberAggregateModel!A;
163 	else static if (dataHasAggregateModel!(TypeOf!A) && getRenderedAsMemberString!A.length == 1)
164 		alias Model = RenderedAsMemberStringAggregateModel!A;
165 	else static if (dataHasAggregateModel!(TypeOf!A) && getRenderedAsPointeeString!A.length == 1)
166 		alias Model = RenderedAsPointeeStringModel!A;
167 	else static if (is(TypeOf!A : Duration))
168 		alias Model = DurationModel!A;
169 	else static if (isNullable!(TypeOf!A))
170 		alias Model = NullableModel!A;
171 	else static if (isTimemarked!(TypeOf!A))
172 		alias Model = TimemarkedModel!A;
173 	else static if (dataHasAggregateModel!(TypeOf!A))
174 		alias Model = AggregateModel!A;
175 	else
176 		alias Model = ScalarModel!A;
177 }
178 
179 struct StaticArrayModel(alias A)// if (dataHasStaticArrayModel!(TypeOf!A))
180 {
181 	enum Collapsable = true;
182 
183 	mixin State;
184 
185 	alias Data = TypeOf!A;
186 	static assert(isProcessible!Data);
187 
188 	alias ElementType = typeof(Data.init[0]);
189 	Model!ElementType[Data.length] model;
190 	alias model this;
191 
192 	this()(const(Data) data) if (Data.sizeof <= (void*).sizeof)
193 	{
194 		foreach(i; 0..data.length)
195 			model[i] = Model!ElementType(data[i]);
196 	}
197 
198 	this()(ref const(Data) data) if (Data.sizeof > (void*).sizeof)
199 	{
200 		foreach(i; 0..data.length)
201 			model[i] = Model!ElementType(data[i]);
202 	}
203 
204 	mixin visitImpl;
205 }
206 
207 struct RaRModel(alias A)// if (dataHasRandomAccessRangeModel!(TypeOf!A))
208 {
209 	import automem : Vector;
210 	import std.experimental.allocator.mallocator : Mallocator;
211 
212 	enum Collapsable = true;
213 
214 	mixin State;
215 
216 	alias Data = TypeOf!A;
217 	static assert(isProcessible!Data);
218 
219 	alias ElementType = typeof(Data.init[0]);
220 	Vector!(Model!ElementType, Mallocator) model;
221 	alias model this;
222 
223 	this()(const(Data) data) if (Data.sizeof <= (void*).sizeof)
224 	{
225 		update(data);
226 	}
227 
228 	this()(ref const(Data) data) if (Data.sizeof > (void*).sizeof)
229 	{
230 		update(data);
231 	}
232 
233 	void update(ref const(Data) data)
234 	{
235 		model.length = data.length;
236 		foreach(i, ref e; model)
237 			e = Model!ElementType(data[i]);
238 	}
239 
240 	void update(T)(ref TaggedAlgebraic!T v)
241 	{
242 		update(taget!Data(v));
243 	}
244 
245 	mixin visitImpl;
246 }
247 
248 struct AssocArrayModel(alias A)// if (dataHasAssociativeArrayModel!(TypeOf!A))
249 {
250 	import automem : Vector;
251 	import std.experimental.allocator.mallocator : Mallocator;
252 
253 	enum Collapsable = true;
254 
255 	static assert(dataHasAssociativeArrayModel!(TypeOf!A));
256 
257 	mixin State;
258 
259 	alias Data = TypeOf!A;
260 	alias Key = typeof(Data.init.byKey.front);
261 	static assert(isProcessible!Data);
262 
263 	alias ElementType = typeof(Data.init[0]);
264 	Vector!(Model!ElementType, Mallocator) model;
265 	Vector!(Key, Mallocator) keys;
266 	alias model this;
267 
268 	this()(const(Data) data) if (Data.sizeof <= (void*).sizeof)
269 	{
270 		update(data);
271 	}
272 
273 	this()(ref const(Data) data) if (Data.sizeof > (void*).sizeof)
274 	{
275 		update(data);
276 	}
277 
278 	void update(ref const(Data) data)
279 	{
280 		model.length = data.length;
281 		keys.reserve(data.length);
282 		foreach(k; data.byKey)
283 			keys ~= k;
284 		foreach(i, ref e; model)
285 			e = Model!ElementType(data[keys[i]]);
286 	}
287 
288 	void update(T)(ref TaggedAlgebraic!T v)
289 	{
290 		update(taget!Data(v));
291 	}
292 
293 	mixin visitImpl;
294 }
295 
296 private enum isCollapsable(T) = is(typeof(T.Collapsable)) && T.Collapsable;
297 
298 struct TaggedAlgebraicModel(alias A)// if (dataHasTaggedAlgebraicModel!(TypeOf!A))
299 {
300 	alias Data = TypeOf!A;
301 	static assert(isProcessible!Data);
302 
303 	import std.traits : Fields;
304 	import std.meta : anySatisfy;
305 	enum Collapsable = anySatisfy!(isCollapsable, Fields!Payload);
306 
307 	private static struct Payload
308 	{
309 		static foreach(i, fname; Data.UnionType.fieldNames)
310 		{
311 			mixin("Model!(Data.UnionType.FieldTypes[__traits(getMember, Data.Kind, fname)]) " ~ fname ~ ";");
312 		}
313 	}
314 
315 	static struct TAModel
316 	{
317 		TaggedAlgebraic!Payload value;
318 		alias value this;
319 
320 		@property void collapsed(bool v)
321 		{
322 			final switch(value.kind)
323 			{
324 				foreach (i, FT; value.UnionType.FieldTypes)
325 				{
326 					case __traits(getMember, value.Kind, value.UnionType.fieldNames[i]):
327 						static if (is(typeof(taget!FT(value).collapsed) == bool))
328 							taget!FT(value).collapsed = v;
329 					return;
330 				}
331 			}
332 			assert(0); // never reached
333 		}
334 
335 		@property bool collapsed() const
336 		{
337 			final switch(value.kind)
338 			{
339 				foreach (i, FT; value.UnionType.FieldTypes)
340 				{
341 					case __traits(getMember, value.Kind, value.UnionType.fieldNames[i]):
342 						static if (is(typeof(taget!FT(value).collapsed) == bool))
343 							return taget!FT(value).collapsed;
344 						else
345 							assert(0);
346 				}
347 			}
348 			assert(0); // never reached
349 		}
350 
351 		@property size() const
352 		{
353 			final switch(value.kind)
354 			{
355 				foreach (i, FT; value.UnionType.FieldTypes)
356 				{
357 					case __traits(getMember, value.Kind, value.UnionType.fieldNames[i]):
358 						return taget!FT(value).size;
359 				}
360 			}
361 			assert(0);
362 		}
363 
364 		this(T)(T v)
365 		{
366 			value = v;
367 		}
368 	}
369 	TAModel tamodel;
370 	alias tamodel this;
371 
372 	/// returns a model corresponding to given data value
373 	static TAModel makeModel(ref const(Data) data)
374 	{
375 		final switch(data.kind)
376 		{
377 			foreach (i, FT; data.UnionType.FieldTypes)
378 			{
379 				case __traits(getMember, data.Kind, data.UnionType.fieldNames[i]):
380 					return TAModel(Model!FT(data.taget!FT));
381 			}
382 		}
383 	}
384 
385 	this()(auto ref const(Data) data)
386 	{
387 		tamodel = makeModel(data);
388 	}
389 
390 	bool visit(Order order, Visitor)(ref const(Data) data, ref Visitor visitor)
391 	{
392 		dbgPrint!(true, true)(" dataHasTaggedAlgebraicModel!Data");
393 		final switch (data.kind) {
394 			foreach (i, fname; Data.UnionType.fieldNames)
395 			{
396 				case __traits(getMember, data.Kind, fname):
397 					if (taget!(this.UnionType.FieldTypes[i])(tamodel).visit!order(
398 							taget!(Data.UnionType.FieldTypes[i])(data),
399 							visitor,
400 						))
401 					{
402 						dbgPrint!(true, true)("premature quitting at ", __FILE__, ":", __LINE__);
403 						return true;
404 					}
405 				break;
406 			}
407 		}
408 		return false;
409 	}
410 }
411 
412 template AggregateModel(alias A) // if (dataHasAggregateModel!(TypeOf!A) && !is(TypeOf!A : Duration) && !hasRenderedAs!A)
413 {
414 	alias Data = TypeOf!A;
415 	static assert(isProcessible!Data);
416 
417 	static if (DrawableMembers!Data.length == 1)
418 	{
419 		struct SingleMemberAggregateModel(T)
420 		{
421 			enum member = DrawableMembers!T[0];
422 			alias Member = TypeOf!(mixin("T." ~ member));
423 			Model!Member single_member_model;
424 			alias single_member_model this;
425 
426 			enum Collapsable = single_member_model.Collapsable;
427 
428 			this()(auto ref const(T) data)
429 			{
430 				import std.format : format;
431 
432 				static if (isNullable!(typeof(mixin("data." ~ member))) ||
433 						isTimemarked!(typeof(mixin("data." ~ member))))
434 				{
435 					if (!mixin("data." ~ member).isNull)
436 						mixin("single_member_model = Model!Member(data.%1$s);".format(member));
437 				}
438 				else
439 					mixin("single_member_model = Model!Member(data.%1$s);".format(member));
440 			}
441 
442 			bool visit(Order order, Visitor)(auto ref const(T) data, ref Visitor visitor)
443 			{
444 				return single_member_model.visit!order(mixin("data." ~ member), visitor);
445 			}
446 		}
447 		alias AggregateModel = SingleMemberAggregateModel!Data;
448 	}
449 	else
450 	{
451 		struct AggregateModel
452 		{
453 			enum Collapsable = true;
454 
455 			import std.format : format;
456 
457 			mixin State;
458 
459 			import auxil.traits : DrawableMembers;
460 			static foreach(member; DrawableMembers!Data)
461 				mixin("Model!(Data.%1$s) %1$s;".format(member));
462 
463 			this()(auto ref const(Data) data)
464 			{
465 				foreach(member; DrawableMembers!Data)
466 				{
467 					static if (isNullable!(typeof(mixin("data." ~ member))) ||
468 							isTimemarked!(typeof(mixin("data." ~ member))))
469 					{
470 						if (mixin("data." ~ member).isNull)
471 							continue;
472 					}
473 					else
474 						mixin("this.%1$s = Model!(Data.%1$s)(data.%1$s);".format(member));
475 				}
476 			}
477 
478 			mixin visitImpl;
479 		}
480 	}
481 }
482 
483 struct RenderedAsAggregateModel(alias A)// if (dataHasAggregateModel!(TypeOf!A) && hasRenderedAs!A)
484 {
485 	import auxil.traits : getRenderedAs;
486 
487 	alias Data = TypeOf!A;
488 	static assert(isProcessible!Data);
489 
490 	alias Proxy = getRenderedAs!A;
491 	static assert(isProcessible!Proxy);
492 	Proxy proxy;
493 	Model!proxy proxy_model;
494 
495 	enum Collapsable = proxy_model.Collapsable;
496 
497 	alias proxy_model this;
498 
499 	this()(auto ref const(Data) data)
500 	{
501 		import std.conv : to;
502 		proxy = data.to!Proxy;
503 		proxy_model = Model!proxy(proxy);
504 	}
505 
506 	bool visit(Order order, Visitor)(auto ref const(Data) ignored_data, ref Visitor visitor)
507 	{
508 		return proxy_model.visit!order(proxy, visitor);
509 	}
510 }
511 
512 struct RenderedAsMemberAggregateModel(alias A)// if (dataHasAggregateModel!Data && hasRenderedAsMember!Data)
513 {
514 	import auxil.traits : getRenderedAs;
515 
516 	alias Data = TypeOf!A;
517 	static assert(isProcessible!Data);
518 
519 	enum member_name = getRenderedAsMember!Data;
520 	Model!(mixin("Data." ~ member_name)) model;
521 
522 	enum Collapsable = model.Collapsable;
523 
524 	alias model this;
525 
526 	this()(auto ref const(Data) data)
527 	{
528 		model = typeof(model)(mixin("data." ~ member_name));
529 	}
530 
531 	bool visit(Order order, Visitor)(auto ref const(Data) data, ref Visitor visitor)
532 	{
533 		return model.visit!order(mixin("data." ~ member_name), visitor);
534 	}
535 }
536 
537 struct RenderedAsMemberStringAggregateModel(alias A)// if (dataHasAggregateModel!Data && getRenderedAsMemberString!Data.length == 1)
538 {
539 	alias Data = TypeOf!A;
540 	static assert(isProcessible!Data);
541 
542 	enum member_name = getRenderedAsMemberString!A[0];
543 	Model!(mixin("Data." ~ member_name)) model;
544 
545 	enum Collapsable = model.Collapsable;
546 
547 	alias model this;
548 
549 	this()(auto ref const(Data) data)
550 	{
551 		model = typeof(model)(mixin("data." ~ member_name));
552 	}
553 
554 	bool visit(Order order, Visitor)(auto ref const(Data) data, ref Visitor visitor)
555 	{
556 		return model.visit!order(mixin("data." ~ member_name), visitor);
557 	}
558 }
559 
560 struct RenderedAsPointeeStringModel(alias A)
561 {
562 	alias Data = TypeOf!A;
563 	static assert(isProcessible!Data);
564 
565 	enum member_name = getRenderedAsPointeeString!A[0];
566 	Model!(typeof(mixin("*Data.init." ~ member_name))) model;
567 
568 	enum Collapsable = model.Collapsable;
569 
570 	alias model this;
571 
572 	this()(auto ref const(Data) data)
573 	{
574 		model = typeof(model)(*mixin("data." ~ member_name));
575 	}
576 
577 	bool visit(Order order, Visitor)(auto ref const(Data) data, ref Visitor visitor)
578 	{
579 		return model.visit!order(*mixin("data." ~ member_name), visitor);
580 	}
581 }
582 
583 struct DurationModel(alias A)
584 {
585 	import std.datetime : Duration;
586 
587 	alias Data = TypeOf!A;
588 	static assert(isProcessible!Data);
589 	static assert(is(TypeOf!A : Duration));
590 
591 	enum Collapsable = false;
592 
593 	alias Proxy = string;
594 	static assert(isProcessible!Proxy);
595 	Proxy proxy;
596 	Model!proxy proxy_model;
597 
598 	alias proxy_model this;
599 
600 	this()(auto ref const(Data) data)
601 	{
602 		import std.conv : to;
603 		proxy = data.to!Proxy;
604 		proxy_model = Model!proxy(proxy);
605 	}
606 
607 	bool visit(Order order, Visitor)(auto ref const(Data) data, ref Visitor visitor)
608 	{
609 		return proxy_model.visit!order(proxy, visitor);
610 	}
611 }
612 
613 struct NullableModel(alias A)
614 {
615 	import std.typecons : Nullable;
616 
617 	alias Data = TypeOf!A;
618 	static assert(isProcessible!Data);
619 	static assert(isInstanceOf!(Nullable, Data));
620 	alias Payload = typeof(Data.get);
621 
622 	enum Collapsable = true;
623 
624 	enum NulledPayload = __traits(identifier, A) ~ ": null";
625 	Model!string  nulled_model = makeModel(NulledPayload);
626 	Model!Payload nullable_model;
627 	private bool isNull;
628 
629 	alias nullable_model this;
630 
631 	@property auto size()
632 	{
633 		return (isNull) ? nulled_model.size : nullable_model.size;
634 	}
635 
636 	@property auto size(double v)
637 	{
638 		if (isNull)
639 			nulled_model.size = v;
640 		else
641 			nullable_model.size = v;
642 	}
643 
644 	this()(auto ref const(Data) data)
645 	{
646 		isNull = data.isNull;
647 		if (isNull)
648 			nullable_model = Model!Payload();
649 		else
650 			nullable_model = Model!Payload(data.get);
651 	}
652 
653 	bool visit(Order order, Visitor)(auto ref const(Data) data, ref Visitor visitor)
654 	{
655 		isNull = data.isNull;
656 		if (isNull)
657 			return nulled_model.visit!order(NulledPayload, visitor);
658 		else
659 			return nullable_model.visit!order(data.get, visitor);
660 	}
661 }
662 
663 private template NoInout(T)
664 {
665 	static if (is(T U == inout U))
666 		alias NoInout = U;
667 	else
668 		alias NoInout = T;
669 }
670 
671 version(HAVE_TIMEMARKED)
672 struct TimemarkedModel(alias A)
673 {
674 	import rdp.timemarked : Timemarked;
675 
676 	alias Data = TypeOf!A;
677 	static assert(isInstanceOf!(Timemarked, Data));
678 
679 	@("renderedAsPointee.payload")
680 	struct TimemarkedPayload
681 	{
682 		alias Payload = NoInout!(typeof(Data.value));
683 		const(Payload)* payload;
684 		static string prefix = __traits(identifier, A);
685 
686 		this(ref const(Payload) payload)
687 		{
688 			this.payload = &payload;
689 		}
690 	}
691 
692 	enum Collapsable = true;
693 
694 	enum NulledPayload = __traits(identifier, A) ~ ": null";
695 	Model!string nulled_model = Model!string(NulledPayload);
696 	Model!TimemarkedPayload timemarked_model;
697 	version(none) Model!(Data.value) value_model;
698 	version(none) Model!(Data.timestamp) timestamp_model;
699 	private bool isNull;
700 
701 	alias timemarked_model this;
702 
703 	@property auto size()
704 	{
705 		return (isNull) ? nulled_model.size : timemarked_model.size;
706 	}
707 
708 	@property auto size(double v)
709 	{
710 		if (isNull)
711 			nulled_model.size = v;
712 		else
713 			timemarked_model.size = v;
714 	}
715 
716 	this()(auto ref const(Data) data)
717 	{
718 		isNull = data.isNull;
719 		if (isNull)
720 			timemarked_model = Model!TimemarkedPayload();
721 		else
722 			timemarked_model = Model!TimemarkedPayload(data.value);
723 	}
724 
725 	bool visit(Order order, Visitor)(auto ref const(Data) data, ref Visitor visitor)
726 	{
727 		isNull = data.isNull;
728 		if (isNull)
729 			return nulled_model.visit!order(NulledPayload, visitor);
730 
731 		if (timemarked_model.visit!order(TimemarkedPayload(data.value), visitor))
732 			return true;
733 
734 		version(none)
735 		{
736 			visitor.indent;
737 			scope(exit) visitor.unindent;
738 
739 			if (timestamp_model.visit!order(data.timestamp, visitor))
740 				return true;
741 		}
742 
743 		return false;
744 	}
745 }
746 
747 struct ScalarModel(alias A)
748 	if (!dataHasAggregateModel!(TypeOf!A) && 
749 	    !dataHasStaticArrayModel!(TypeOf!A) &&
750 	    !dataHasRandomAccessRangeModel!(TypeOf!A) &&
751 	    !dataHasTaggedAlgebraicModel!(TypeOf!A) &&
752 	    !dataHasAssociativeArrayModel!(TypeOf!A))
753 {
754 	enum Spacing = 1;
755 	float size = 0;
756 
757 	enum Collapsable = false;
758 
759 	alias Data = TypeOf!A;
760 	static assert(isProcessible!Data);
761 
762 	this()(auto ref const(Data) data)
763 	{
764 	}
765 
766 	private bool visit(Order order, Visitor)(auto ref const(Data) data, ref Visitor visitor)
767 	{
768 		import std.algorithm : among;
769 
770 		enum Sinking     = order == Order.Sinking;
771 		enum Bubbling    = !Sinking; 
772 		enum hasTreePath = Visitor.treePathEnabled;
773 		enum hasSize     = Visitor.sizeEnabled;
774 
775 		static if (hasTreePath)
776 		{
777 			with(visitor) final switch(state)
778 			{
779 				case State.seeking:
780 					if (tree_path.value == path.value)
781 						state = State.first;
782 				break;
783 				case State.first:
784 					state = State.rest;
785 				break;
786 				case State.rest:
787 					// do nothing
788 				break;
789 				case State.finishing:
790 				{
791 					dbgPrint!(hasSize, hasTreePath)("state is State.finishing");
792 					return true;
793 				}
794 			}
795 		}
796 		if (visitor.complete)
797 		{
798 			dbgPrint!(hasSize, hasTreePath)("visitor.complete is true");
799 			return true;
800 		}
801 
802 		static if (hasTreePath) with(visitor) { dbgPrint!(hasSize, hasTreePath)(" no constraint ", position, " ", deferred_change, " ", destination, " ");}
803 		else {dbgPrint!(hasSize, hasTreePath)(" no constraint ", Data.stringof, " ", data);}
804 		static if (hasSize) this.size = visitor.size + this.Spacing;
805 		static if (hasTreePath) with(visitor) 
806 		{
807 			position += deferred_change;
808 			deferred_change = (Sinking) ? this.size : -this.size;
809 
810 			if (state.among(State.first, State.rest))
811 			{
812 				static if (Sinking) visitor.processLeaf!order(data, this);
813 				if ((Sinking  && position+deferred_change > destination) ||
814 					(Bubbling && position                 < destination))
815 				{
816 					dbgPrint!(hasSize, hasTreePath)("state becomes State.finishing at ", __FILE__, ":", __LINE__);
817 					state = State.finishing;
818 					path = tree_path;
819 				}
820 				static if (Bubbling) visitor.processLeaf!order(data, this);
821 			}
822 		}
823 		else
824 			visitor.processLeaf!order(data, this);
825 
826 		return false;
827 	}
828 }
829 
830 auto makeModel(T)(auto ref const(T) data)
831 {
832 	return Model!T(data);
833 }
834 
835 mixin template visitImpl()
836 {
837 	bool visit(Order order, Visitor)(ref const(Data) data, ref Visitor visitor)
838 		if (Data.sizeof > 24)
839 	{
840 		return baseVisit!order(data, visitor);
841 	}
842 
843 	bool visit(Order order, Visitor)(const(Data) data, ref Visitor visitor)
844 		if (Data.sizeof <= 24)
845 	{
846 		return baseVisit!order(data, visitor);
847 	}
848 
849 	bool baseVisit(Order order, Visitor)(auto ref const(Data) data, ref Visitor visitor)
850 	{
851 		static if (Data.sizeof > 24 && !__traits(isRef, data))
852 			pragma(msg, "Warning: ", Data, " is a value type and has size larger than 24 bytes");
853 
854 		// static assert(Data.sizeof <= 24 || __traits(isRef, data));
855 		import std.algorithm : among;
856 
857 		enum Sinking     = order == Order.Sinking;
858 		enum Bubbling    = !Sinking; 
859 		enum hasTreePath = Visitor.treePathEnabled;
860 		enum hasSize     = Visitor.sizeEnabled;
861 
862 		static if (hasTreePath)
863 		{
864 			with(visitor) final switch(state)
865 			{
866 				case State.seeking:
867 					if (tree_path.value == path.value)
868 						state = State.first;
869 				break;
870 				case State.first:
871 					state = State.rest;
872 				break;
873 				case State.rest:
874 					// do nothing
875 				break;
876 				case State.finishing:
877 				{
878 					dbgPrint!(hasSize, hasTreePath)("state is State.finishing");
879 					return true;
880 				}
881 			}
882 		}
883 		if (visitor.complete)
884 		{
885 			dbgPrint!(hasSize, hasTreePath)("visitor.complete is true");
886 			return true;
887 		}
888 
889 		static if (hasSize) size = header_size = visitor.size + Spacing;
890 
891 		static if (hasTreePath) with(visitor)
892 		{
893 			if (visitor.state.among(visitor.State.first, visitor.State.rest))
894 			{
895 				static if (Sinking)
896 				{
897 					visitor.position += visitor.deferred_change;
898 					visitor.deferred_change = this.header_size;
899 				}
900 				visitor.enterNode!(order, Data)(data, this);
901 				static if (Sinking)
902 				{
903 					if (position+deferred_change > destination)
904 					{
905 						dbgPrint!(hasSize, hasTreePath)("state becomes State.finishing [", visitor.tree_path, "] at ", __FILE__, ":", __LINE__);
906 						state = State.finishing;
907 						path = tree_path;
908 					}
909 				}
910 			}
911 		}
912 		else
913 			visitor.enterNode!(order, Data)(data, this);
914 
915 		scope(exit)
916 		{
917 			static if (hasTreePath) with(visitor)
918 			{
919 				if (state.among(State.first, State.rest))
920 				{
921 					static if (Bubbling)
922 					{
923 						position += deferred_change;
924 						deferred_change = -this.header_size;
925 						if (position <= destination)
926 						{
927 							dbgPrint!(hasSize, hasTreePath)("state becomes State.finishing at ", __FILE__, ":", __LINE__);
928 							state = State.finishing;
929 							path = tree_path;
930 						}
931 					}
932 					visitor.leaveNode!order(data, this);
933 				}
934 			}
935 			else
936 				visitor.leaveNode!order(data, this);
937 		}
938 
939 		dbgPrint!(hasSize, hasTreePath)(" ", Data.stringof);
940 		if (!this.collapsed)
941 		{
942 			visitor.indent;
943 			scope(exit) visitor.unindent;
944 
945 			static if (Bubbling && hasTreePath)
946 			{
947 				// Edge case if the start path starts from this collapsable exactly
948 				// then the childs of the collapsable aren't processed
949 				if (visitor.path.value.length && visitor.tree_path.value[] == visitor.path.value[])
950 				{
951 					dbgPrint!(hasSize, hasTreePath)("special edge case at ", __FILE__, ":", __LINE__);
952 					return false;
953 				}
954 			}
955 
956 			static if (hasTreePath) visitor.tree_path.put(0);
957 			static if (hasTreePath) scope(exit) visitor.tree_path.popBack;
958 			const len = getLength!(Data, data);
959 			static if (is(typeof(model.length)))
960 				assert(len == model.length);
961 			if (!len)
962 				return false;
963 
964 			size_t start_value;
965 			static if (Bubbling)
966 			{
967 				start_value = len;
968 				start_value--;
969 			}
970 			static if (hasTreePath)
971 			{
972 				if (visitor.state.among(visitor.State.seeking, visitor.State.first))
973 				{
974 					auto idx = visitor.tree_path.value.length;
975 					if (idx && visitor.path.value.length >= idx)
976 					{
977 						start_value = visitor.path.value[idx-1];
978 						// position should change only if we've got the initial path
979 						// and don't get the end
980 						if (visitor.state == visitor.State.seeking) visitor.deferred_change = 0;
981 					}
982 				}
983 			}
984 			static if (dataHasStaticArrayModel!Data || 
985 			           dataHasRandomAccessRangeModel!Data ||
986 			           dataHasAssociativeArrayModel!Data)
987 			{
988 				foreach(i; TwoFacedRange!order(start_value, data.length))
989 				{
990 					static if (hasTreePath) visitor.tree_path.back = i;
991 					static if (hasSize) scope(exit) this.size += model[i].size;
992 					auto idx = getIndex!(Data)(this, i);
993 					if (model[i].visit!order(data[idx], visitor))
994 					{
995 						dbgPrint!(hasSize, hasTreePath)("premature quitting at ", __FILE__, ":", __LINE__);
996 						return true;
997 					}
998 				}
999 			}
1000 			else static if (dataHasAggregateModel!Data)
1001 			{
1002 				scope(exit) dbgPrint!(hasSize, hasTreePath)("scope(exit) this.size: ", this.size);
1003 				switch(start_value)
1004 				{
1005 					static foreach(i; 0..len)
1006 					{
1007 						// reverse fields order if Order.Bubbling
1008 						case (Sinking) ? i : len - i - 1:
1009 						{
1010 							enum FieldNo = (Sinking) ? i : len - i - 1;
1011 							enum member = DrawableMembers!Data[FieldNo];
1012 							static if (hasTreePath) visitor.tree_path.back = cast(int) FieldNo;
1013 							static if (hasSize) scope(exit) this.size += mixin("this." ~ member).size;
1014 							dbgPrint!(hasSize, hasTreePath)("this.size: ", this.size);
1015 							scope(exit) dbgPrint!(hasSize, hasTreePath)("member.size: ", mixin("this." ~ member).size);
1016 							if (mixin("this." ~ member).visit!order(mixin("data." ~ member), visitor))
1017 							{
1018 								dbgPrint!(hasSize, hasTreePath)("premature quitting at ", __FILE__, ":", __LINE__);
1019 								return true;
1020 							}
1021 						}
1022 						goto case;
1023 					}
1024 					// the dummy case needed because every `goto case` should be followed by a case clause
1025 					case len:
1026 						// flow cannot get here directly
1027 						if (start_value == len)
1028 							assert(0);
1029 					break;
1030 					default:
1031 						assert(0);
1032 				}
1033 			}
1034 		}
1035 		else
1036 		{
1037 			dbgPrint!(hasSize, hasTreePath)("is collapsed");
1038 		}
1039 
1040 		return false;
1041 	}
1042 }
1043 
1044 private auto getIndex(Data, M)(ref M model, size_t i)
1045 {
1046 	static if (dataHasStaticArrayModel!Data || 
1047 	           dataHasRandomAccessRangeModel!Data ||
1048 	           dataHasAggregateModel!Data)
1049 		return i;
1050 	else static if (dataHasAssociativeArrayModel!Data)
1051 		return model.keys[i];
1052 	else
1053 		static assert(0);
1054 }
1055 
1056 private auto getLength(Data, alias data)()
1057 {
1058 	static if (dataHasStaticArrayModel!Data || 
1059 	           dataHasRandomAccessRangeModel!Data ||
1060 	           dataHasAssociativeArrayModel!Data)
1061 		return data.length;
1062 	else static if (dataHasAggregateModel!Data)
1063 		return DrawableMembers!Data.length;
1064 	else
1065 		static assert(0);
1066 }
1067 
1068 void dbgPrint(bool hasSize, bool hasTreePath, Args...)(Args args)
1069 {
1070 	version(none) static if (hasSize)
1071 	{
1072 		import std;
1073 		debug writeln(args);
1074 	}
1075 }
1076 
1077 private enum PropertyKind { setter, getter }
1078 
1079 auto setPropertyByTreePath(string propertyName, Value, Data, Model)(auto ref Data data, ref Model model, int[] path, Value value)
1080 {
1081 	auto pv = PropertyVisitor!(propertyName, Value)();
1082 	pv.path.value = path;
1083 	pv.value = value;
1084 	pv.propertyKind = PropertyKind.setter;
1085 	model.visitForward(data, pv);
1086 }
1087 
1088 auto getPropertyByTreePath(string propertyName, Value, Data, Model)(auto ref Data data, ref Model model, int[] path)
1089 {
1090 	auto pv = PropertyVisitor!(propertyName, Value)();
1091 	pv.path.value = path;
1092 	pv.propertyKind = PropertyKind.getter;
1093 	model.visitForward(data, pv);
1094 	return pv.value;
1095 }
1096 
1097 private struct PropertyVisitor(string propertyName, Value)
1098 {
1099 	import std.typecons : Nullable;
1100 
1101 	TreePathVisitor default_visitor;
1102 	alias default_visitor this;
1103 
1104 	PropertyKind propertyKind;
1105 	Nullable!Value value;
1106 	bool completed;
1107 
1108 	this(Value value)
1109 	{
1110 		this.value = value;
1111 	}
1112 
1113 	bool complete()
1114 	{
1115 		return completed;
1116 	}
1117 
1118 	void enterNode(Order order, Data, Model)(ref const(Data) data, ref Model model)
1119 	{
1120 		static if (is(typeof(mixin("model." ~ propertyName))))
1121 		{
1122 			if (propertyKind == PropertyKind.getter)
1123 				value = mixin("model." ~ propertyName);
1124 			else if (propertyKind == PropertyKind.setter)
1125 				mixin("model." ~ propertyName) = value.get;
1126 		}
1127 		else
1128 			value.nullify;
1129 
1130 		processLeaf!order(data, model);
1131 	}
1132 
1133 	void processLeaf(Order order, Data, Model)(ref const(Data) data, ref Model model)
1134 	{
1135 		assert(!completed);
1136 		completed = tree_path.value[] == path.value[];
1137 	}
1138 }
1139 
1140 void applyByTreePath(T, Data, Model)(auto ref Data data, ref Model model, const(int)[] path, void delegate(ref const(T) value) dg)
1141 {
1142 	auto pv = ApplyVisitor!T();
1143 	pv.path.value = path;
1144 	pv.dg = dg;
1145 	model.visitForward(data, pv);
1146 }
1147 
1148 private struct ApplyVisitor(T)
1149 {
1150 	import std.typecons : Nullable;
1151 
1152 	TreePathVisitor default_visitor;
1153 	alias default_visitor this;
1154 
1155 	void delegate(ref const(T) value) dg;
1156 	bool completed;
1157 
1158 	bool complete()
1159 	{
1160 		return completed;
1161 	}
1162 
1163 	void enterNode(Order order, Data, Model)(ref const(Data) data, ref Model model)
1164 	{
1165 		static if (is(Data == T))
1166 		{
1167 			completed = tree_path.value[] == path.value[];
1168 			if (completed)
1169 			{
1170 				dg(data);
1171 				return;
1172 			}
1173 		}
1174 
1175 		processLeaf!order(data, model);
1176 	}
1177 
1178 	void processLeaf(Order order, Data, Model)(ref const(Data) data, ref Model model)
1179 	{
1180 		static if (is(Data == T))
1181 		{
1182 			assert(!completed);
1183 			completed = tree_path.value[] == path.value[];
1184 			if (completed)
1185 				dg(data);
1186 		}
1187 	}
1188 }
1189 
1190 enum Order { Sinking, Bubbling, }
1191 
1192 private struct TwoFacedRange(Order order)
1193 {
1194 	int s, l;
1195 
1196 	@disable this();
1197 
1198 	this(size_t s, size_t l)
1199 	{
1200 		this.s = cast(int) s;
1201 		this.l = cast(int) l;
1202 	}
1203 
1204 	bool empty() const
1205 	{
1206 		return (-1 >= s) || (s >= l);
1207 	}
1208 
1209 	int front() const
1210 	{
1211 		assert(!empty);
1212 		return s;
1213 	}
1214 
1215 	void popFront()
1216 	{
1217 		assert(!empty);
1218 		static if (order == Order.Sinking)  s++; else
1219 		static if (order == Order.Bubbling) s--; else
1220 		static assert(0);
1221 	}
1222 }
1223 
1224 version(unittest) @Name("two_faced_range")
1225 unittest
1226 {
1227 	import unit_threaded;
1228 
1229 	int[] empty;
1230 
1231 	{
1232 		auto rf = TwoFacedRange!(Order.Sinking)(1, 2);
1233 		rf.should.be == [1];
1234 		// lower boundary is always inclusive
1235 		auto rb = TwoFacedRange!(Order.Bubbling)(1, 2);
1236 		rb.should.be == [1, 0];
1237 	}
1238 	{
1239 		auto rf = TwoFacedRange!(Order.Sinking)(2, 4);
1240 		rf.should.be == [2, 3];
1241 		// lower boundary is always inclusive
1242 		auto rb = TwoFacedRange!(Order.Bubbling)(2, 4);
1243 		rb.should.be == [2, 1, 0];
1244 	}
1245 	{
1246 		auto rf = TwoFacedRange!(Order.Sinking)(0, 4);
1247 		rf.should.be == [0, 1, 2, 3];
1248 		auto rb = TwoFacedRange!(Order.Bubbling)(0, 4);
1249 		rb.should.be == [0];
1250 	}
1251 	{
1252 		auto rf = TwoFacedRange!(Order.Sinking)(4, 4);
1253 		rf.should.be == empty;
1254 		auto rb = TwoFacedRange!(Order.Bubbling)(4, 4);
1255 		rb.should.be == empty;
1256 	}
1257 	{
1258 		auto rf = TwoFacedRange!(Order.Sinking)(0, 0);
1259 		rf.should.be == empty;
1260 		auto rb = TwoFacedRange!(Order.Bubbling)(0, 0);
1261 		rb.should.be == empty;
1262 	}
1263 }
1264 
1265 void visit(Model, Data, Visitor)(ref Model model, auto ref Data data, ref Visitor visitor, double destination)
1266 {
1267 	visitor.destination = destination;
1268 	if (destination == visitor.position)
1269 		return;
1270 	else if (destination < visitor.position)
1271 		model.visitBackward(data, visitor);
1272 	else
1273 		model.visitForward(data, visitor);
1274 }
1275 
1276 void visitForward(Model, Data, Visitor)(ref Model model, auto ref const(Data) data, ref Visitor visitor)
1277 {
1278 	enum order = Order.Sinking;
1279 	static if (Visitor.treePathEnabled)
1280 	{
1281 		visitor.state = (visitor.path.value.length) ? visitor.State.seeking : visitor.State.rest;
1282 		visitor.deferred_change = 0;
1283 	}
1284 	visitor.enterTree!order(data, model);
1285 	model.visit!order(data, visitor);
1286 }
1287 
1288 void visitBackward(Model, Data, Visitor)(ref Model model, auto ref Data data, ref Visitor visitor)
1289 {
1290 	enum order = Order.Bubbling;
1291 	static if (Visitor.treePathEnabled)
1292 	{
1293 		visitor.state = (visitor.path.value.length) ? visitor.State.seeking : visitor.State.rest;
1294 		visitor.deferred_change = 0;
1295 	}
1296 	visitor.enterTree!order(data, model);
1297 	model.visit!order(data, visitor);
1298 }
1299 
1300 struct TreePath
1301 {
1302 	import std.experimental.allocator.mallocator : Mallocator;
1303 	import automem.vector : Vector;
1304 
1305 @safe:
1306 
1307 	Vector!(int, Mallocator)  value;
1308 
1309 	ref int back() return @nogc
1310 	{
1311 		assert(value.length);
1312 		return value[$-1];
1313 	}
1314 
1315 	void popBack() @nogc
1316 	{
1317 		value.popBack;
1318 	}
1319 
1320 	void clear() @nogc
1321 	{
1322 		value.clear;
1323 	}
1324 
1325 	auto put(int i) @nogc @trusted
1326 	{
1327 		value.put(i);
1328 	}
1329 
1330 	import std.range : isOutputRange;
1331 	import std.format : FormatSpec;
1332 
1333 	void toString(Writer) (ref Writer w, scope const ref FormatSpec!char fmt) const  @trusted
1334 		if (isOutputRange!(Writer, char))
1335 	{
1336 		import std;
1337 		import std.conv : text;
1338 
1339 		w.put('[');
1340 		if (value.length)
1341 		{
1342 			foreach(e; value[0..$-1])
1343 				copy(text(e, "."), w);
1344 			copy(text(value[$-1]), w);
1345 		}
1346 		w.put(']');
1347 	}
1348 }
1349 
1350 version(unittest) @Name("null_visitor")
1351 unittest
1352 {
1353 	int[] data = [1, 2];
1354 	NullVisitor visitor;
1355 	auto model = makeModel(data);
1356 	model.visitForward(data, visitor);
1357 }
1358 
1359 import std.typecons : Flag;
1360 
1361 alias SizeEnabled     = Flag!"SizeEnabled";
1362 alias TreePathEnabled = Flag!"TreePathEnabled";
1363 
1364 alias NullVisitor      = DefaultVisitorImpl!(SizeEnabled.no,  TreePathEnabled.no );
1365 alias MeasuringVisitor = DefaultVisitorImpl!(SizeEnabled.yes, TreePathEnabled.no );
1366 alias TreePathVisitor  = DefaultVisitorImpl!(SizeEnabled.no,  TreePathEnabled.yes);
1367 alias DefaultVisitor   = DefaultVisitorImpl!(SizeEnabled.yes, TreePathEnabled.yes);
1368 
1369 /// Default implementation of Visitor
1370 struct DefaultVisitorImpl(
1371 	SizeEnabled _size_,
1372 	TreePathEnabled _tree_path_,
1373 )
1374 {
1375 	alias sizeEnabled     = _size_;
1376 	alias treePathEnabled = _tree_path_;
1377 
1378 	alias SizeType = double;
1379 	static if (sizeEnabled == SizeEnabled.yes)
1380 	{
1381 		SizeType size;
1382 
1383 		this(SizeType s) @safe @nogc nothrow
1384 		{
1385 			size = s;
1386 		}
1387 	}
1388 
1389 	static if (treePathEnabled == TreePathEnabled.yes)
1390 	{
1391 		enum State { seeking, first, rest, finishing, }
1392 		State state;
1393 		TreePath tree_path, path;
1394 		SizeType position, deferred_change, destination;
1395 	}
1396 
1397 	void indent() {}
1398 	void unindent() {}
1399 	bool complete() @safe @nogc { return false; }
1400 	void enterTree(Order order, Data, Model)(auto ref const(Data) data, ref Model model) {}
1401 	void enterNode(Order order, Data, Model)(ref const(Data) data, ref Model model) {}
1402 	void leaveNode(Order order, Data, Model)(ref const(Data) data, ref Model model) {}
1403 	void processLeaf(Order order, Data, Model)(ref const(Data) data, ref Model model) {}
1404 }
1405 
1406 version(unittest) @Name("MeasuringVisitor")
1407 unittest
1408 {
1409 	import std.algorithm : map;
1410 	import unit_threaded : should, be;
1411 
1412 	auto data = [0, 1, 2, 3];
1413 	auto model = makeModel(data);
1414 	auto visitor = MeasuringVisitor(9);
1415 
1416 	model.collapsed = false;
1417 	model.visitForward(data, visitor);
1418 
1419 	model.size.should.be == 50;
1420 	model[].map!"a.size".should.be == [10, 10, 10, 10];
1421 }
1422 
1423 version(unittest) @Name("union")
1424 unittest
1425 {
1426 	union U
1427 	{
1428 		int i;
1429 		double d;
1430 	}
1431 
1432 	U u;
1433 	auto m = makeModel(u);
1434 }
1435 
1436 version(unittest) @Name("DurationTest")
1437 unittest
1438 {
1439 	import std.datetime : Duration;
1440 	import std.meta : AliasSeq;
1441 
1442 	@renderedAs!string
1443 	static struct DurationProxy
1444 	{
1445 		@ignored
1446 		Duration ptr;
1447 
1448 		this(Duration d)
1449 		{
1450 			ptr = d;
1451 		}
1452  
1453 		string opCast(T : string)()
1454 		{
1455 			return ptr.toISOExtString;
1456 		}
1457 	}
1458 
1459 	static struct Test
1460 	{
1461 		@renderedAs!DurationProxy
1462 		Duration d;
1463 	}
1464 
1465 	Test test;
1466 	auto m = makeModel(test);
1467 
1468 	import std.traits : FieldNameTuple;
1469 	import std.meta : AliasSeq;
1470 
1471 	static assert(FieldNameTuple!(typeof(m))                                 == AliasSeq!("single_member_model"));
1472 	static assert(FieldNameTuple!(typeof(m.single_member_model))             == AliasSeq!("proxy", "proxy_model"));
1473 	static assert(FieldNameTuple!(typeof(m.single_member_model.proxy))       == AliasSeq!(""));
1474 	static assert(FieldNameTuple!(typeof(m.single_member_model.proxy_model)) == AliasSeq!("size"));
1475 
1476 	@renderedAs!string
1477 	Duration d;
1478 
1479 	static assert(dataHasAggregateModel!(TypeOf!d));
1480 	static assert(hasRenderedAs!d);
1481 
1482 	auto m2 = makeModel(d);
1483 	static assert(is(m2.Proxy == string));
1484 }