1 /** 2 * Container types 3 * 4 * Authors: Tristan Brice Velloza Kildaire (deavmi) 5 */ 6 module niknaks.containers; 7 8 import core.sync.mutex : Mutex; 9 import std.datetime : Duration, dur; 10 import std.datetime.stopwatch : StopWatch, AutoStart; 11 import core.thread : Thread; 12 import core.sync.condition : Condition; 13 import std.functional : toDelegate; 14 import core.exception : ArrayIndexError; 15 import core.exception : RangeError; 16 import std.string : format; 17 import niknaks.arrays : removeResize; 18 import std.traits : hasMember, hasStaticMember, Parameters, arity, ReturnType, TemplateArgsOf; 19 import std.meta : AliasSeq, staticIndexOf; 20 import niknaks.functional : Optional; 21 22 private version(unittest) 23 { 24 import std.stdio : writeln; 25 } 26 27 private version(unittest) 28 { 29 import std.functional : toDelegate; 30 31 private void DebugTouch(T)(Graph!(T) node) 32 { 33 writeln("Touching graph node ", node); 34 } 35 } 36 37 private version(unittest) 38 { 39 class DNode 40 { 41 private int x; 42 this(int x) 43 { 44 this.x = x; 45 } 46 47 public int getTestVal() 48 { 49 return this.x; 50 } 51 } 52 53 class ClassWithoutSimpleThis 54 { 55 this(int, int) 56 { 57 58 } 59 } 60 } 61 62 private version(unittest) 63 { 64 class Thing 65 { 66 this(int) 67 { 68 69 } 70 } 71 72 class ArglessThing 73 { 74 this() 75 { 76 77 } 78 } 79 } 80 81 private version(unittest) 82 { 83 struct Person 84 { 85 private static size_t _g = 0; 86 size_t uniq; 87 this(int) 88 { 89 this.uniq = ++_g; 90 } 91 } 92 } 93 94 /** 95 * Represents an entry of 96 * some value of type `V` 97 * 98 * Associated with this 99 * is a timer used to 100 * check against for 101 * expiration 102 */ 103 private template Entry(V) 104 { 105 /** 106 * The entry type 107 */ 108 public struct Entry 109 { 110 private V value; 111 private StopWatch timer; 112 113 @disable 114 private this(); 115 116 /** 117 * Creates a new entry 118 * with the given value 119 * 120 * Params: 121 * value = the value 122 */ 123 public this(V value) 124 { 125 setValue(value); 126 timer = StopWatch(AutoStart.yes); 127 } 128 129 /** 130 * Sets the value of this 131 * entry 132 * 133 * Params: 134 * value = the value 135 */ 136 public void setValue(V value) 137 { 138 this.value = value; 139 } 140 141 /** 142 * Returns the value associated 143 * with this entry 144 * 145 * Returns: the value 146 */ 147 public V getValue() 148 { 149 return this.value; 150 } 151 152 /** 153 * Resets the timer back 154 * to zero 155 */ 156 public void bump() 157 { 158 timer.reset(); 159 } 160 161 /** 162 * Gets the time elapsed 163 * since this entry was 164 * instantiated 165 * 166 * Returns: the elapsed 167 * time 168 */ 169 public Duration getElapsedTime() 170 { 171 return timer.peek(); 172 } 173 } 174 } 175 176 /** 177 * A `CacheMap` with a key type of `K` 178 * and value type of `V` 179 */ 180 public template CacheMap(K, V) 181 { 182 /** 183 * A replacement function which takes 184 * in the key of type `K` and returns 185 * a value of type `V` 186 * 187 * This is the delegate-based variant 188 */ 189 public alias ReplacementDelegate = V delegate(K); 190 191 /** 192 * A replacement function which takes 193 * in the key of type `K` and returns 194 * a value of type `V` 195 * 196 * This is the function-based variant 197 */ 198 public alias ReplacementFunction = V function(K); 199 200 /** 201 * A caching map which when queried 202 * for a key which does not exist yet 203 * will call a so-called replacement 204 * function which produces a result 205 * which will be stored at that key's 206 * location 207 * 208 * After this process a timer is started, 209 * and periodically entries are checked 210 * for timeouts, if they have timed out 211 * then they are removed and the process 212 * begins again. 213 * 214 * Accessing an entry will reset its 215 * timer ONLY if it has not yet expired 216 * however accessing an entry which 217 * has expired causing an on-demand 218 * replacement function call, just not 219 * a removal in between 220 */ 221 public class CacheMap 222 { 223 private Entry!(V)[K] map; 224 private Mutex lock; 225 private Duration expirationTime; 226 private ReplacementDelegate replFunc; 227 228 private Thread checker; 229 private bool isRunning; 230 private Condition condVar; 231 private Duration sweepInterval; 232 233 /** 234 * Constructs a new cache map with the 235 * given replacement delegate and the 236 * expiration deadline. 237 * 238 * Params: 239 * replFunc = the replacement delegate 240 * expirationTime = the expiration 241 * deadline 242 * sweepInterval = the interval at 243 * which the sweeper thread should 244 * run at to check for expired entries 245 */ 246 this(ReplacementDelegate replFunc, Duration expirationTime = dur!("seconds")(10), Duration sweepInterval = dur!("seconds")(10)) 247 { 248 this.replFunc = replFunc; 249 this.lock = new Mutex(); 250 this.expirationTime = expirationTime; 251 252 this.sweepInterval = sweepInterval; 253 this.condVar = new Condition(this.lock); 254 this.checker = new Thread(&checkerFunc); 255 this.isRunning = true; 256 this.checker.start(); 257 258 } 259 260 /** 261 * Constructs a new cache map with the 262 * given replacement function and the 263 * expiration deadline. 264 * 265 * Params: 266 * replFunc = the replacement function 267 * expirationTime = the expiration 268 * deadline 269 * sweepInterval = the interval at 270 * which the sweeper thread should 271 * run at to check for expired entries 272 */ 273 this(ReplacementFunction replFunc, Duration expirationTime = dur!("seconds")(10), Duration sweepInterval = dur!("seconds")(10)) 274 { 275 this(toDelegate(replFunc), expirationTime, sweepInterval); 276 } 277 278 /** 279 * Creates an entry for the given 280 * key by creating the `Entry` 281 * at the key and then setting 282 * that entry's value with the 283 * replacement function 284 * 285 * Params: 286 * key = the key 287 * Returns: the value set 288 */ 289 private V makeKey(K key) 290 { 291 // Lock the mutex 292 this.lock.lock(); 293 294 // On exit 295 scope(exit) 296 { 297 // Unlock the mutex 298 this.lock.unlock(); 299 } 300 301 // Run the replacement function for this key 302 V newValue = replFunc(key); 303 304 // Create a new entry with this value 305 Entry!(V) newEntry = Entry!(V)(newValue); 306 307 // Save this entry into the hashmap 308 this.map[key] = newEntry; 309 310 return newValue; 311 } 312 313 /** 314 * Called to update an existing 315 * `Entry` (already present) in 316 * the map. This will run the 317 * replacement function and update 318 * the value present. 319 * 320 * Params: 321 * key = the key 322 * Returns: the value set 323 */ 324 private V updateKey(K key) 325 { 326 // Lock the mutex 327 this.lock.lock(); 328 329 // On exit 330 scope(exit) 331 { 332 // Unlock the mutex 333 this.lock.unlock(); 334 } 335 336 // Run the replacement function for this key 337 V newValue = replFunc(key); 338 339 // Update the value saved at this key's entry 340 this.map[key].setValue(newValue); 341 342 return newValue; 343 } 344 345 /** 346 * Check's a specific key for expiration, 347 * and if expired then refreshes it if 348 * not it leaves it alone. 349 * 350 * Returns the key's value 351 * 352 * Params: 353 * key = the key to check 354 * Returns: the key's value 355 */ 356 private V expirationCheck(K key) 357 { 358 // Lock the mutex 359 this.lock.lock(); 360 361 // On exit 362 scope(exit) 363 { 364 // Unlock the mutex 365 this.lock.unlock(); 366 } 367 368 // Obtain the entry at this key 369 Entry!(V)* entry = key in this.map; 370 371 // If the key exists 372 if(entry != null) 373 { 374 // If this entry expired, run the refresher 375 if(entry.getElapsedTime() >= this.expirationTime) 376 { 377 version(unittest) { writeln("Expired entry for key '", key, "', refreshing"); } 378 379 updateKey(key); 380 } 381 // Else, if not, then bump the entry 382 else 383 { 384 entry.bump(); 385 } 386 } 387 // If it does not exist (then make it) 388 else 389 { 390 version(unittest) { writeln("Hello there, we must MAKE key as it does not exist"); } 391 makeKey(key); 392 version(unittest) { writeln("fic"); } 393 } 394 395 return this.map[key].getValue(); 396 } 397 398 /** 399 * Gets the value of 400 * the entry at the 401 * provided key 402 * 403 * This may or may not 404 * call the replication 405 * function 406 * 407 * Params: 408 * key = the key to 409 * lookup by 410 * 411 * Returns: the value 412 */ 413 public V get(K key) 414 { 415 // Lock the mutex 416 this.lock.lock(); 417 418 // On exit 419 scope(exit) 420 { 421 // Unlock the mutex 422 this.lock.unlock(); 423 } 424 425 // The key's value 426 V keyValue; 427 428 // On access expiration check 429 keyValue = expirationCheck(key); 430 431 return keyValue; 432 } 433 434 /** 435 * See_Also: get 436 */ 437 public V opIndex(K key) 438 { 439 return get(key); 440 } 441 442 /** 443 * Removes the given key 444 * returning whether or 445 * not it was a success 446 * 447 * Params: 448 * key = the key to 449 * remove 450 * Returns: `true` if the 451 * key existed, `false` 452 * otherwise 453 */ 454 public bool removeKey(K key) 455 { 456 // Lock the mutex 457 this.lock.lock(); 458 459 // On exit 460 scope(exit) 461 { 462 // Unlock the mutex 463 this.lock.unlock(); 464 } 465 466 // Remove the key 467 return this.map.remove(key); 468 } 469 470 /** 471 * Runs at the latest every 472 * `expirationTime` ticks 473 * and checks the entire 474 * map for expired 475 * entries 476 */ 477 private void checkerFunc() 478 { 479 while(this.isRunning) 480 { 481 // Lock the mutex 482 this.lock.lock(); 483 484 // On loop exit 485 scope(exit) 486 { 487 // Unlock the mutex 488 this.lock.unlock(); 489 } 490 491 // Sleep until sweep interval 492 this.condVar.wait(this.sweepInterval); 493 494 // Run the expiration check 495 K[] marked; 496 foreach(K curKey; this.map.keys()) 497 { 498 Entry!(V) curEntry = this.map[curKey]; 499 500 // If entry has expired mark it for removal 501 if(curEntry.getElapsedTime() >= this.expirationTime) 502 { 503 version(unittest) { writeln("Marked entry '", curEntry, "' for removal"); } 504 marked ~= curKey; 505 } 506 } 507 508 foreach(K curKey; marked) 509 { 510 Entry!(V) curEntry = this.map[curKey]; 511 512 version(unittest) { writeln("Removing entry '", curEntry, "'..."); } 513 this.map.remove(curKey); 514 } 515 } 516 } 517 518 /** 519 * Wakes up the checker 520 * immediately such that 521 * it can perform a cycle 522 * over the map and check 523 * for expired entries 524 */ 525 private void doLiveCheck() 526 { 527 // Lock the mutex 528 this.lock.lock(); 529 530 // Signal wake up 531 this.condVar.notify(); 532 533 // Unlock the mutex 534 this.lock.unlock(); 535 } 536 537 /** 538 * On destruction, set 539 * the running status 540 * to `false`, then 541 * wake up the checker 542 * and wait for it to 543 * exit 544 */ 545 ~this() 546 { 547 version(unittest) 548 { 549 writeln("Dtor running"); 550 551 scope(exit) 552 { 553 writeln("Dtor running [done]"); 554 } 555 } 556 557 // Set run state to false 558 this.isRunning = false; 559 560 // Signal to stop 561 doLiveCheck(); 562 563 // Wait for it to stop 564 this.checker.join(); 565 } 566 } 567 } 568 569 /** 570 * Tests the usage of the `CacheMap` type 571 * along with the expiration of entries 572 * mechanism 573 */ 574 unittest 575 { 576 int i = 0; 577 int getVal(string) 578 { 579 i++; 580 return i; 581 } 582 583 // Create a CacheMap with 10 second expiration and 10 second sweeping interval 584 CacheMap!(string, int) map = new CacheMap!(string, int)(&getVal, dur!("seconds")(10)); 585 586 // Get the value 587 int tValue = map["Tristan"]; 588 assert(tValue == 1); 589 590 // Get the value (should still be cached) 591 tValue = map["Tristan"]; 592 assert(tValue == 1); 593 594 // Wait for expiry (by sweeping thread) 595 Thread.sleep(dur!("seconds")(11)); 596 597 // Should call replacement function 598 tValue = map["Tristan"]; 599 assert(tValue == 2); 600 601 // Wait for expiry (by sweeping thread) 602 writeln("Sleeping now 11 secs"); 603 Thread.sleep(dur!("seconds")(11)); 604 605 // Destroy the map (such that it ends the sweeper) 606 destroy(map); 607 } 608 609 /** 610 * Creates a `CacheMap` which tests out 611 * the on-access expiration checking of 612 * entries by accessing an entry faster 613 * then the sweep interval and by 614 * having an expiration interval below 615 * the aforementioned interval 616 */ 617 unittest 618 { 619 int i = 0; 620 int getVal(string) 621 { 622 i++; 623 return i; 624 } 625 626 // Create a CacheMap with 5 second expiration and 10 second sweeping interval 627 CacheMap!(string, int) map = new CacheMap!(string, int)(&getVal, dur!("seconds")(5), dur!("seconds")(10)); 628 629 // Get the value 630 int tValue = map["Tristan"]; 631 assert(tValue == 1); 632 633 // Wait for 5 seconds (the entry should then be expired by then for on-access check) 634 Thread.sleep(dur!("seconds")(5)); 635 636 // Get the value (should have replacement function run) 637 tValue = map["Tristan"]; 638 assert(tValue == 2); 639 640 // Destroy the map (such that it ends the sweeper 641 destroy(map); 642 } 643 644 /** 645 * Tests the usage of the `CacheMap`, 646 * specifically the explicit key 647 * removal method 648 */ 649 unittest 650 { 651 int i = 0; 652 int getVal(string) 653 { 654 i++; 655 return i; 656 } 657 658 // Create a CacheMap with 10 second expiration and 10 second sweeping interval 659 CacheMap!(string, int) map = new CacheMap!(string, int)(&getVal, dur!("seconds")(10), dur!("seconds")(10)); 660 661 // Get the value 662 int tValue = map["Tristan"]; 663 assert(tValue == 1); 664 665 // Remove the key 666 assert(map.removeKey("Tristan")); 667 668 // Get the value 669 tValue = map["Tristan"]; 670 assert(tValue == 2); 671 672 // Destroy the map (such that it ends the sweeper 673 destroy(map); 674 } 675 676 /** 677 * A visitation stratergy 678 * which always returns 679 * `true` 680 */ 681 public template Always(T) 682 { 683 /** 684 * Whatever graph node is 685 * provided always accept 686 * a visitation to it 687 * 688 * Params: 689 * treeNode = the node 690 * Returns: `true` always 691 */ 692 public bool Always(Graph!(T) treeNode) 693 { 694 version(unittest) 695 { 696 import std.stdio : writeln; 697 writeln("Strat for: ", treeNode); 698 } 699 return true; 700 } 701 } 702 703 /** 704 * A touching stratergy 705 * that does nothing 706 */ 707 public template Nothing(T) 708 { 709 /** 710 * Consumes a graph node 711 * and does zilch with it 712 * 713 * Params: 714 * treeNode = the node 715 */ 716 public void Nothing(Graph!(T)) {} 717 } 718 719 /** 720 * The inclusion stratergy which 721 * will be called upon the graph 722 * node prior to it being visited 723 * during a dfs operation. 724 * 725 * It is a predicate to determine 726 * whether or not the graph node 727 * in concern should be recursed 728 * upon. 729 */ 730 public template InclusionStratergy(T) 731 { 732 public alias InclusionStratergy = bool delegate(Graph!(T) item); 733 } 734 735 /** 736 * This is called on a graph node 737 * as part of the first action 738 * that takes place during the 739 * visitation of said node during 740 * a dfs operation. 741 */ 742 public template TouchStratergy(T) 743 { 744 public alias TouchStratergy = void delegate(Graph!(T) item); 745 } 746 747 /** 748 * A graph of nodes. 749 * 750 * These nodes are comprised of 751 * two components. The first of 752 * which is their associated value 753 * of type `T`, then the second 754 * are their children nodes 755 * (if any). The latter are of 756 * type `Graph!(T)` and therefore 757 * when constructing one such node 758 * it can also be added as a child 759 * of another node, therefore 760 * allowing you to build your 761 * graph as you see fit. 762 * 763 * Some notable functionality, 764 * other than the obvious, 765 * is the pluggable dfs method 766 * which let's you perform 767 * a recursive search on 768 * the graph, parameterized 769 * by two stratergies. The first 770 * is the so-called `TouchStratergy` 771 * which specifies the function 772 * to be called on the current node 773 * when `dfs` is called on it - 774 * this is the first thing that 775 * is done. The other parameter 776 * is the `VisitationStratergy` 777 * which is a predicate that 778 * will be called BEFORE 779 * entering the dfs (recursing) 780 * of a candidate child node. 781 * With this things like trees 782 * can be built or rather 783 * _derived_ from a graph. 784 * This is infact what the visitation 785 * tree type does. 786 * 787 * See_Also: `VisitationTree` 788 */ 789 public class Graph(T) 790 { 791 private T value; 792 private Graph!(T)[] children; 793 794 /** 795 * Constructs a new graph with 796 * the given value to set 797 * 798 * Params: 799 * value = the value of 800 * this graph node 801 */ 802 this(T value) 803 { 804 this.value = value; 805 } 806 807 /** 808 * Creates a new graph without 809 * associating any value with 810 * itself 811 */ 812 this() 813 { 814 815 } 816 817 /** 818 * Sets the graph node's 819 * associated value 820 * 821 * Params: 822 * value = the valye 823 */ 824 public void setValue(T value) 825 { 826 this.value = value; 827 } 828 829 /** 830 * Obtains the value associated with 831 * this graph node 832 * 833 * Returns: the value `T` 834 */ 835 public T getValue() 836 { 837 return this.value; 838 } 839 840 /** 841 * Appends another graph node 842 * to the array of children 843 * of this node's 844 * 845 * Params: 846 * node = the tree node 847 * to append 848 */ 849 public void appendNode(Graph!(T) node) 850 { 851 this.children ~= node; 852 } 853 854 /** 855 * Removes a given graph node 856 * from th array of children 857 * of thie node's 858 * 859 * Params: 860 * node = the graph node to 861 * remove 862 * Returns: `true` if the node 863 * was found and then removed, 864 * otherwise `false` 865 */ 866 public bool removeNode(Graph!(T) node) 867 { 868 bool found = false; 869 size_t idx; 870 for(size_t i = 0; i < this.children.length; i++) 871 { 872 found = this.children[i] == node; 873 if(found) 874 { 875 idx = i; 876 break; 877 } 878 } 879 880 if(found) 881 { 882 this.children = this.children.removeResize(idx); 883 return true; 884 } 885 886 return false; 887 } 888 889 /** 890 * Checks if the given type is 891 * that of a graph node 892 * 893 * Returns: `true` if so, `false` 894 * otherwise 895 */ 896 private static bool isGraphNodeType(E)() 897 { 898 return __traits(isSame, E, Graph!(T)); 899 } 900 901 /** 902 * Checks if the given type is 903 * that of a graph node's value 904 * type 905 * 906 * Returns: `true` if so, `false` 907 * otherwise 908 */ 909 private static bool isGraphValueType(E)() 910 { 911 return __traits(isSame, E, T); 912 } 913 914 /** 915 * Returns a slice of the requested 916 * type. This is either `Graph!(T)` 917 * or `T` itself, therefore returning 918 * an array of either 919 * 920 * Returns: an array of the requested 921 * type of children 922 */ 923 public E[] opSlice(E)() 924 if(isGraphNodeType!(E) || isGraphValueType!(E)) 925 { 926 // If the children as graph nodes is requested 927 static if(isGraphNodeType!(E)) 928 { 929 return this.children; 930 } 931 // If the children as values themselves is requested 932 else static if(isGraphValueType!(E)) 933 { 934 T[] slice; 935 foreach(Graph!(T) tnode; this.children) 936 { 937 slice ~= tnode.value; 938 } 939 return slice; 940 } 941 } 942 943 /** 944 * Returns an array of all the childrens' 945 * associated values 946 * 947 * Returns: a `T[]` 948 */ 949 public T[] opSlice() 950 { 951 return opSlice!(T)(); 952 } 953 954 /** 955 * Returns the element of the child 956 * at the given index. 957 * 958 * The type `E` can be specified 959 * as either `Graph!(T)` or `T` 960 * which will hence return a node 961 * from the children array at the 962 * given index of that type (either 963 * the child node or the child node's 964 * value). 965 * 966 * Params: 967 * idx = the index 968 * Returns: the type `E` 969 */ 970 public E opIndex(E)(size_t idx) 971 if(isGraphNodeType!(E) || isGraphValueType!(E)) 972 { 973 // If the child as a graph node is requested 974 static if(isGraphNodeType!(E)) 975 { 976 return this.children[idx]; 977 } 978 // If the child as a value itself is requested 979 else static if(isGraphValueType!(E)) 980 { 981 return this.children[idx].value; 982 } 983 } 984 985 /** 986 * Returns the value of 987 * the child node at 988 * the provided index 989 * 990 * Params: 991 * idx = the index 992 * Returns: the value 993 */ 994 public T opIndex(size_t idx) 995 { 996 return opIndex!(T)(idx); 997 } 998 999 /** 1000 * Returns the number 1001 * of children attached 1002 * to this node 1003 * 1004 * Returns: the count 1005 */ 1006 @property 1007 public size_t length() 1008 { 1009 return this.children.length; 1010 } 1011 1012 /** 1013 * Returns the number 1014 * of children attached 1015 * to this node 1016 * 1017 * Returns: the count 1018 */ 1019 public size_t opDollar() 1020 { 1021 return this.length; 1022 } 1023 1024 /** 1025 * Performs a depth first search 1026 * on the graph by firstly calling 1027 * the `TouchStratergy` on the current 1028 * node and then iterating over all 1029 * of its children and only recursing 1030 * on each of them if the `InclusionStratergy` 1031 * allows it. 1032 * 1033 * The touch stratergy is called 1034 * as part of the first line of code 1035 * in the call to the dfs on a 1036 * given graph node. 1037 * 1038 * Note that is you don't have a good 1039 * inclusion stratergy and touch startergy 1040 * then you may have a stack overflow 1041 * occur if your graph has cycles 1042 * 1043 * Params: 1044 * strat = the `InclusionStratergy` 1045 * touch = the `TouchStratergy` 1046 * Returns: a `T[]` 1047 */ 1048 public T[] dfs 1049 ( 1050 InclusionStratergy!(T) strat = toDelegate(&Always!(T)), 1051 TouchStratergy!(T) touch = toDelegate(&Nothing!(T)) 1052 ) 1053 { 1054 version(unittest) 1055 { 1056 writeln("dfs entry: ", this); 1057 } 1058 1059 T[] collected; 1060 scope(exit) 1061 { 1062 version(unittest) 1063 { 1064 writeln("leaving node ", this, " with collected ", collected); 1065 } 1066 } 1067 1068 // Touch 1069 touch(this); // root[x] 1070 1071 foreach(Graph!(T) child; this.children) // subtree[x], 1072 { 1073 if(strat(child)) 1074 { 1075 version(unittest) 1076 { 1077 writeln("dfs, strat good for child: ", child); 1078 } 1079 1080 // Visit 1081 collected ~= child.dfs(strat, touch); 1082 } 1083 else 1084 { 1085 version(unittest) 1086 { 1087 writeln("dfs, strat ignored for child: ", child); 1088 } 1089 } 1090 } 1091 1092 // "Visit" 1093 collected ~= this.value; 1094 1095 1096 return collected; 1097 } 1098 1099 /** 1100 * Returns a string representation 1101 * of this node and its value 1102 * 1103 * Returns: a `string` 1104 */ 1105 public override string toString() 1106 { 1107 return format("GraphNode [val: %s]", this.value); 1108 } 1109 1110 /** 1111 * This will attempt to find the 1112 * graph node which contains the 1113 * provided value. 1114 * 1115 * Value matching is based on applying 1116 * the `==`/`opEquals()` operator 1117 * to each graph nodes' `getValue()` 1118 * within the graph. 1119 * 1120 * Params: 1121 * val = the value 1122 * Returns: an optional containing 1123 * the graph node if found, else 1124 * an empty optional 1125 */ 1126 public Optional!(Graph!(T)) findByValue(T val) 1127 { 1128 if(getValue() == val) 1129 { 1130 return Optional!(Graph!(T))(this); 1131 } 1132 1133 foreach(Graph!(T) g; this.children) 1134 { 1135 Optional!(Graph!(T)) g_opt = g.findByValue(val); 1136 if(g_opt.isPresent()) 1137 { 1138 return g_opt; 1139 } 1140 } 1141 1142 return Optional!(Graph!(T)).empty(); 1143 } 1144 } 1145 1146 /** 1147 * Test out usage of the `Graph!(T)` 1148 */ 1149 unittest 1150 { 1151 Graph!(string) treeOfStrings = new Graph!(string)("Top"); 1152 1153 Graph!(string) subtree_1 = new Graph!(string)("1"); 1154 Graph!(string) subtree_2 = new Graph!(string)("2"); 1155 Graph!(string) subtree_3 = new Graph!(string)("3"); 1156 1157 treeOfStrings.appendNode(subtree_1); 1158 treeOfStrings.appendNode(subtree_2); 1159 treeOfStrings.appendNode(subtree_3); 1160 1161 assert(treeOfStrings.opIndex!(Graph!(string))(0) == subtree_1); 1162 assert(treeOfStrings.opIndex!(Graph!(string))(1) == subtree_2); 1163 assert(treeOfStrings.opIndex!(Graph!(string))(2) == subtree_3); 1164 1165 assert(treeOfStrings[0] == subtree_1.getValue()); 1166 assert(treeOfStrings[1] == subtree_2.getValue()); 1167 assert(treeOfStrings[2] == subtree_3.getValue()); 1168 1169 assert(treeOfStrings.opDollar() == 3); 1170 1171 Optional!(Graph!(string)) match_opt = treeOfStrings.findByValue("2"); 1172 assert(match_opt.isPresent()); 1173 assert(match_opt.get() is subtree_2); 1174 1175 InclusionStratergy!(string) strat = toDelegate(&Always!(string)); 1176 TouchStratergy!(string) touch = toDelegate(&DebugTouch!(string)); 1177 1178 string[] result = treeOfStrings.dfs(strat, touch); 1179 writeln("dfs: ", result); 1180 1181 assert(result[0] == "1"); 1182 assert(result[1] == "2"); 1183 assert(result[2] == "3"); 1184 assert(result[3] == "Top"); 1185 1186 1187 auto i = treeOfStrings.opSlice!(Graph!(string))(); 1188 writeln("Siblings: ", i); 1189 assert(i[0] == subtree_1); 1190 assert(i[1] == subtree_2); 1191 assert(i[2] == subtree_3); 1192 1193 auto p = treeOfStrings.opSlice!(string)(); 1194 writeln("Siblings (vals): ", p); 1195 assert(p == treeOfStrings[]); 1196 1197 1198 assert(treeOfStrings.removeNode(subtree_1)); 1199 assert(!treeOfStrings.removeNode(subtree_1)); 1200 } 1201 1202 /** 1203 * A kind-of a graph which has the ability 1204 * to linearize all of its nodes which 1205 * results in performing a depth first 1206 * search resulting in the collection of 1207 * all nodes into a single array with 1208 * elements on the left hand side being 1209 * the most leafiest (and left-to-right 1210 * on the same depth are in said order). 1211 * 1212 * It also marks a node as visited on 1213 * entry to it via the dfs call to it. 1214 * 1215 * When dfs is performed, a child node 1216 * is only recursed upon if it has not 1217 * yet been visited. 1218 * 1219 * With all this, it means a graph of 1220 * relations can be flattened into an 1221 * array. 1222 */ 1223 public class VisitationTree(T) : Graph!(T) 1224 { 1225 private bool visisted; 1226 1227 /** 1228 * Constructs a new node 1229 * 1230 * Params: 1231 * value = the value 1232 */ 1233 this(T value) 1234 { 1235 super(value); 1236 } 1237 1238 /** 1239 * Performs the linearization 1240 * 1241 * Returns: the linearized list 1242 */ 1243 public T[] linearize() 1244 { 1245 return dfs(toDelegate(&_shouldVisit), toDelegate(&_touch)); 1246 } 1247 1248 /** 1249 * The inclusion startergy 1250 * 1251 * Params: 1252 * tnode = the graph node 1253 * Returns: `true` if not 1254 * yet visited or incompatible 1255 * node type 1256 */ 1257 private static bool _shouldVisit(Graph!(T) tnode) 1258 { 1259 VisitationTree!(T) vnode = cast(VisitationTree!(T))tnode; 1260 return vnode && !vnode.isVisited(); 1261 } 1262 1263 /** 1264 * The touching stratergy 1265 * 1266 * Only works on compatible 1267 * graph nodes 1268 * 1269 * Params: 1270 * tnode = the tree node 1271 */ 1272 private static void _touch(Graph!(T) tnode) 1273 { 1274 VisitationTree!(T) vnode = cast(VisitationTree!(T))tnode; 1275 if(vnode) 1276 { 1277 vnode.mark(); 1278 } 1279 } 1280 1281 /** 1282 * Marks this node as 1283 * visited 1284 */ 1285 public void mark() 1286 { 1287 this.visisted = true; 1288 } 1289 1290 /** 1291 * Checks this node has been 1292 * visited 1293 * 1294 * Returns: `true` if visited, 1295 * otherwise `false` 1296 */ 1297 public bool isVisited() 1298 { 1299 return this.visisted; 1300 } 1301 } 1302 1303 /** 1304 * Tests out using the visitation tree 1305 */ 1306 unittest 1307 { 1308 VisitationTree!(string) root = new VisitationTree!(string)("root"); 1309 1310 VisitationTree!(string) thing = new VisitationTree!(string)("subtree"); 1311 root.appendNode(thing); 1312 thing.appendNode(root); 1313 1314 string[] linearized = root.linearize(); 1315 writeln(linearized); 1316 1317 assert(linearized[0] == "subtree"); 1318 assert(linearized[1] == "root"); 1319 } 1320 1321 public class CycleDetectionTree(T) : Graph!(T) 1322 { 1323 private size_t cnt; 1324 1325 /** 1326 * Constructs a new node 1327 * 1328 * Params: 1329 * value = the value 1330 */ 1331 this(T value) 1332 { 1333 super(value); 1334 } 1335 1336 public void mark(size_t s) 1337 { 1338 this.cnt += s; 1339 } 1340 1341 public void mark() 1342 { 1343 mark(1); 1344 } 1345 1346 public void opUnary(string op)() 1347 if(op == "++") 1348 { 1349 mark(); 1350 } 1351 1352 public void opBinaryRight(string op, size_t)(size_t lhs) 1353 if(op == "+") 1354 { 1355 assert(lhs > 0); // FIXME: Throw rather 1356 mark(lhs); 1357 } 1358 1359 public bool isVisited() 1360 { 1361 return this.cnt > 0; 1362 } 1363 1364 public bool wasCycled() 1365 { 1366 return this.cnt > 1; 1367 } 1368 1369 public size_t getCnt() 1370 { 1371 return this.cnt; 1372 } 1373 } 1374 1375 /** 1376 * Basic implementation of a `SectorType` 1377 */ 1378 public struct BasicSector(T) 1379 { 1380 private T[] data; 1381 1382 this(T[] data) 1383 { 1384 this.data = data; 1385 } 1386 1387 /** 1388 * Constructs a new `BasicSector` 1389 * 1390 * Params: 1391 * data = the data the sector 1392 * should reference 1393 * Returns: a new `BasicSector` 1394 */ 1395 public static BasicSector!(T) make(T[] data) 1396 { 1397 return BasicSector!(T)(data); 1398 } 1399 1400 /** 1401 * Ontains the element at the 1402 * given index 1403 * 1404 * Params: 1405 * idx = the index 1406 * Returns: the element 1407 */ 1408 public T opIndex(size_t idx) 1409 { 1410 return this.data[idx]; 1411 } 1412 1413 /** 1414 * Set something at an index 1415 * 1416 * Params: 1417 * value = the value to set 1418 * index = the index to store 1419 * the value at 1420 */ 1421 public void opIndexAssign(T value, size_t index) 1422 { 1423 this.data[index] = value; 1424 } 1425 1426 /** 1427 * Obtains this sector's length 1428 * 1429 * Returns: the length 1430 */ 1431 public size_t opDollar() 1432 { 1433 return this.data.length; 1434 } 1435 1436 /** 1437 * Obtains this sector's length 1438 * 1439 * Returns: the length 1440 */ 1441 @property 1442 public size_t length() 1443 { 1444 return opDollar(); 1445 } 1446 1447 /** 1448 * Returns a slice of this sector 1449 * between the given bounds 1450 * 1451 * Params: 1452 * start = the lower (inclusive) 1453 * bound 1454 * end = the upper (exclusive) 1455 * bound 1456 * Returns: the slice 1457 */ 1458 public T[] opSlice(size_t start, size_t end) 1459 { 1460 return this.data[start..end]; 1461 } 1462 1463 /** 1464 * Ontains a slice of the 1465 * entire sector 1466 * 1467 * Returns: the slice 1468 */ 1469 public T[] opSlice() 1470 { 1471 return opSlice(0, opDollar); 1472 } 1473 1474 /** 1475 * Sets the new size of this 1476 * sector's internal buffer 1477 * 1478 * Note that this should 1479 * only be smaller or equal 1480 * to the current internal 1481 * buffer size 1482 * 1483 * Params: 1484 * newSize = the new size 1485 */ 1486 @property 1487 public void length(size_t newSize) 1488 { 1489 assert(newSize <= this.data.length); 1490 this.data.length = newSize; 1491 } 1492 } 1493 1494 /** 1495 * Compile-time function to check if 1496 * a given data type is a valid 1497 * `SectorType`, meaning that it 1498 * can be used for a `View` 1499 * implementation 1500 * 1501 * Returns: `true` if so, `false` 1502 * otherwise 1503 */ 1504 public bool isSector(S)() 1505 { 1506 bool s = true; 1507 1508 alias args = TemplateArgsOf!(S); 1509 pragma(msg, args); 1510 static if(!args.length) 1511 { 1512 return false; 1513 } 1514 1515 alias T = args[0]; 1516 1517 // Has opSlice(size_t, size_t) with T[] return 1518 s &= hasMember!(S, "opSlice") && 1519 __traits(isSame, Parameters!(S.opSlice), AliasSeq!(size_t, size_t)) && 1520 __traits(isSame, ReturnType!(S.opSlice), T[]); 1521 1522 // Has opSlice() with T[] return 1523 bool foundNonParamOpSlice = false; 1524 foreach(func; __traits(getOverloads, S, "opSlice")) 1525 { 1526 if(arity!(func) == 0) 1527 { 1528 static if(__traits(isSame, ReturnType!(S.opSlice), T[])) 1529 { 1530 foundNonParamOpSlice = true; 1531 } 1532 } 1533 } 1534 s &= foundNonParamOpSlice; 1535 1536 // Has length method 1537 s &= hasMember!(S, "length") && 1538 __traits(isSame, ReturnType!(S.length), size_t) && 1539 staticIndexOf!("@property", __traits(getFunctionAttributes, S.length)) != -1; 1540 1541 // Has length (setter) method 1542 bool found_len_setter = false; 1543 static foreach(lenFunc; __traits(getOverloads, S, "length")) 1544 { 1545 static if 1546 ( 1547 __traits(isSame, Parameters!(lenFunc), AliasSeq!(size_t)) && 1548 __traits(isSame, ReturnType!(lenFunc), void) && 1549 staticIndexOf!("@property", __traits(getFunctionAttributes, lenFunc)) != -1 1550 ) 1551 { 1552 found_len_setter = true; 1553 } 1554 } 1555 s &= found_len_setter; 1556 1557 // Has opDollar with size_t return 1558 s &= hasMember!(S, "opDollar") && 1559 __traits(isSame, Parameters!(S.opDollar), AliasSeq!()) && 1560 __traits(isSame, ReturnType!(S.opDollar), size_t); 1561 1562 // Has opIndex(size_t) with T return 1563 s &= hasMember!(S, "opIndex") && 1564 __traits(isSame, Parameters!(S.opIndex), AliasSeq!(size_t)) && 1565 __traits(isSame, ReturnType!(S.opIndex), T); 1566 1567 // Has opIndexAssign(size_t) with T return 1568 s &= hasMember!(S, "opIndexAssign") && 1569 __traits(isSame, Parameters!(S.opIndexAssign), AliasSeq!(T, size_t)) && 1570 __traits(isSame, ReturnType!(S.opIndexAssign), void); 1571 1572 1573 // Has make(T[] data) returning S (implied S!(T) due to template arg check earlier) 1574 s &= hasStaticMember!(S, "make") && 1575 __traits(isSame, Parameters!(S.make), AliasSeq!(T[])) && 1576 __traits(isSame, ReturnType!(S.make), S); 1577 1578 return s; 1579 } 1580 1581 /** 1582 * A view represents a collection of 1583 * arrays which can be accessed 1584 * in an array like manner and have their 1585 * elements changed too. Therefore this 1586 * provides access to these originally 1587 * non-contiguous data sets as if they 1588 * were one contiguous array. 1589 * 1590 * Updating of elements is allowed, 1591 * fetching of elements (and slices) 1592 * and lastly sizing down but NOT 1593 * updwards. This last constraint 1594 * is why this is considered a "view". 1595 */ 1596 public struct View(T, SectorType = BasicSector!(T)) 1597 if(isSector!(SectorType)()) 1598 { 1599 private SectorType[] sectors; 1600 1601 // Maybe current size should be here as we 1602 // are a view, we should allow modification 1603 // but not make any NEW arrays 1604 private size_t curSize; 1605 1606 /** 1607 * Computes the sum of the 1608 * length of all sectors 1609 * attached to us 1610 * 1611 * Returns: the total 1612 */ 1613 private size_t computeTotalLen() 1614 { 1615 size_t l; 1616 foreach(SectorType sector; this.sectors) 1617 { 1618 l += sector.opDollar(); 1619 } 1620 return l; 1621 } 1622 1623 /** 1624 * Returns the total length 1625 * of the data in the view 1626 * 1627 * Returns: the length 1628 */ 1629 public size_t opDollar() 1630 { 1631 return this.length; 1632 } 1633 1634 /** 1635 * Retrieves the value of 1636 * the element at the 1637 * given position 1638 * 1639 * Params: 1640 * idx = the position 1641 * of the element 1642 * Returns: the value 1643 */ 1644 public T opIndex(size_t idx) 1645 { 1646 // Within range of "fake" size 1647 if(!(idx < this.length)) 1648 { 1649 throw new ArrayIndexError(idx, this.length); 1650 } 1651 1652 size_t thunk; 1653 foreach(SectorType sector; this.sectors) 1654 { 1655 if(idx-thunk < sector.opDollar()) 1656 { 1657 return sector[idx-thunk]; 1658 } 1659 else 1660 { 1661 thunk += sector.opDollar(); 1662 } 1663 } 1664 1665 // NOTE: This should be unreachable but 1666 // compiler moans and groans 1667 assert(false); 1668 } 1669 1670 /** 1671 * Updates the element at 1672 * the given index with 1673 * a new value 1674 * 1675 * Params: 1676 * value = the new value 1677 * idx = the element 1678 * to update's position 1679 */ 1680 public void opIndexAssign(T value, size_t idx) 1681 { 1682 // Within range of "fake" size 1683 if(!(idx < this.length)) 1684 { 1685 throw new ArrayIndexError(idx, this.length); 1686 } 1687 1688 size_t thunk; 1689 foreach(ref SectorType sector; this.sectors) 1690 { 1691 version(unittest) 1692 { 1693 writeln(sector); 1694 writeln("idx: ", idx); 1695 writeln("thunk: ", thunk); 1696 } 1697 1698 if(idx-thunk < sector.opDollar()) 1699 { 1700 sector[idx-thunk] = value; 1701 return; 1702 } 1703 else 1704 { 1705 thunk += sector.opDollar(); 1706 } 1707 } 1708 } 1709 1710 /** 1711 * Returns a copy of the entire 1712 * view 1713 * 1714 * Returns: a `T[]` 1715 */ 1716 public T[] opSlice() 1717 { 1718 return this[0..this.length]; 1719 } 1720 1721 /** 1722 * Returns a copy of the view 1723 * within the provided bounds 1724 * 1725 * Params: 1726 * start = the starting 1727 * index 1728 * end = the ending index 1729 * (exclusive) 1730 * Returns: 1731 */ 1732 public T[] opSlice(size_t start, size_t end) 1733 { 1734 // Invariant of start <= end 1735 if(!(start <= end)) 1736 { 1737 throw new RangeError("Starting index must be smaller than or equal to ending index"); 1738 } 1739 // If the indices are equal, then it is empty 1740 else if(start == end) 1741 { 1742 return []; 1743 } 1744 // Within range of "fake" size 1745 else if(!((start < this.length) && (end <= this.length))) 1746 { 1747 throw new RangeError("start index or end index not under range"); 1748 } 1749 1750 T[] collected; 1751 1752 size_t thunk; 1753 foreach(SectorType sector; this.sectors) 1754 { 1755 // If the current sector contains 1756 // both the starting AND ending 1757 // indices 1758 if(start-thunk < sector.opDollar() && end-thunk <= sector.opDollar()) 1759 { 1760 return sector[start-thunk..end-thunk]; 1761 } 1762 // If the current sector's starting 1763 // index (only) is included 1764 else if(start-thunk < sector.opDollar() && !(end-thunk <= sector.opDollar())) 1765 { 1766 collected ~= sector[start-thunk..$]; 1767 } 1768 // If the current sector's ending 1769 // index (only) is included 1770 else if(!(start-thunk < sector.opDollar()) && end-thunk <= sector.opDollar()) 1771 { 1772 collected ~= sector[0..end-thunk]; 1773 } 1774 // If the current sector's entirety 1775 // is to be included 1776 else 1777 { 1778 collected ~= sector[]; 1779 } 1780 1781 thunk += sector.opDollar(); 1782 } 1783 1784 return collected; 1785 } 1786 1787 private static bool isArrayAppend(P)() 1788 { 1789 return __traits(isSame, P, T[]); 1790 } 1791 1792 private static bool isElementAppend(P)() 1793 { 1794 return __traits(isSame, P, T); 1795 } 1796 1797 /** 1798 * Appends a new value to 1799 * the end of the view 1800 * 1801 * Params: 1802 * value = the value 1803 * to append 1804 */ 1805 public void opOpAssign(string op, E)(E value) 1806 if(op == "~" && (isArrayAppend!(E) || isElementAppend!(E))) 1807 { 1808 static if(isArrayAppend!(E)) 1809 { 1810 add(value); 1811 } 1812 else 1813 { 1814 add([value]); 1815 } 1816 } 1817 1818 /** 1819 * Takes the data with which we 1820 * constructs a kind-of `SectorType` 1821 * from. We then adjust the total 1822 * size and append the new sector 1823 * 1824 * Params: 1825 * data = the data to append 1826 */ 1827 private void add(T[] data) 1828 { 1829 // Create a new sector 1830 SectorType sec = SectorType.make(data); 1831 1832 // Update the tracking size 1833 this.curSize += sec.length; 1834 1835 // Concatenate it to the view 1836 this.sectors ~= sec; 1837 } 1838 1839 /** 1840 * Returns the total length 1841 * of the data in the view 1842 * 1843 * Returns: the length 1844 */ 1845 @property 1846 public size_t length() 1847 { 1848 return this.curSize; 1849 } 1850 1851 /** 1852 * Resizes the total 1853 * length of the view. 1854 * 1855 * This allows the user 1856 * to either keep the 1857 * size the same or 1858 * shrink the view, 1859 * but never extend 1860 * it. 1861 * 1862 * 1863 * Params: 1864 * size = the new size 1865 * Throws: 1866 * RangeError if an 1867 * attempt to extend 1868 * the length is made 1869 */ 1870 @property 1871 public void length(size_t size) 1872 { 1873 // TODO: Need we continuously compute this? 1874 // ... we should have a tracking field for 1875 // ... this 1876 // Would only need to be called in length(size_t) 1877 // and add(T[]) 1878 size_t actualSize = computeTotalLen(); 1879 1880 // On successful exit, update the "fake" size 1881 scope(success) 1882 { 1883 this.curSize = size; 1884 } 1885 1886 1887 // Don't allow sizing up (doesn't make sense for a view) 1888 if(size > actualSize) 1889 { 1890 auto r = new RangeError(); 1891 r.msg = "Cannot extend the size of a view past its total size (of all attached sectors)"; 1892 throw r; 1893 } 1894 // If nothing changes 1895 else if(size == actualSize) 1896 { 1897 // Nothing 1898 } 1899 // If shrinking to zero 1900 else if(size == 0) 1901 { 1902 // Just drop everything 1903 this.sectors.length = 0; 1904 } 1905 // If shrinking (arbitrary) 1906 else 1907 { 1908 // Sectors from left-to-right to keep 1909 size_t sectorCnt; 1910 1911 // Accumulator 1912 size_t accumulator; 1913 1914 foreach(ref SectorType sector; this.sectors) 1915 { 1916 accumulator += sector.length; 1917 sectorCnt++; 1918 if(size <= accumulator) 1919 { 1920 // TODO: Resize on the tail-end sector? 1921 1922 // Bleed size (accumulation of previous sectors) 1923 // called "x". Then we do `size-x`, this gives 1924 // us the bleed size and we use this as the 1925 // tail-end sector's new size 1926 size_t tailEndTrimSize = size-(accumulator-sector.length); 1927 version(unittest) 1928 { 1929 writeln("tailEndTrimSize: ", tailEndTrimSize); 1930 } 1931 sector.length(tailEndTrimSize); 1932 1933 break; 1934 } 1935 } 1936 1937 this.sectors.length = sectorCnt; 1938 } 1939 } 1940 } 1941 1942 unittest 1943 { 1944 View!(int) view; 1945 assert(view.opDollar() == 0); 1946 1947 try 1948 { 1949 view[1]; 1950 assert(false); 1951 } 1952 catch(ArrayIndexError e) 1953 { 1954 assert(e.index == 1); 1955 assert(e.length == 0); 1956 } 1957 1958 view ~= [1,3,45]; 1959 assert(view.opDollar() == 3); 1960 assert(view.length == 3); 1961 1962 view ~= 2; 1963 assert(view.opDollar() == 4); 1964 assert(view.length == 4); 1965 1966 assert(view[0] == 1); 1967 assert(view[1] == 3); 1968 assert(view[2] == 45); 1969 assert(view[3] == 2); 1970 assert(view[0..2] == [1,3]); 1971 assert(view[0..4] == [1,3,45,2]); 1972 1973 // Update elements 1974 view[0] = 71; 1975 view[3] = 50; 1976 1977 // Set size to same size 1978 view.length = view.length; 1979 1980 // Check that update is present 1981 // and size unchanged 1982 int[] all = view[]; 1983 assert(all == [71,3,45,50]); 1984 1985 // Truncate by 1 element 1986 view.length = view.length-1; 1987 all = view[]; 1988 assert(all == [71,3,45]); 1989 1990 // This should fail 1991 try 1992 { 1993 view[3] = 3; 1994 assert(false); 1995 } 1996 catch(RangeError e) 1997 { 1998 } 1999 2000 // This should fail 2001 try 2002 { 2003 int j = view[3]; 2004 assert(false); 2005 } 2006 catch(RangeError e) 2007 { 2008 } 2009 2010 // Up-sizing past real size should not be allowed 2011 try 2012 { 2013 view.length = view.length+1; 2014 assert(false); 2015 } 2016 catch(RangeError e) 2017 { 2018 } 2019 2020 // Size to zero 2021 view.length = 0; 2022 assert(view.length == 0); 2023 assert(view[] == []); 2024 } 2025 2026 unittest 2027 { 2028 View!(int) view; 2029 view ~= 1; 2030 view ~= [2,3,4]; 2031 view ~= 5; 2032 2033 assert(view[0..5] == [1,2,3,4,5]); 2034 2035 // test: start <= end invariant broken 2036 try 2037 { 2038 auto j = view[1..0]; 2039 assert(false); 2040 } 2041 catch(RangeError e) 2042 { 2043 2044 } 2045 2046 // test: end out of bounds 2047 try 2048 { 2049 auto j = view[1..view.length+1]; 2050 assert(false); 2051 } 2052 catch(RangeError e) 2053 { 2054 2055 } 2056 2057 int[] d = [1,2,3]; 2058 writeln("according to dlang: ", d[1..2]); 2059 2060 writeln("test lekker: ", view[1..2]); 2061 assert(view[1..2] == [2]); 2062 2063 writeln("test lekker: ", view[1..1]); 2064 assert(view[1..1] == []); 2065 } 2066 2067 import niknaks.meta : isClassType, isStructType; 2068 2069 private mixin template Methods(EntityType) 2070 { 2071 // Class-based types 2072 static if(isClassType!(EntityType)) 2073 { 2074 /** 2075 * Pools a new entry by the given 2076 * value. If no such pool entry 2077 * exists then it is constructed, 2078 * stored and returned, else the 2079 * existing entry is returned. 2080 * 2081 * Params: 2082 * v = the key to pool by 2083 * Returns: the pooled entry 2084 * (as an `EntryType`) 2085 */ 2086 public EntryType pool(ValueType v) 2087 { 2088 2089 EntryType* ent = v in _p; 2090 if(ent is null) 2091 { 2092 static if(entryTakesValue_onCtor) 2093 { 2094 _p[v] = new EntryType(v); 2095 } 2096 else 2097 { 2098 pragma(msg, "Class: Argless ctor less"); 2099 _p[v] = new EntryType(); 2100 } 2101 2102 return pool(v); 2103 } 2104 return *ent; 2105 } 2106 } 2107 // Struct-based types 2108 else static if(isStructType!(EntryType)) 2109 { 2110 /** 2111 * Pools a new entry by the given 2112 * value. If no such pool entry 2113 * exists then it is constructed, 2114 * stored and returned, else the 2115 * existing entry is returned. 2116 * 2117 * Params: 2118 * v = the key to pool by 2119 * Returns: the pooled entry 2120 * (as an `EntryType*`) 2121 */ 2122 public EntryType* pool(ValueType v) 2123 { 2124 EntryType* ent = v in _p; 2125 if(ent is null) 2126 { 2127 static if(entryTakesValue_onCtor) 2128 { 2129 _p[v] = EntryType(v); 2130 } 2131 else 2132 { 2133 pragma(msg, "Struct: Argless ctor less"); 2134 _p[v] = EntryType(); 2135 } 2136 2137 return pool(v); 2138 } 2139 return ent; 2140 } 2141 } 2142 } 2143 2144 /** 2145 * Represents a pool which takes 2146 * in a key of type `ValueType`. 2147 * If a corresponding `EntryType` 2148 * is mapped-to via said key then 2149 * it is returned, else it is 2150 * constructed on the spot, stored 2151 * and then finally returned 2152 * 2153 * If your `EntryType` is to 2154 * have its constructor called 2155 * _without_ taking in a single 2156 * argument of type `ValueType` 2157 * then set the `entryTakesValue_onCtor` 2158 * to `false`. It is `true` by 2159 * default. 2160 * 2161 * The result of pooling an `EntryType` 2162 * which is a struct-type is that 2163 * it will have a pointer returned, 2164 * i.e. an `EntryType*` 2165 */ 2166 public struct Pool(EntryType, ValueType, bool entryTakesValue_onCtor = true) 2167 if 2168 ( 2169 isClassType!(EntryType) || 2170 isStructType!(EntryType) 2171 ) 2172 { 2173 private EntryType[ValueType] _p; 2174 2175 /** 2176 * Clears the entry that would 2177 * be mapped to by the given 2178 * key 2179 * 2180 * Params: 2181 * v = the key 2182 * Returns: `true` if it existed, 2183 * `false` otheriwse 2184 */ 2185 public bool clear(ValueType v) 2186 { 2187 EntryType* ent = v in _p; 2188 if(ent is null) 2189 { 2190 return false; 2191 } 2192 2193 _p.remove(v); 2194 2195 return true; 2196 } 2197 2198 mixin Methods!(EntryType); 2199 } 2200 2201 /** 2202 * General usage of the pool 2203 */ 2204 unittest 2205 { 2206 Pool!(DNode, int) d; 2207 static assert(__traits(compiles, Pool!(DNode, int)())); 2208 2209 2210 // Initial pool creates the node 2211 DNode p1 = d.pool(1); 2212 assert(p1 !is null); 2213 2214 // Pooling again by said value 2215 // should now return the same 2216 // exact object 2217 DNode p2 = d.pool(1); 2218 assert(p2 is p1); 2219 2220 // Any other value makes an entirely 2221 // new node 2222 DNode p3 = d.pool(2); 2223 assert(p3 !is p2); 2224 2225 // Class-based type without a `this(ValueType)` ctor 2226 static assert(__traits(compiles, Pool!(ClassWithoutSimpleThis, int)()) == false); 2227 2228 // Only class types and struct types are supported 2229 static assert(__traits(compiles, Pool!(int, int)()) == false); 2230 } 2231 2232 /** 2233 * Tests out the clearing of 2234 * an entry by its key 2235 */ 2236 unittest 2237 { 2238 Pool!(Thing, int) p; 2239 Thing t1 = p.pool(1); 2240 assert(t1); 2241 2242 Thing t2 = p.pool(1); 2243 assert(t2 is t1); 2244 2245 // Clear, now pooling should give different address 2246 p.clear(1); 2247 2248 Thing t3 = p.pool(1); 2249 assert(t3 !is t2); 2250 } 2251 2252 /** 2253 * Tests usage of a type that 2254 * consumes no `ValueType` 2255 * on construction of itself 2256 * (`EntryType` has a zero-arity 2257 * constructor) 2258 */ 2259 unittest 2260 { 2261 static assert(__traits(compiles, Pool!(ArglessThing, int, false))); 2262 2263 Pool!(ArglessThing, int, false) p; 2264 ArglessThing t1 = p.pool(1); 2265 assert(t1); 2266 2267 ArglessThing t2 = p.pool(1); 2268 assert(t2 is t1); 2269 } 2270 2271 /** 2272 * Tests pooling with an `EntryType` 2273 * which is a struct type 2274 */ 2275 unittest 2276 { 2277 // Struct-based types are supported 2278 struct P {} 2279 static assert(__traits(compiles, Pool!(P, int, false)()) == true); 2280 2281 Pool!(Person, int) p; 2282 Person* t1 = p.pool(1); 2283 assert(t1); 2284 2285 Person* t2 = p.pool(1); 2286 assert(t2 is t1); 2287 assert(t2.uniq == 1); 2288 2289 Person* t3 = p.pool(2); 2290 assert(t3); 2291 assert(t3.uniq == 2); 2292 assert(t3 !is t2); 2293 }