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 }