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