1 /******************************************************************************* 2 3 Contains extensions for Map based classes to dump the contents of maps to a 4 file or to read from a file into a map. Includes struct versioning support. 5 6 This module provides you with several ways to load/dump a map from/into 7 a file: 8 9 * Using the specialized version SerializingMap of the class Map 10 * Using the provided MapExtension mixin to extend a map yourself 11 * Using the class MapSerializer to use the load/dump functions directly 12 13 See documentation of class MapSerializer for more details 14 15 Copyright: 16 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 17 All rights reserved. 18 19 License: 20 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 21 Alternatively, this file may be distributed under the terms of the Tango 22 3-Clause BSD License (see LICENSE_BSD.txt for details). 23 24 *******************************************************************************/ 25 26 module ocean.util.container.map.utils.MapSerializer; 27 28 29 import ocean.meta.types.Qualifiers; 30 31 import ocean.io.digest.Fnv1, 32 ocean.io.serialize.SimpleStreamSerializer, 33 ocean.io.serialize.TypeId, 34 ocean.util.container.map.Map; 35 import ocean.core.Array : copy; 36 import ocean.core.Exception; 37 38 import ocean.core.ExceptionDefinitions : IOException; 39 import ocean.io.model.IConduit : IOStream; 40 41 import ocean.core.Tuple, 42 ocean.io.stream.Buffered, 43 ocean.io.device.File; 44 45 import ocean.meta.traits.Basic; 46 47 import ocean.util.serialize.contiguous.MultiVersionDecorator, 48 ocean.util.serialize.contiguous.Serializer, 49 ocean.util.serialize.Version; 50 51 import ocean.text.convert.Formatter; 52 53 /******************************************************************************* 54 55 Temporary solution to expose protected `convert` method of version 56 decorator to this module. Eventually MapSerializer should be rewritten 57 to either inherit MultiVersionDecorator or mixin necessary methods directly. 58 59 This is not done in first round of changes because it is potentially 60 intrusive implementation change. 61 62 *******************************************************************************/ 63 64 private class Converter : VersionDecorator 65 { 66 } 67 68 /******************************************************************************* 69 70 Specialized version of class Map that includes serialization capabilities 71 using the MapExtension mixin 72 73 Everything else is identical to the class Map, that means you still need to 74 implement your own hashing functionality. 75 76 Note: 77 The serializer allows simple transition from using a map with a 78 non-versioned key / value to a versioned one. The version of the 79 key / value must be v0 and the layout must be the same, 80 otherwise loading will fail. 81 82 Params: 83 V = type of the value 84 K = type of the key 85 86 *******************************************************************************/ 87 88 abstract class SerializingMap ( V, K ) : Map!(V, K) 89 { 90 /*************************************************************************** 91 92 Mixin extensions for serialization 93 94 ***************************************************************************/ 95 96 mixin MapExtension!(K, V); 97 98 /*************************************************************************** 99 100 Constructor. 101 102 Same as the Constructor of Map, but additionally initializes the 103 serializer. 104 105 ***************************************************************************/ 106 107 protected this ( size_t n, float load_factor = 0.75 ) 108 { 109 this.serializer = new MapSerializer; 110 super(n, load_factor); 111 } 112 113 /*************************************************************************** 114 115 Constructor. 116 117 Same as the Constructor of Map, but additionally initializes the 118 serializer. 119 120 ***************************************************************************/ 121 122 protected this ( IAllocator allocator, size_t n, float load_factor = 0.75 ) 123 { 124 this.serializer = new MapSerializer; 125 super(allocator, n, load_factor); 126 } 127 } 128 129 /******************************************************************************* 130 131 Template meant to be used with mixin in classes that inherit from the class 132 Map. 133 134 Extends the class with a load() and dump() function. The mixed in class has 135 to initialize the member 'serializer' in its constructor. 136 137 See SerializingMap for an usage example 138 139 Params: 140 K = key type of the map 141 V = value type of the map 142 143 *******************************************************************************/ 144 145 template MapExtension ( K, V ) 146 { 147 /*************************************************************************** 148 149 Delegate used to check whether a given record should be dumped or loaded 150 151 ***************************************************************************/ 152 153 alias bool delegate ( ref K, ref V ) CheckDg; 154 155 /*************************************************************************** 156 157 Instance of the serializer, needs to be initialized in the class 158 constructor 159 160 ***************************************************************************/ 161 162 protected MapSerializer serializer; 163 164 /*************************************************************************** 165 166 Loads a file into the map 167 168 Params: 169 file_path = path to the file 170 171 ***************************************************************************/ 172 173 public void load ( cstring file_path ) 174 { 175 this.serializer.load!(K, V)(this, file_path); 176 } 177 178 /*************************************************************************** 179 180 Loads a file into the map 181 182 Params: 183 io_device = the device to load from 184 185 ***************************************************************************/ 186 187 public void load ( IConduit io_device ) 188 { 189 this.serializer.loadConduit!(K, V)(this, io_device); 190 } 191 192 /*************************************************************************** 193 194 Loads a file into the map 195 196 Params: 197 file_path = path to the file 198 check = function called for every entry, should return true if 199 it should be loaded 200 201 ***************************************************************************/ 202 203 public void load ( cstring file_path, scope CheckDg check ) 204 { 205 scope file = new File(file_path, File.ReadExisting); 206 207 this.load(file, check); 208 } 209 210 /*************************************************************************** 211 212 Loads a file into the map 213 214 Params: 215 io_device = the device to load from 216 check = function called for every entry, should return true if 217 it should be loaded 218 219 ***************************************************************************/ 220 221 public void load ( IConduit io_device, scope CheckDg check ) 222 { 223 scope add = ( ref K k, ref V v ) 224 { 225 if (check(k,v)) 226 { 227 bool added = false; 228 229 static if ( isArrayType!(V) == ArrayKind.Dynamic ) 230 { 231 (*this.put(k, added)).copy(v); 232 } 233 else 234 { 235 (*this.put(k, added)) = v; 236 } 237 238 // If added key is an array and new don't reuse the memory it 239 // references 240 static if ( isArrayType!(K) == ArrayKind.Dynamic ) 241 { 242 if ( added ) 243 { 244 k = k.dup; 245 } 246 } 247 } 248 }; 249 250 this.serializer.loadDgConduit!(K, V)(io_device, add); 251 } 252 253 /*************************************************************************** 254 255 Dumps a map into a file 256 257 Params: 258 file_path = path to the file 259 260 ***************************************************************************/ 261 262 public void dump ( cstring file_path ) 263 { 264 this.serializer.dump!(K, V)(this, file_path); 265 } 266 267 /*************************************************************************** 268 269 Dumps a map to an IO device 270 271 Params: 272 io_device = the device to dump to 273 274 ***************************************************************************/ 275 276 public void dump ( IConduit io_device ) 277 { 278 this.serializer.dumpConduit!(K, V)(this, io_device); 279 } 280 281 /*************************************************************************** 282 283 Writes a map to a file. 284 285 Params: 286 file_path = path to where the map should be dumped to 287 check = function called for each key/value to confirm that it 288 should be dumped 289 290 ***************************************************************************/ 291 292 public void dump ( cstring file_path, scope CheckDg check ) 293 { 294 scope file = new File(file_path, File.Style(File.Access.Write, 295 File.Open.Create, 296 File.Share.None)); 297 298 this.dump(file, check); 299 } 300 301 /*************************************************************************** 302 303 Writes a map to an IO device. 304 305 Params: 306 io_device = the device to dump to 307 check = function called for each key/value to confirm that it 308 should be dumped 309 310 ***************************************************************************/ 311 312 public void dump ( IConduit io_device, scope CheckDg check ) 313 { 314 scope adder = ( void delegate ( ref K, ref V ) add ) 315 { 316 foreach ( ref k, ref v; this ) if ( check(k,v) ) 317 { 318 add(k, v); 319 } 320 }; 321 322 this.serializer.dumpDgConduit!(K, V)(io_device, adder); 323 } 324 } 325 326 /******************************************************************************* 327 328 Offers functionality to load/dump the content of Maps (optionally of 329 anything actually, using the delegate version of the dump/load functions). 330 331 Features include backwards compability with auto-conversion to the requested 332 struct version. It makes use of the same functions that the 333 StructLoader/StructDumper use for the conversion. 334 335 This means that structs used with this function should have the const 336 member StructVersion as well as an alias to the old version (if one exists) 337 called "StructPrevious" (this is identical to the requirements for vesioned 338 struct in the StructDumper/Loader). 339 340 Additionally, a validation using the struct hash is done too, to exclude 341 potential human errors while setting up the version info. 342 343 If you have a map saved in the old version (2) and at the same time updated 344 the struct definition of that map, you can still take advantage of the 345 auto-conversion functionality if you simply define the old struct version as 346 version 0 and your current one as version 1. The loader is smart enough to 347 figure out the old version by hash and converts it to the newer one. 348 349 Usage Example: 350 --- 351 struct MyValue0 352 { 353 const StructVersion = 0; 354 int my_value; 355 } 356 357 auto serializer = new MapSerializer; 358 auto myMap = new HashMap!(MyValue0)(10); 359 360 // Assume code to fill map with values here 361 //... 362 // 363 364 serializer.dump(myMap, "version0.map"); 365 366 // Later... 367 368 serilizer.load(myMap, "version0.map"); 369 370 371 // Now, if you have changed the struct, create a new version of it 372 373 struct MyValue1 374 { 375 const StructVersion = 1; 376 int my_value; 377 378 int my_new_value; 379 380 void convert_my_new_value ( ref MyValue0 old ) 381 { 382 this.my_new_value = old.my_value * 2; 383 } 384 } 385 386 // This is our map with the new version 387 auto myNewMap = new HashMap!(MyValue1)(10); 388 389 // Load old version 390 serializer.load(myNewMap, "version0.map"); 391 392 // .. use map as desired. 393 394 // You can do the same thing with the key in case it is a struct. 395 --- 396 *******************************************************************************/ 397 398 class MapSerializer 399 { 400 import ocean.io.digest.Fnv1: StaticFnv1a64; 401 402 /*************************************************************************** 403 404 Magic Marker for HashMap files, part of the header 405 406 ***************************************************************************/ 407 408 private static immutable uint MAGIC_MARKER = 0xCA1101AF; 409 410 /*************************************************************************** 411 412 Current file header version 413 414 ***************************************************************************/ 415 416 private static immutable ubyte HEADER_VERSION = 5; 417 418 /*************************************************************************** 419 420 Exception thrown when the file that was loaded is incomplete. Will soon 421 be unused 422 423 ***************************************************************************/ 424 425 class UnexpectedEndException : Exception 426 { 427 mixin DefaultExceptionCtor; 428 } 429 430 /*************************************************************************** 431 432 Helper template for version handling. 433 Takes a tuple of types and changes the type and position index to what 434 ever it has as .StructPrevious member. 435 436 Only works with tuples of length 2 437 438 Params: 439 index = index of the type that will be made into StructPrevious 440 T... = tuple of the types 441 442 ***************************************************************************/ 443 444 template AddStructPrevious ( ubyte index, T... ) 445 { 446 static assert ( T.length == 2 ); 447 static assert ( index <= 1 ); 448 449 static if ( index == 0 ) 450 { 451 alias Tuple!(T[0].StructPrevious, T[1]) AddStructPrevious; 452 } 453 else 454 { 455 alias Tuple!(T[0], T[1].StructPrevious) AddStructPrevious; 456 } 457 } 458 459 /*************************************************************************** 460 461 Takes a type tuple and transforms it into the same type tuple but with 462 the types being pointers to the original types. 463 464 Params: 465 T = tuple to convert 466 467 ***************************************************************************/ 468 469 template AddPtr ( T... ) 470 { 471 static if ( T.length > 0 ) 472 { 473 alias Tuple!(T[0]*, AddPtr!(T[1..$])) AddPtr; 474 } 475 else 476 { 477 alias T AddPtr; 478 } 479 } 480 481 /*************************************************************************** 482 483 Struct to be used for creating unique hash 484 485 ***************************************************************************/ 486 487 private struct KeyValueStruct( K, V) 488 { 489 K k; 490 V v; 491 } 492 493 /*************************************************************************** 494 495 Evaluates to the fnv1 hash of the types that make up the struct. 496 If S is no struct, mangled name of the type is used. 497 498 Params: 499 S = struct containing key & value 500 501 ***************************************************************************/ 502 503 template StructHash ( S ) 504 { 505 static if ( is (typeof(TypeHash!(S))) ) 506 { 507 static immutable StructHash = TypeHash!(S); 508 } 509 else 510 { 511 static immutable StructHash = StaticFnv1a64!(typeof(S.k).mangleof ~ 512 typeof(S.v).mangleof); 513 } 514 } 515 516 /*************************************************************************** 517 518 File header writen at the beginning of a dumped HashMap 519 520 ***************************************************************************/ 521 522 private struct FileHeader ( K, V, ubyte VERSION ) 523 { 524 /*********************************************************************** 525 526 Magic Marker, making sure that this file is really what we expect it 527 to be 528 529 ***********************************************************************/ 530 531 uint marker = MAGIC_MARKER; 532 533 /*********************************************************************** 534 535 Version of the FileHeader. Should be changed for any modification 536 537 ***********************************************************************/ 538 539 ubyte versionNumber = VERSION; 540 541 /*********************************************************************** 542 543 Hash or Version of the struct types, making sure that the key and 544 value types are the same as when this file was saved. 545 546 ***********************************************************************/ 547 548 static if ( VERSION <= 2 && 549 !is ( K == class) && !is (V == class) && 550 !is ( K == interface) && !is (V == interface) ) 551 { 552 ulong hash = TypeHash!(KeyValueStruct!(K,V)); 553 } 554 555 static if ( VERSION >= 3 && VERSION <= 4 ) 556 { 557 static if ( Version.Info!(K).exists ) 558 { 559 ubyte key_version = Version.Info!(K).number; 560 } 561 562 static if ( Version.Info!(V).exists ) 563 { 564 ubyte value_version = Version.Info!(V).number; 565 } 566 567 static if ( !Version.Info!(K).exists && 568 !Version.Info!(V).exists && 569 !is ( K == class) && !is (V == class) && 570 !is ( K == interface) && !is (V == interface) ) 571 { 572 ulong hash = TypeHash!(KeyValueStruct!(K,V)); 573 } 574 } 575 else static if ( VERSION >= 5 ) 576 { 577 ubyte key_version = Version.Info!(K).number; 578 ubyte value_version = Version.Info!(V).number; 579 ulong hash = StructHash!(KeyValueStruct!(K,V)); 580 } 581 } 582 583 /*************************************************************************** 584 585 Delegate used to put values in a map 586 587 ***************************************************************************/ 588 589 template PutterDg ( K, V ) 590 { 591 alias void delegate ( ref K, ref V ) PutterDg; 592 } 593 594 /*************************************************************************** 595 596 Delegate used to add new values from a map 597 598 ***************************************************************************/ 599 600 template AdderDg ( K, V ) 601 { 602 alias void delegate ( void delegate ( ref K, ref V ) ) AdderDg; 603 } 604 605 /*************************************************************************** 606 607 Pair of buffers used for conversion 608 609 ***************************************************************************/ 610 611 struct BufferPair 612 { 613 void[] first, second; 614 } 615 616 /*************************************************************************** 617 618 buffered output instance 619 620 ***************************************************************************/ 621 622 private BufferedOutput buffered_output; 623 624 /*************************************************************************** 625 626 buffered input instance 627 628 ***************************************************************************/ 629 630 private BufferedInput buffered_input; 631 632 /*************************************************************************** 633 634 Temporary buffers to convert value structs 635 636 ***************************************************************************/ 637 638 private BufferPair value_convert_buffer; 639 640 /*************************************************************************** 641 642 Temporary buffers to convert key structs 643 644 ***************************************************************************/ 645 646 private BufferPair key_convert_buffer; 647 648 /*************************************************************************** 649 650 Writing buffer for the StructDumper 651 652 ***************************************************************************/ 653 654 private void[] dump_buffer; 655 656 /*************************************************************************** 657 658 Struct converter with internal buffer 659 660 ***************************************************************************/ 661 662 private Converter converter; 663 664 /*************************************************************************** 665 666 Constructor 667 668 Params: 669 buffer_size = optional, size of the input/output buffers used for 670 reading/writing 671 672 ***************************************************************************/ 673 674 this ( size_t buffer_size = 64 * 1024 ) 675 { 676 this.buffered_output = new BufferedOutput(null, buffer_size); 677 this.buffered_input = new BufferedInput(null, buffer_size); 678 679 this.converter = new Converter; 680 } 681 682 683 /*************************************************************************** 684 685 Writes a map to a file. 686 687 Params: 688 map = instance of the array map to dump 689 file_path = path to where the map should be dumped to 690 691 ***************************************************************************/ 692 693 public void dump ( K, V ) ( Map!(V, K) map, cstring file_path ) 694 { 695 scope file = new File(file_path, File.Style(File.Access.Write, 696 File.Open.Create, 697 File.Share.None)); 698 699 this.dumpConduit(map, file); 700 } 701 702 703 /*************************************************************************** 704 705 Writes a map to an IO device. 706 707 Params: 708 io_device = the device to dump to 709 file_path = path to where the map should be dumped to 710 711 ***************************************************************************/ 712 713 public void dumpConduit ( K, V ) ( Map!(V, K) map, IConduit io_device ) 714 { 715 scope adder = ( void delegate ( ref K, ref V ) add ) 716 { 717 foreach ( ref k, ref v; map ) 718 { 719 add(k, v); 720 } 721 }; 722 723 this.dumpDgConduit!(K, V)(io_device, adder); 724 } 725 726 727 /*************************************************************************** 728 729 Writes a map to a file. 730 731 Params: 732 file_path = path to where the map should be dumped to 733 adder = function called with a delegate that can be used to add 734 elements that are to be dumped. Once that delegate 735 returns, the rest will be written. 736 737 ***************************************************************************/ 738 739 public void dumpDg ( K, V ) ( cstring file_path, AdderDg!(K, V) adder ) 740 { 741 scope file = new File(file_path, File.Style(File.Access.Write, 742 File.Open.Create, 743 File.Share.None)); 744 this.dumpDgConduit!(K, V)(file, adder); 745 } 746 747 748 /*************************************************************************** 749 750 Writes a map to an IO device. 751 752 Params: 753 io_device = the device to dump to 754 adder = function called with a delegate that can be used to add 755 elements that are to be dumped. Once that delegate 756 returns, the rest will be written. 757 758 ***************************************************************************/ 759 760 public void dumpDgConduit ( K, V ) ( IConduit io_device, 761 AdderDg!(K, V) adder ) 762 { 763 this.buffered_output.output(io_device); 764 this.buffered_output.clear(); 765 766 this.dumpInternal!(K,V)(this.buffered_output, adder); 767 } 768 769 770 /*************************************************************************** 771 772 Internal dump function 773 774 Params: 775 K = Key type of the map 776 V = Value type of the map 777 HeaderVersion = version of the file header we're trying to load 778 buffered = stream to write to 779 adder = function called with a delegate that can be used to add 780 elements that aare to be dumped. Once the delegate 781 returns the writing process will be finalized 782 783 ***************************************************************************/ 784 785 private void dumpInternal ( K, V, ubyte HeaderVersion = HEADER_VERSION ) 786 ( BufferedOutput buffered, AdderDg!(K, V) adder ) 787 { 788 size_t nr_rec = 0; 789 790 FileHeader!(K,V, HeaderVersion) fh; 791 792 SimpleStreamSerializer.write(buffered, fh); 793 // Write dummy value first 794 SimpleStreamSerializer.write(buffered, nr_rec); 795 796 scope addKeyVal = ( ref K key, ref V val ) 797 { 798 SimpleStreamSerializerArrays.write!(K)(buffered, key); 799 SimpleStreamSerializerArrays.write!(V)(buffered, val); 800 nr_rec++; 801 }; 802 803 scope(exit) 804 { 805 buffered.flush(); 806 807 // Write actual length now 808 buffered.seek(fh.sizeof); 809 SimpleStreamSerializer.write(buffered, nr_rec); 810 811 buffered.flush(); 812 } 813 814 adder(addKeyVal); 815 } 816 817 818 /*************************************************************************** 819 820 loads dumped map content from the file system 821 822 Does not support structs with dynamic arrays yet. 823 824 Throws: 825 Exception when the file has not the expected fileheader and 826 other Exceptions for various kinds of errors (file not found, etc) 827 828 Params: 829 K = key of the array map 830 V = value of the corresponding key 831 map = instance of the array map 832 file_path = path to the file to load from 833 834 ***************************************************************************/ 835 836 public void load ( K, V ) ( Map!(V, K) map, cstring file_path ) 837 { 838 scope file = new File(file_path, File.ReadExisting); 839 840 this.loadConduit(map, file); 841 } 842 843 844 /*************************************************************************** 845 846 loads dumped map content from the file system 847 848 Does not support structs with dynamic arrays yet. 849 850 Throws: 851 Exception when the file has not the expected fileheader and 852 other Exceptions for various kinds of errors (file not found, etc) 853 854 Params: 855 K = key of the array map 856 V = value of the corresponding key 857 map = instance of the array map 858 io_device = the device to load from 859 860 ***************************************************************************/ 861 862 public void loadConduit ( K, V ) ( Map!(V, K) map, IConduit io_device ) 863 { 864 scope putter = ( ref K k, ref V v ) 865 { 866 bool added = false; 867 868 static if ( isArrayType!(V) == ArrayKind.Dynamic ) 869 { 870 copy(*map.put(k, added), v); 871 } 872 else 873 { 874 (*map.put(k, added)) = v; 875 } 876 877 // If added key is an array and new don't reuse the memory it 878 // references 879 static if ( isArrayType!(K) == ArrayKind.Dynamic ) 880 { 881 if ( added ) 882 { 883 k = k.dup; 884 } 885 } 886 }; 887 888 this.loadDgConduit!(K, V)(io_device, putter); 889 } 890 891 892 /*************************************************************************** 893 894 Loads dumped map content from the file system 895 896 Does not support structs with dynamic arrays yet. 897 898 Throws: 899 Exception when the file has not the expected fileheader and 900 other Exceptions for various kinds of errors (file not found, etc) 901 902 Params: 903 K = key of the array map 904 V = value of the corresponding key 905 file_path = path to the file to load from 906 putter = function called for each entry to insert it into the map 907 908 ***************************************************************************/ 909 910 public void loadDg ( K, V ) ( cstring file_path, PutterDg!(K, V) putter ) 911 { 912 scope file = new File(file_path, File.ReadExisting); 913 914 this.loadDgConduit!(K, V)(file, putter); 915 } 916 917 918 /*************************************************************************** 919 920 Loads dumped map content from the file system 921 922 Does not support structs with dynamic arrays yet. 923 924 Throws: 925 Exception when the file has not the expected fileheader and 926 other Exceptions for various kinds of errors (file not found, etc) 927 928 Params: 929 K = key of the array map 930 V = value of the corresponding key 931 io_device = the device to load from 932 putter = function called for each entry to insert it into the map 933 934 ***************************************************************************/ 935 936 public void loadDgConduit ( K, V ) ( IConduit io_device, 937 PutterDg!(K, V) putter ) 938 { 939 this.buffered_input.input(io_device); 940 941 loadInternal!(K,V)(this.buffered_input, putter); 942 } 943 944 945 /*************************************************************************** 946 947 Loads dumped map content from a input stream 948 949 Does not support structs with dynamic arrays yet. 950 951 Throws: 952 Exception when the file has not the expected fileheader and 953 other Exceptions for various kinds of errors (file not found, etc) 954 955 Params: 956 K = key of the array map 957 V = value of the corresponding key 958 HeaderVersion = version of the file header we're trying to load 959 buffered = input stream to read from 960 putter = function called for each entry to insert it into the map 961 962 ***************************************************************************/ 963 964 private void loadInternal ( K, V, ubyte HeaderVersion = HEADER_VERSION ) 965 ( BufferedInput buffered, PutterDg!(K, V) putter ) 966 { 967 bool raw_load = false; 968 969 FileHeader!(K,V, HeaderVersion) fh_expected; 970 FileHeader!(K,V, HeaderVersion) fh_actual; 971 972 fh_actual.versionNumber = ubyte.max; 973 974 buffered.seek(0); 975 buffered.compress(); 976 buffered.populate(); 977 978 SimpleStreamSerializer.read(buffered, fh_actual); 979 980 if ( fh_actual.marker != fh_expected.marker ) 981 { 982 throw new Exception(format("Expected Magic Marker {}, got {}", 983 fh_expected.marker, fh_actual.marker), 984 __FILE__, __LINE__); 985 } 986 else if ( fh_actual.versionNumber != fh_expected.versionNumber ) 987 { 988 if ( fh_actual.versionNumber == 2 ) 989 { 990 return this.loadOld!(K,V)(buffered, putter); 991 } 992 if ( fh_actual.versionNumber == 3 ) 993 { 994 raw_load = true; 995 } 996 else if ( fh_actual.versionNumber == 4 ) 997 { 998 return this.loadInternal!(K, V, 4)(buffered, putter); 999 } 1000 else 1001 { 1002 throw new Exception( 1003 format("Expected version of file header to be {}, but got" 1004 ~ " {}, aborting!", fh_expected.versionNumber, 1005 fh_actual.versionNumber), 1006 __FILE__, __LINE__); 1007 } 1008 } 1009 1010 bool conv; 1011 1012 // Code for converting from older Key/Value structs 1013 static if ( is ( typeof ( fh_expected.key_version ) ) ) 1014 { 1015 if ( fh_expected.key_version != 0 ) 1016 { 1017 conv = this.handleVersion!(MapSerializer.loadInternal, 0, K, V) 1018 (fh_actual.key_version, fh_expected.key_version, 1019 this.key_convert_buffer, putter, buffered); 1020 1021 if ( conv ) return; 1022 } 1023 } 1024 1025 static if ( is ( typeof ( fh_expected.value_version ) ) ) 1026 { 1027 if ( fh_expected.value_version != 0 ) 1028 { 1029 conv = this.handleVersion!(MapSerializer.loadInternal, 1, K, V) 1030 (fh_actual.value_version, fh_expected.value_version, 1031 this.value_convert_buffer, putter, buffered); 1032 1033 if ( conv ) return; 1034 } 1035 } 1036 1037 static if ( is ( typeof ( fh_expected.hash ) ) ) 1038 { 1039 if ( fh_expected.hash != fh_actual.hash ) 1040 { 1041 throw new Exception( 1042 format("File struct differ from struct used to load " 1043 ~ "(expected 0x{:X} but got 0x{:X})!", 1044 fh_expected.hash, fh_actual.hash), 1045 __FILE__, __LINE__); 1046 } 1047 } 1048 1049 size_t nr_rec; 1050 1051 if ( buffered.readable < nr_rec.sizeof ) 1052 { 1053 buffered.compress(); 1054 buffered.populate(); 1055 } 1056 SimpleStreamSerializerArrays.read(buffered, nr_rec); 1057 1058 for ( ulong i=0; i < nr_rec;i++ ) 1059 { 1060 K key; 1061 V value; 1062 1063 if ( buffered.readable < V.sizeof + K.sizeof ) 1064 { 1065 buffered.compress(); 1066 buffered.populate(); 1067 } 1068 1069 if ( raw_load ) 1070 { 1071 SimpleStreamSerializer.read!(K)(buffered, key); 1072 SimpleStreamSerializer.read!(V)(buffered, value); 1073 } 1074 else 1075 { 1076 SimpleStreamSerializerArrays.read!(K)(buffered, key); 1077 SimpleStreamSerializerArrays.read!(V)(buffered, value); 1078 } 1079 1080 putter(key, value); 1081 } 1082 } 1083 1084 1085 /*************************************************************************** 1086 1087 Checks if a struct needs to be converted and converts it if required 1088 1089 Params: 1090 loadFunc = function to use to load older version of the struct 1091 index = index of the type in the tuple that should be 1092 checked/converted 1093 T = tuple of key/value types 1094 actual = version that was found in the data 1095 expected = version that is desired 1096 buffer = conversion buffer to use 1097 putter = delegate to use to put the data into the map 1098 buffered = buffered input stream 1099 1100 Returns: 1101 true if conversion happened, else false 1102 1103 Throws: 1104 if conversion failed 1105 1106 ***************************************************************************/ 1107 1108 private bool handleVersion ( alias loadFunc, size_t index, T... ) 1109 ( Version.Type actual, 1110 Version.Type expected, 1111 ref BufferPair buffer, 1112 scope void delegate ( ref T ) putter, 1113 BufferedInput buffered ) 1114 { 1115 if ( actual < expected ) 1116 { 1117 return this.tryConvert!(true, MapSerializer.loadInternal, index, T) 1118 (buffer, putter, buffered); 1119 } 1120 1121 return false; 1122 } 1123 1124 1125 /*************************************************************************** 1126 1127 Checks if a struct needs to be converted and converts it if required 1128 1129 Params: 1130 throw_if_unable = if true, an exception is thrown if we can't 1131 convert this struct 1132 loadFunc = function to use to load older version of the struct 1133 index = index of the type in the tuple that should be 1134 checked/converted 1135 T = tuple of key/value types 1136 buffer = conversion buffer to use 1137 putter = delegate to use to put the data into the map 1138 buffered = buffered input stream 1139 1140 Returns: 1141 true if a conversion happened, false if we can't convert it and 1142 throw_if_unable is false 1143 1144 Throws: 1145 if throw_if_unable is true and we couldn't convert it 1146 1147 ***************************************************************************/ 1148 1149 private bool tryConvert ( bool throw_if_unable, alias loadFunc, 1150 size_t index, T... ) 1151 ( ref BufferPair buffer, 1152 scope void delegate ( ref T ) putter, 1153 BufferedInput buffered ) 1154 { 1155 static assert ( T.length == 2 ); 1156 1157 static immutable other = index == 1 ? 0 : 1; 1158 1159 static if ( Version.Info!(T[index]).exists ) 1160 { 1161 // Normally we should be able to use `MissingVersion.exists` 1162 // directly (which is `false`), but it triggers a DMD bug in D1. 1163 // DMD doesn't seem able to do anything with 1164 // `Version.Info!(T[index]).prev` except compare it with a type. 1165 // This can be checked by replacing `can_convert` with: 1166 // `const can_convert = Version.Info!(T[index]).prev.exists;` 1167 static immutable can_convert 1168 = !is(Version.Info!(T[index]).prev == MissingVersion); 1169 } 1170 else 1171 { 1172 static immutable can_convert = false; 1173 } 1174 1175 static if ( can_convert ) 1176 { 1177 alias AddStructPrevious!(index, T) TWithPrev; 1178 1179 scope convPut = ( ref TWithPrev keyval ) 1180 { 1181 auto buf = &keyval[index] is buffer.first.ptr ? 1182 &buffer.second : &buffer.first; 1183 1184 AddPtr!(T) res; 1185 1186 Serializer.serialize(keyval[index], *buf); 1187 res[index] = this.converter.convert!(T[index], TWithPrev[index])(*buf).ptr; 1188 res[other] = &keyval[other]; 1189 1190 putter(*res[0], *res[1]); 1191 }; 1192 1193 loadFunc!(TWithPrev)(buffered, convPut); 1194 1195 return true; 1196 } 1197 else static if ( throw_if_unable ) 1198 { 1199 throw new Exception("Cannot convert to new version!", 1200 __FILE__, __LINE__); 1201 } 1202 else 1203 { 1204 return false; 1205 } 1206 } 1207 1208 1209 /*************************************************************************** 1210 1211 Previous load function, kept so that old versions can still be loaded 1212 1213 Params: 1214 K = type of the key 1215 V = type of the value 1216 buffered = stream to read from 1217 putter = delegate used to write loaded records to the map 1218 1219 ***************************************************************************/ 1220 1221 private void loadOld ( K, V ) ( BufferedInput buffered, 1222 scope void delegate ( ref K, ref V ) putter ) 1223 { 1224 size_t nr_rec; 1225 1226 FileHeader!(K,V,2) fh_expected; 1227 FileHeader!(K,V,2) fh_actual; 1228 1229 buffered.seek(0); 1230 buffered.compress(); 1231 buffered.populate(); 1232 1233 SimpleStreamSerializer.read(buffered, fh_actual); 1234 1235 if ( fh_actual.marker != fh_expected.marker ) 1236 { 1237 throw new Exception("Magic Marker mismatch"); 1238 } 1239 else if ( fh_actual.versionNumber != fh_expected.versionNumber ) 1240 { 1241 throw new Exception("Version of file header " 1242 ~ " does not match our version, aborting!"); 1243 } 1244 else static if ( is ( typeof ( fh_expected.hash ) ) ) 1245 if ( fh_actual.hash != fh_expected.hash ) 1246 { 1247 bool conv; 1248 1249 // convert from a previous key struct to the current 1250 conv = this.tryConvert!(false, MapSerializer.loadOld, 0, K, V) 1251 (this.key_convert_buffer, putter, buffered); 1252 1253 if ( conv ) return; 1254 1255 // convert from a previous value struct to the current 1256 conv = this.tryConvert!(false, MapSerializer.loadOld, 1, K, V) 1257 (this.value_convert_buffer, putter, buffered); 1258 1259 if ( conv ) return; 1260 1261 throw new Exception("Unable to convert structs " ~ K.stringof ~ ", " ~ 1262 V.stringof ~ 1263 " to our structs, aborting!"); 1264 } 1265 1266 if ( buffered.readable < nr_rec.sizeof ) 1267 { 1268 buffered.compress(); 1269 buffered.populate(); 1270 } 1271 SimpleStreamSerializerArrays.read(buffered, nr_rec); 1272 1273 for ( ulong i=0; i < nr_rec;i++ ) 1274 { 1275 K key; 1276 V value; 1277 1278 if ( buffered.readable < V.sizeof + K.sizeof ) 1279 { 1280 buffered.compress(); 1281 buffered.populate(); 1282 } 1283 1284 SimpleStreamSerializer.read!(K)(buffered, key); 1285 SimpleStreamSerializer.read!(V)(buffered, value); 1286 putter(key, value); 1287 } 1288 } 1289 } 1290 1291 1292 /******************************************************************************* 1293 1294 Unittests 1295 1296 *******************************************************************************/ 1297 1298 version (unittest) 1299 { 1300 import ocean.io.device.MemoryDevice, 1301 ocean.io.digest.Fnv1, 1302 ocean.core.Test, 1303 ocean.util.container.map.model.StandardHash, 1304 ocean.util.container.map.Map, 1305 ocean.util.container.map.HashMap; 1306 1307 import ocean.meta.types.Arrays; 1308 1309 /*************************************************************************** 1310 1311 Slightly specialized version of Map that would simply take the raw value 1312 of structs to hash them 1313 1314 ***************************************************************************/ 1315 1316 class StructhashMap (V, K) : Map!(V, K) 1317 { 1318 /*********************************************************************** 1319 1320 Constructor 1321 1322 Params: 1323 n = amount of expected elements 1324 1325 ***********************************************************************/ 1326 1327 public this ( size_t n ) 1328 { 1329 super(n); 1330 } 1331 1332 /*********************************************************************** 1333 1334 Dumb and easy toHash method. 1335 1336 Simply takes the key and passes it directly to fnv1a. 1337 1338 Parameters: 1339 key = key of which the hash is desired 1340 1341 Returns: 1342 hash of the key 1343 1344 ***********************************************************************/ 1345 1346 public override hash_t toHash ( in K key ) 1347 { 1348 return Fnv1a.fnv1(key); 1349 } 1350 1351 } 1352 1353 /*************************************************************************** 1354 1355 Dump function that dumps in the old format, to test whether we can still 1356 read it (and convert it) 1357 1358 Params: 1359 K = type of key 1360 V = type of value 1361 buffered = output stream to write to 1362 adder = delegate called with a delegate that can be used to add 1363 values 1364 1365 ***************************************************************************/ 1366 1367 void dumpOld ( K, V ) ( BufferedOutput buffered, 1368 MapSerializer.AdderDg!(K, V) adder ) 1369 { 1370 size_t nr_rec = 0; 1371 1372 MapSerializer.FileHeader!(K,V,2) fh; 1373 1374 SimpleStreamSerializer.write(buffered, fh); 1375 // Write dummy value for now 1376 SimpleStreamSerializer.write(buffered, nr_rec); 1377 1378 scope addKeyVal = ( ref K key, ref V val ) 1379 { 1380 SimpleStreamSerializer.write!(K)(buffered, key); 1381 SimpleStreamSerializer.write!(V)(buffered, val); 1382 nr_rec++; 1383 }; 1384 1385 scope(exit) 1386 { 1387 buffered.flush(); 1388 1389 // Write actual length now 1390 buffered.seek(fh.sizeof); 1391 SimpleStreamSerializer.write(buffered, nr_rec); 1392 1393 buffered.flush(); 1394 } 1395 1396 adder(addKeyVal); 1397 } 1398 1399 /*************************************************************************** 1400 1401 Test writing & loading of the given combination of struct types on a 1402 virtual file 1403 1404 Depending on the exact combinations, the key structs should offer the 1405 following methods: 1406 1407 * compare ( <other_key_struct> * ) - compare the two structs 1408 * K old() - convert this struct to the older one 1409 1410 The value structs should offer only a compare function. 1411 1412 Params: 1413 K = type of the key to write 1414 V = type of the value to write 1415 KNew = type of the key to read, automatic conversion will happen 1416 VNew = type of the value to read, automatic conversion will happen 1417 custom_dump = optional, custom code to use for dumping the data. 1418 The code is expected to define a variable 1419 "header_size" containing the size of the header 1420 1421 ***************************************************************************/ 1422 1423 void testCombination ( K, V, KNew, VNew, istring custom_dump = "" ) 1424 ( size_t iterations ) 1425 { 1426 auto t = new NamedTest("Combination {" ~ 1427 "K = " ~ K.stringof ~ 1428 ", V = " ~ V.stringof ~ 1429 ", KNew = " ~ KNew.stringof ~ 1430 ", VNew = " ~ VNew.stringof ~ "}" 1431 ); 1432 static immutable ValueArraySize = 200; 1433 1434 scope array = new MemoryDevice; 1435 scope map = new StructhashMap!(V, K)(iterations); 1436 scope serializer = new MapSerializer; 1437 1438 // helper to get a key from size_t 1439 K initKey ( int i ) 1440 { 1441 static if ( is ( K == struct ) ) 1442 { 1443 return K(i); 1444 } 1445 else 1446 { 1447 return i; 1448 } 1449 } 1450 1451 // helper to get old key from new key 1452 K fromNew ( KNew k ) 1453 { 1454 static if ( is ( K == struct ) ) 1455 { 1456 return k.old(); 1457 } 1458 else 1459 { 1460 return k; 1461 } 1462 } 1463 1464 V initVal ( int i ) 1465 { 1466 static if ( isArrayType!(V) == ArrayKind.Dynamic ) 1467 { 1468 alias ElementTypeOf!(V) VA; 1469 1470 auto r = new VA[ValueArraySize]; 1471 1472 foreach ( ref e; r ) 1473 { 1474 e = VA(i); 1475 } 1476 1477 return r; 1478 } 1479 else return V(i); 1480 } 1481 1482 // Fill test map 1483 for ( int i = 0; i < iterations; ++i ) 1484 { 1485 *map.put(initKey(i)) = initVal(i); 1486 } 1487 1488 scope adder = ( void delegate ( ref K, ref V ) add ) 1489 { 1490 foreach ( ref k, ref v; map ) 1491 { 1492 add(k, v); 1493 } 1494 }; 1495 1496 // Dump test map (to memory) 1497 static if ( custom_dump.length > 0 ) 1498 { 1499 mixin(custom_dump); 1500 } 1501 else 1502 { 1503 serializer.buffered_output.output(array); 1504 serializer.dumpInternal!(K, V)(serializer.buffered_output, adder); 1505 1506 auto header_size = MapSerializer.FileHeader!(K, V, MapSerializer.HEADER_VERSION).sizeof; 1507 } 1508 1509 // Check size of dump 1510 static if ( isArrayType!(V) == ArrayKind.Dynamic ) 1511 { 1512 t.test(array.bufferSize() == 1513 (K.sizeof + size_t.sizeof + 1514 ElementTypeOf!(V).sizeof * ValueArraySize) * 1515 iterations + header_size + size_t.sizeof, 1516 "Written size is not the expected value!"); 1517 } 1518 else 1519 { 1520 t.test(array.bufferSize() == (K.sizeof + V.sizeof) * 1521 iterations + header_size + size_t.sizeof, 1522 "Written size is not the expected value!"); 1523 } 1524 1525 // Check load function 1526 size_t amount_loaded = 0; 1527 scope checker = ( ref KNew k, ref VNew v ) 1528 { 1529 amount_loaded++; 1530 static if ( isArrayType!(VNew) == ArrayKind.Dynamic ) 1531 { 1532 foreach ( i, el; v ) 1533 { 1534 t.test(el.compare(&(*map.get(fromNew(k)))[i]), 1535 "Loaded item unequal saved item!"); 1536 } 1537 } 1538 else 1539 { 1540 t.test(v.compare(map.get(fromNew(k))), 1541 "Loaded item unequal saved item!"); 1542 } 1543 }; 1544 1545 array.seek(0); 1546 serializer.buffered_input.input(array); 1547 serializer.loadInternal!(KNew, VNew)(serializer.buffered_input, checker); 1548 1549 t.test(amount_loaded == map.length, 1550 "Amount of loaded items unequal amount of written items!"); 1551 } 1552 } 1553 1554 unittest 1555 { 1556 static immutable Iterations = 10_000; 1557 1558 static immutable old_load_code = 1559 `scope bufout = new BufferedOutput(array, 2048); 1560 bufout.seek(0); 1561 dumpOld!(K, V)(bufout, adder); 1562 1563 auto header_size = MapSerializer.FileHeader!(K, V, 2).sizeof;`; 1564 1565 static immutable version4_load_code = 1566 `serializer.buffered_output.output(array); 1567 serializer.dumpInternal!(K, V, 4)(serializer.buffered_output, adder); 1568 1569 auto header_size = MapSerializer.FileHeader!(K, V, 4).sizeof; 1570 `; 1571 1572 static struct TestNoVersion 1573 { 1574 long i; 1575 1576 static TestNoVersion opCall ( long i ) 1577 { 1578 TestNoVersion t; 1579 t.i = i*2; 1580 return t; 1581 } 1582 1583 bool compare ( TestNoVersion* other ) 1584 { 1585 return i == other.i; 1586 } 1587 } 1588 1589 static struct Test1 1590 { 1591 enum StructVersion = 0; 1592 1593 long i; 1594 } 1595 1596 static struct Test2 1597 { 1598 enum StructVersion = 1; 1599 alias Test1 StructPrevious; 1600 1601 long i; 1602 long o; 1603 static void convert_o ( ref Test1 t, ref Test2 dst ) { dst.o = t.i+1; } 1604 1605 bool compare ( Test1* old ) 1606 { 1607 return old.i == i && old.i+1 == o; 1608 } 1609 1610 bool compare ( Test2* old ) 1611 { 1612 return *old == this; 1613 } 1614 } 1615 1616 static struct OldStruct 1617 { 1618 enum StructVersion = 0; 1619 1620 int old; 1621 1622 bool compare ( OldStruct * o ) 1623 { 1624 return *o == this; 1625 } 1626 } 1627 1628 static struct NewStruct 1629 { 1630 enum StructVersion = 1; 1631 alias OldStruct StructPrevious; 1632 1633 int old; 1634 1635 int a_bit_newer; 1636 static void convert_a_bit_newer ( ref OldStruct, ref NewStruct dst ) 1637 { 1638 dst.a_bit_newer = dst.old+1; 1639 } 1640 1641 bool compare ( OldStruct* old ) 1642 { 1643 return old.old == this.old && 1644 old.old+1 == a_bit_newer; 1645 } 1646 } 1647 1648 static struct OldKey 1649 { 1650 enum StructVersion = 0; 1651 1652 int old2; 1653 1654 bool compare ( OldKey * o ) 1655 { 1656 return *o == this; 1657 } 1658 1659 OldKey old ( ) 1660 { 1661 return this; 1662 } 1663 } 1664 1665 static struct NewKey 1666 { 1667 enum StructVersion = 1; 1668 alias OldKey StructPrevious; 1669 1670 int old1; 1671 1672 static void convert_old1 ( ref OldKey o, ref NewKey dst ) 1673 { 1674 dst.old1 = o.old2; 1675 } 1676 1677 int newer; 1678 1679 static void convert_newer ( ref OldKey o, ref NewKey dst ) 1680 { 1681 dst.newer = o.old2+1; 1682 } 1683 1684 bool compare ( OldKey * oldk ) 1685 { 1686 return oldk.old2 == old1 && oldk.old2+1 == newer; 1687 } 1688 1689 OldKey old ( ) 1690 { 1691 return OldKey(old1); 1692 } 1693 } 1694 1695 static struct NewerKey 1696 { 1697 enum StructVersion = 2; 1698 alias NewKey StructPrevious; 1699 1700 int old1; 1701 int wops; 1702 1703 static void convert_wops ( ref NewKey k, ref NewerKey dst ) 1704 { 1705 dst.wops = k.old1; 1706 } 1707 1708 bool compare ( NewKey * n ) 1709 { 1710 return n.old1 == old1 && wops == n.old1; 1711 } 1712 1713 OldKey old ( ) 1714 { 1715 return OldKey(old1); 1716 } 1717 } 1718 1719 static struct NewerStruct 1720 { 1721 enum StructVersion = 2; 1722 alias NewStruct StructPrevious; 1723 1724 int old; 1725 long of; 1726 1727 static void convert_of ( ref NewStruct n, ref NewerStruct dst ) 1728 { 1729 dst.of = n.a_bit_newer; 1730 } 1731 1732 bool compare ( OldStruct * olds ) 1733 { 1734 return olds.old == old; 1735 } 1736 } 1737 1738 static struct NoPrevious 1739 { 1740 enum StructVersion = 42; 1741 1742 int hello; 1743 1744 bool compare ( NoPrevious* olds ) 1745 { 1746 return olds.hello == hello; 1747 } 1748 } 1749 1750 static struct SinglePrevious 1751 { 1752 enum StructVersion = 42; 1753 int hello42; 1754 1755 static void convert_hello42 ( ref StructPrevious p, ref SinglePrevious dst ) 1756 { 1757 dst.hello42 = p.hello + 42; 1758 } 1759 1760 bool compare ( StructPrevious* olds ) 1761 { 1762 return this.hello42 == (olds.hello + 42); 1763 } 1764 1765 static struct StructPrevious 1766 { 1767 enum StructVersion = 41; 1768 alias SinglePrevious StructNext; 1769 int hello; 1770 1771 bool compare ( StructNext* news ) 1772 { 1773 return this.hello == (news.hello42 - 42); 1774 } 1775 } 1776 } 1777 1778 1779 // Test creation of a SerializingMap instance 1780 class HashingSerializingMap : SerializingMap!(int,int) 1781 { 1782 public this ( size_t n, float load_factor = 0.75 ) 1783 { 1784 super(n, load_factor); 1785 } 1786 1787 override: 1788 mixin StandardHash.toHash!(int); 1789 } 1790 1791 1792 // Test same and old version 1793 testCombination!(hash_t, Test1, hash_t, Test2)(Iterations); 1794 testCombination!(hash_t, Test2, hash_t, Test2)(Iterations); 1795 1796 // Test Arrays 1797 testCombination!(hash_t, Test2[], hash_t, Test2[])(Iterations); 1798 1799 // Test unversioned structs 1800 testCombination!(hash_t, TestNoVersion, hash_t, TestNoVersion)(Iterations); 1801 1802 // Test old versions 1803 testCombination!(hash_t, TestNoVersion, hash_t, TestNoVersion, old_load_code)(Iterations); 1804 1805 // Test conversion of old files to new ones 1806 testCombination!(hash_t, OldStruct, hash_t, NewStruct, old_load_code)(Iterations); 1807 1808 // Test conversion of old files with 1809 // different key versions to new ones 1810 testCombination!(OldKey, OldStruct, OldKey, OldStruct, old_load_code)(Iterations); 1811 testCombination!(OldKey, OldStruct, NewKey, OldStruct, old_load_code)(Iterations); 1812 testCombination!(OldKey, OldStruct, NewKey, OldStruct, old_load_code)(Iterations); 1813 testCombination!(OldKey, OldStruct, NewKey, NewStruct, old_load_code)(Iterations); 1814 testCombination!(OldKey, OldStruct, NewerKey, NewStruct, old_load_code)(Iterations); 1815 testCombination!(OldKey, OldStruct, NewerKey, NewerStruct,old_load_code)(Iterations); 1816 1817 1818 ////////////// Test Loading of version 4 header 1819 1820 // Test old versions 1821 testCombination!(hash_t, TestNoVersion, hash_t, TestNoVersion, version4_load_code)(Iterations); 1822 1823 // Test conversion of old files to new ones 1824 testCombination!(hash_t, OldStruct, hash_t, NewStruct, version4_load_code)(Iterations); 1825 1826 // Test conversion of old files with 1827 // different key versions to new ones 1828 testCombination!(OldKey, OldStruct, OldKey, OldStruct, version4_load_code)(Iterations); 1829 testCombination!(OldKey, OldStruct, NewKey, OldStruct, version4_load_code)(Iterations); 1830 testCombination!(OldKey, OldStruct, NewKey, OldStruct, version4_load_code)(Iterations); 1831 testCombination!(OldKey, OldStruct, NewKey, NewStruct, version4_load_code)(Iterations); 1832 testCombination!(OldKey, OldStruct, NewerKey, NewStruct, version4_load_code)(Iterations); 1833 testCombination!(OldKey, OldStruct, NewerKey, NewerStruct,version4_load_code)(Iterations); 1834 1835 // Test that structs that stripped backward compatibily can be used 1836 testCombination!(hash_t, NoPrevious, hash_t, NoPrevious)(Iterations); 1837 testCombination!(hash_t, SinglePrevious.StructPrevious, 1838 hash_t, SinglePrevious)(Iterations); 1839 } 1840 1841 version (unittest) 1842 { 1843 // Make sure structs with a StructNext can be instantiated 1844 struct S ( ubyte V ) 1845 { 1846 enum StructVersion = V; 1847 1848 // comment this out and it works 1849 static if (V == 0) 1850 alias S!(1) StructNext; 1851 1852 static if (V == 1) 1853 alias S!(0) StructPrevious; 1854 } 1855 1856 unittest 1857 { 1858 alias SerializingMap!(S!(0), ulong) Map; 1859 } 1860 } 1861 1862 unittest 1863 { 1864 class Map : SerializingMap!(int, int) 1865 { 1866 this ( ) { super(10); } 1867 1868 // This method has to accept `const(int)` because it overrides templated 1869 // base `toHash (K) (in K key)` method and DMD demands exact qualifier 1870 // match for arguments. 1871 public override hash_t toHash ( in int key ) { return key; } 1872 } 1873 1874 scope device = new MemoryDevice; 1875 auto map = new Map; 1876 *map.put(10) = 20; 1877 map.dump(device); 1878 1879 map.clear(); 1880 test!("==")(map.length, 0); 1881 device.seek(0); 1882 1883 map.load(device); 1884 1885 test!("==")(map.length, 1); 1886 test!("in")(10, map); 1887 test!("==")(map[10], 20); 1888 }