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