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