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