1 module jaster.serialise.serialiser; 2 3 private 4 { 5 import std.stdio; 6 import std.traits, std.format, std.range, std.typecons, std.algorithm, std.exception; 7 import jaster.serialise.archive; 8 } 9 10 alias UseArrayBaseType = Flag!"useBaseType"; 11 alias UsedObjectsT = ArchiveObject[][ArchiveObject]; 12 13 //version = SERIALISER_DEBUG_OUTPUT; 14 15 /++ 16 + This UDA should be attached to any field that is used as an attribute. 17 + 18 + For binary based archives, this likely has no real meaning. 19 + For text based archives, this will likely determine how the value is formatted. 20 + 21 + Example: 22 + For example, with the SDLang archive (`ArchiveSDL`), the following struct. 23 + 24 + ``` 25 + struct Foo 26 + { 27 + int bar; 28 + 29 + @Attribute 30 + string type; 31 + } 32 + ``` 33 + 34 + Would produce the following SDLang file. 35 + 36 + Foo type="SomeType" { 37 + bar 200 38 + } 39 + ++/ 40 struct Attribute {} 41 42 /++ 43 + This UDA should be attached to a single field that represents the main value of a struct. 44 + 45 + For binary based archives, this likely has no real meaning. 46 + For text based archives, this will likely determine how the value is formatted. 47 + 48 + Example: 49 + For example, with the SDLang archive (`ArchiveSDL`), the following struct. 50 + 51 + ``` 52 + struct Foo 53 + { 54 + int bar; 55 + 56 + @MainValue 57 + string type; 58 + } 59 + ``` 60 + 61 + Would produce the following SDLang file. 62 + 63 + Foo "SomeType" { 64 + bar 200 65 + } 66 + ++/ 67 struct MainValue {} 68 69 /++ 70 + This UDA is to give a custom name to a type/field. 71 + 72 + Example: 73 + For example, with the SDLang archive (`ArchiveSDL`), the following struct. 74 + 75 + ``` 76 + @Name("FooBar") 77 + struct Foo 78 + { 79 + int bar; 80 + 81 + @Name("Not_a_type") 82 + string type; 83 + } 84 + ``` 85 + 86 + Would produce the following SDLang file. 87 + 88 + FooBar { 89 + bar 200 90 + Not_a_type "SomeType" 91 + } 92 + ++/ 93 struct Name 94 { 95 string name; 96 } 97 98 /++ 99 + This UDA is used to specify certain settings for a type/field. 100 + 101 + Notes: 102 + If @Setting is applied to the struct (as in the type itself, instead of a field) then the setting 103 + is applied to all of the struct's field as well. 104 + 105 + Example: 106 + Example of applying it to the entire struct. 107 + 108 + ``` 109 + @Setting(Serialiser.Settings.ArrayAsObject) 110 + struct Foo 111 + { 112 + // Gets applied to this as well 113 + int[] yall; 114 + } 115 + ``` 116 + 117 + Example of applying it to a single field. 118 + 119 + ``` 120 + struct Foo 121 + { 122 + int[] bar; // Not affected 123 + 124 + @Setting(Serialiser.Settings.ArrayAsObject) 125 + string[] types; 126 + } 127 + ``` 128 + ++/ 129 struct Setting 130 { 131 Serialiser.Settings settings; 132 } 133 134 /++ 135 + This UDA only functions when placed on a struct field, or an array of structs. 136 + 137 + This UDA tells the serialiser that $(B all) settingss being applied to the attached field 138 + should also be passed down onto all of the struct's fields. 139 + 140 + This is similar to placing an `@Setting` onto the struct type itself, but sometimes 141 + this may not be possible. For example, the `Vector` struct from DLSL might need some 142 + setting tweaks for it to be serialised the way you want, but you can't modify that code at all 143 + so this UDA is the best that you can do. 144 + 145 + TODO: Example. 146 + ++/ 147 struct InheritSettings {} 148 149 /// This UDA marks a field that should be ignored by the serialiser. 150 struct Ignore {} 151 152 /++ 153 + Retrieves the name to use for the given field, for use in serialisation. 154 + 155 + Rules: 156 + The rules for how the name is retrieved are a bit _convoluted_ so they're documented here. 157 + 158 + For non-structs and non-array types: The return value of `F.stringof` is used. 159 + 160 + For struct types: If the struct has an @Name UDA, then that is used. 161 + If not, the name of the return value of `F.stringof` is used. 162 + 163 + For array types: If `UseBase` is set to `UseBase.yes`, then the return value of `getFieldName!(ElementType!F)` is used. 164 + If not, the return value of `F.stringof` is used. 165 + 166 + For when `F` is an alias to a struct's field: The rule for the field's data type will be used, but with one change, 167 + the `F.stringof` value will instead return the name of the field, instead of the type. 168 + ++/ 169 static string getFieldName(alias F, UseArrayBaseType UseBase = UseArrayBaseType.yes)() 170 { 171 static if(hasUDA!(F, Name) 172 && (isType!F 173 || !isArray!(typeof(F)) 174 || !UseBase 175 ) 176 ) 177 return getUDAs!(F, Name)[0].name; 178 else 179 { 180 static if(!isType!F 181 && isArray!(typeof(F)) 182 && is(ElementType!(typeof(F)) == struct) 183 && UseBase) 184 return getFieldName!(ElementType!(typeof(F))); 185 // else static if(is(typeof(F) == struct)) 186 // { 187 // alias Type = typeof(F); 188 // return getFieldName!Type; 189 // } 190 else 191 return __traits(identifier, F); 192 } 193 } 194 195 /++ 196 + A painless way to get the `Serialiser.Settings` for a type/field. 197 + 198 + Returns: 199 + The `Serialiser.Settings` specified by the @Settings UDA for `F`. 200 + Or `Serialiser.Settings.None` if no settings are specified. 201 + ++/ 202 static Serialiser.Settings getSettings(alias F)() 203 { 204 static if(hasUDA!(F, Setting)) 205 return getUDAs!(F, Setting)[0].settings; 206 else 207 return Serialiser.Settings.None; 208 } 209 210 /++ 211 + The default serialiser provided by the engine. 212 + 213 + This serialiser is more oriented towards creating data oriented for text-based archives, 214 + rather than an optimised format that would be better for binary archives. Both can be used 215 + of course. 216 + ++/ 217 final static class Serialiser 218 { 219 /++ 220 + See the UDA called `Settings`. 221 + ++/ 222 enum Settings : ubyte 223 { 224 /// Apply no settings. 225 None = 0, 226 227 /++ 228 + Tells the serialiser that the array field it is attached to, or all array 229 + fields in the attached struct should be serialised as a child object. 230 + 231 + Notes: 232 + This only works on arrays of struct types for now. 233 + 234 + If there are two arrays in the struct that contain the same type 235 + then this settings $(B must) be used for correct serialisation. 236 + 237 + Example: 238 + Take the given struct. 239 + 240 + ``` 241 + struct Point{int x; int y;} 242 + struct Foo 243 + { 244 + Point[] pointA = [Point(60, 60), Point(120, 120)]; 245 + 246 + @Setting(Serialiser.Settings.ArrayAsObject) 247 + Point[] pointB = [Point(60, 60), Point(200, 200)]; 248 + } 249 + ``` 250 + 251 + Using the SDLang archive, this would generate. 252 + 253 + ``` 254 + Foo { 255 + // The ones embedded inside this scope are from 'pointA' 256 + Point { 257 + x 60 258 + y 60 259 + } 260 + 261 + Point { 262 + x 120 263 + y 120 264 + } 265 + 266 + pointB { 267 + Point { 268 + x 60 269 + y 60 270 + } 271 + 272 + Point { 273 + x 200 274 + y 200 275 + } 276 + } 277 + } 278 + ``` 279 + ++/ 280 ArrayAsObject = 1 << 0, 281 282 /++ 283 + Tells the serialiser that the enum's value (instead of it's name) should be used as the serialised output. 284 + 285 + Notes: 286 + This setting only affects fields that are of an enum type. 287 + 288 + For enum types that are to be ORed together (such as an enum representing a set of flags), 289 + this is the only way to correctly serialise the data. 290 + 291 + Example: 292 + Take the given code: 293 + 294 + ``` 295 + enum Flags 296 + { 297 + Flag1 = 1 // 0b01 298 + Flag2 = 2 // 0b10 299 + } 300 + 301 + struct Foo 302 + { 303 + @Setting(Serialiser.Settings.EnumAsValue) 304 + Flags firstFlag = Flags.Flag1 | Flags.Flag2; 305 + Flags secondFlag = Flags.Flag1; 306 + } 307 + ``` 308 + 309 + Using the SDLang archive, this would generate the following: 310 + 311 + ``` 312 + Foo { 313 + firstFlag 3 314 + secondFlag "Flag1" 315 + } 316 + ``` 317 + ++/ 318 EnumAsValue = 1 << 1 319 } 320 321 public static final 322 { 323 /++ 324 + Serialises the given value, using the provided parent. 325 + 326 + Non-Struct & Non-Array types: 327 + By default, these types of data are stored as child objects to the parent, holding a single 328 + value. For example the field `string name` would become an ArchiveObject called name, holding 329 + a single string value. 330 + 331 + A single field can be marked as @MainValue to become the value of the `parent` object. 332 + 333 + While binary archives may support multiple values under an object, text-based ones may only 334 + allow a single value, which i s what @MainValue will be used for. Everything else 335 + must be an attribute or child object. 336 + 337 + Multiple fields can be marked as @Attribute to be used as an attribute. For example, 338 + the field `@Attribute string name` would be added to the parent as an attribute called 'name', 339 + holding a string value. 340 + 341 + Multiple fields can be given the @Name UDA, and the @Setting UDA. Please see their own documentation. 342 + 343 + Struct types: 344 + Struct types by default create a new child object inside the `parent` to store their data. 345 + Each field in the struct follows the same rules as are being described. 346 + 347 + @Setting and @Name can be attached to structs. 348 + 349 + Array types (for non-structs): 350 + By default, these types of arrays will be stored as a child object in the parent, where all of the 351 + array's values are given to the child object. 352 + 353 + This type of array can be used as the @MainValue for a struct. 354 + 355 + @Setting and @Name (more in the 'Array types (shared)' section) are of course supported as well. 356 + 357 + Array types (for structs): 358 + By default, these types of arrays will write out all of the struct child objects directly into the parent. 359 + However, the settings `Serialiser.Settings.ArrayAsObject` can be used to store these children into a seperate 360 + child object. Please refer to it's documentation. 361 + 362 + @Setting and @Name are supported. 363 + 364 + Array types (shared): 365 + The names chosen when serialising arrays (and other types in general) is a bit complicated, so please refer to the documentation for 366 + `getFieldName`. 367 + 368 + Nullable: 369 + If the value is currently null, then nothing about it is serialised. 370 + 371 + Params: 372 + data = The data to serialise. 373 + parent = The parent to serialise the data into. 374 + ++/ 375 void serialise(T)(T data, ArchiveObject parent) 376 { 377 doSerialise!(T, T, T, Settings.None)(data, parent); 378 } 379 380 /++ 381 + Deserialises the given type using `root` as the root of the data. 382 + 383 + Nullable: 384 + If there is no data for the nullable object, then it is nullafied regardless 385 + of what value it initialises to. 386 + ++/ 387 T deserialise(T)(ArchiveObject root) 388 { 389 T toReturn = T.init; 390 391 UsedObjectsT f; 392 doDeserialise!(T, T, T, Settings.None)(toReturn, root, f); 393 394 return toReturn; 395 } 396 397 /// ditto 398 T deserialise(T)(ArchiveObject root, out UsedObjectsT objectsUsed) 399 { 400 T toReturn = T.init; 401 402 doDeserialise!(T, T, T, Settings.None)(toReturn, root, objectsUsed); 403 404 return toReturn; 405 } 406 } 407 408 // ################# 409 // # SERIALISATION # 410 // ################# 411 private static final 412 { 413 void doSerialise(T, alias MainSymbol, alias Symbol, Settings InheritedSettings)(T data, ArchiveObject parent) 414 if(ArchiveValue.allowed!T && !hasUDA!(Symbol, Attribute)) 415 { 416 debug mixin(serialiseDebug("Value")); 417 418 static if(hasUDA!(Symbol, MainValue)) 419 parent.addValueAs!T(data); 420 else 421 { 422 auto obj = new ArchiveObject(getFieldName!Symbol); 423 obj.addValueAs!T(data); 424 parent.addChild(obj); 425 } 426 } 427 428 void doSerialise(T, alias MainSymbol, alias Symbol, Settings InheritedSettings)(T data, ArchiveObject parent) 429 if(ArchiveValue.allowed!T && hasUDA!(Symbol, Attribute)) 430 { 431 debug mixin(serialiseDebug("Attribute")); 432 433 parent.setAttributeAs!T(getFieldName!Symbol, data); 434 } 435 436 void doSerialise(T, alias MainSymbol, alias Symbol, Settings InheritedSettings)(T data, ArchiveObject parent) 437 if(isArray!T && !isSomeString!T) 438 { 439 debug mixin(serialiseDebug("Array")); 440 441 enum settings = getSettings!MainSymbol | getSettings!Symbol | InheritedSettings; 442 443 static if(ArchiveValue.allowed!(ElementType!T)) 444 { 445 static assert(!(settings & Settings.ArrayAsObject), "The settings 'ArrayAsObject' can only be applied to arrays of structs."); 446 447 static if(!hasUDA!(Symbol, MainValue)) 448 { 449 auto obj = new ArchiveObject(getFieldName!Symbol); 450 parent.addChild(obj); 451 } 452 else 453 auto obj = parent; 454 455 foreach(value; data) 456 obj.addValueAs!(ElementType!T)(value); 457 } 458 else static if(is(ElementType!T == struct)) 459 { 460 static assert(!hasUDA!(Symbol, MainValue), "Arrays of structs cannot be the main value, they can only be children."); 461 462 static if(settings & Settings.ArrayAsObject) 463 { 464 auto obj = new ArchiveObject(getFieldName!(Symbol, UseArrayBaseType.no)); 465 parent.addChild(obj); 466 } 467 else 468 auto obj = parent; 469 470 static if(hasUDA!(Symbol, InheritSettings)) 471 enum ToInherit = InheritedSettings | getSettings!Symbol; 472 else 473 enum ToInherit = InheritedSettings; 474 475 foreach(value; data) 476 doSerialise!(ElementType!T, MainSymbol, Symbol, cast(Settings)InheritedSettings)(value, obj); 477 } 478 else static assert(false, "Unsupported type: " ~ T.stringof); 479 } 480 481 void doSerialise(T, alias MainSymbol, alias Symbol, Settings InheritedSettings)(T data, ArchiveObject parent) 482 if(is(T == enum)) 483 { 484 import std.conv : to; 485 486 enum settings = getSettings!Symbol; 487 488 static if(settings & Settings.EnumAsValue) 489 { 490 OriginalType!T value = data; 491 alias ValueType = OriginalType!T; 492 } 493 else 494 { 495 T value = data; 496 alias ValueType = string; 497 } 498 499 static if(hasUDA!(Symbol, Attribute)) 500 parent.setAttributeAs!ValueType(getFieldName!Symbol, data.to!ValueType); 501 else 502 { 503 auto obj = new ArchiveObject(getFieldName!Symbol); 504 obj.addValueAs!ValueType(data.to!ValueType); 505 parent.addChild(obj); 506 } 507 } 508 509 void doSerialise(T, alias MainSymbol, alias Symbol, Settings InheritedSettings)(T data, ArchiveObject parent) 510 if(is(T == struct)) 511 { 512 debug mixin(serialiseDebug("Struct")); 513 514 auto obj = new ArchiveObject(getFieldName!Symbol); 515 parent.addChild(obj); 516 517 static assert(getSymbolsByUDA!(T, MainValue).length < 2, "There can only be one field marked with @MainValue"); 518 519 foreach(fieldName; FieldNameTuple!T) 520 { 521 static if(isPublic!(T, fieldName) 522 && !hasUDA!(mixin("T."~fieldName), Ignore)) 523 { 524 mixin("alias FieldAlias = T.%s;".format(fieldName)); 525 526 static if(isInstanceOf!(Nullable, typeof(FieldAlias))) 527 alias FieldType = Unqual!(ReturnType!(FieldAlias.get)); 528 else 529 alias FieldType = typeof(FieldAlias); 530 531 static if(hasUDA!(Symbol, InheritSettings)) 532 enum ToInherit = InheritedSettings | getSettings!Symbol; 533 else 534 enum ToInherit = InheritedSettings; 535 536 auto func = () => doSerialise!(FieldType, MainSymbol, FieldAlias, cast(Settings)ToInherit)(mixin("data."~fieldName), obj); 537 static if(isInstanceOf!(Nullable, typeof(FieldAlias))) 538 { 539 if(mixin("!data."~fieldName~".isNull")) 540 func(); 541 } 542 else 543 func(); 544 } 545 } 546 } 547 } 548 549 // ################### 550 // # DESERIALISATION # 551 // ################### 552 private static final 553 { 554 void doDeserialise(T, alias MainSymbol, alias Symbol, Settings InheritedSettings)(ref T data, ArchiveObject obj, ref UsedObjectsT objectsUsed) 555 if(ArchiveValue.allowed!T && !hasUDA!(Symbol, Attribute)) 556 { 557 static if(hasUDA!(Symbol, MainValue)) 558 data = obj.expectValueAs!T(0); 559 else 560 { 561 auto dataObj = obj.expectChild(getFieldName!Symbol); 562 data = dataObj.expectValueAs!T(0); 563 objectsUsed[obj] ~= dataObj; 564 } 565 } 566 567 void doDeserialise(T, alias MainSymbol, alias Symbol, Settings InheritedSettings)(ref T data, ArchiveObject obj, ref UsedObjectsT objectsUsed) 568 if(ArchiveValue.allowed!T && hasUDA!(Symbol, Attribute)) 569 { 570 data = obj.expectAttributeAs!T(getFieldName!Symbol); 571 } 572 573 void doDeserialise(T, alias MainSymbol, alias Symbol, Settings InheritedSettings)(ref T data, ArchiveObject parent, ref UsedObjectsT objectsUsed) 574 if(isArray!T && !isSomeString!T) 575 { 576 enum settings = getSettings!MainSymbol | getSettings!Symbol | InheritedSettings; 577 578 static if(ArchiveValue.allowed!(ElementType!T)) 579 { 580 static if(!hasUDA!(Symbol, MainValue)) 581 { 582 auto obj = parent.expectChild(getFieldName!Symbol); 583 objectsUsed[parent] ~= obj; 584 } 585 else 586 auto obj = parent; 587 588 static if(isDynamicArray!T) 589 { 590 foreach(value; obj.values) 591 data ~= value.coerce!(typeof(data[0])); 592 } 593 else static if(isStaticArray!T) 594 { 595 enforce(obj.values.length == data.length, "Expected %s values, got %s.".format(data.length, obj.values.length)); 596 597 foreach(i, value; obj.values) 598 data[i] = value.coerce!(typeof(data[0])); 599 } 600 else static assert(false, "NSAUasiogoo"); 601 } 602 else static if(is(ElementType!T == struct)) 603 { 604 static if(settings & Settings.ArrayAsObject) 605 { 606 auto obj = parent.expectChild(getFieldName!(Symbol, UseArrayBaseType.no)); 607 objectsUsed[parent] ~= obj; 608 } 609 else 610 auto obj = parent; 611 612 static if(hasUDA!(Symbol, InheritSettings)) 613 enum ToInherit = InheritedSettings | getSettings!Symbol; 614 else 615 enum ToInherit = InheritedSettings; 616 617 foreach(child; obj.children.filter!(c => c.name == getFieldName!Symbol)) 618 { 619 objectsUsed[obj] ~= child; 620 data ~= typeof(data[0]).init; 621 doDeserialise!(ElementType!T, MainSymbol, Symbol, cast(Settings)InheritedSettings)(data[$-1], child, objectsUsed); 622 } 623 } 624 else static assert(false, "Unsupported type: " ~ T.stringof); 625 } 626 627 void doDeserialise(T, alias MainSymbol, alias Symbol, Settings InheritedSettings)(ref T data, ArchiveObject obj, ref UsedObjectsT objectsUsed) 628 if(is(T == enum)) 629 { 630 import std.conv : to; 631 632 static if(getSettings!Symbol & Settings.EnumAsValue) 633 alias ValueType = OriginalType!T; 634 else 635 alias ValueType = string; 636 637 static if(hasUDA!(Symbol, Attribute)) 638 data = obj.expectAttributeAs!ValueType(getFieldName!Symbol).to!T; 639 else 640 { 641 auto dataObj = obj.expectChild(getFieldName!Symbol); 642 data = dataObj.getValueAs!ValueType(0).to!T; 643 objectsUsed[obj] ~= dataObj; 644 } 645 } 646 647 void doDeserialise(T, alias MainSymbol, alias Symbol, Settings InheritedSettings)(ref T data, ArchiveObject obj, ref UsedObjectsT objectsUsed) 648 if(is(T == struct)) 649 { 650 ArchiveObject structObj = (obj.name == getFieldName!Symbol) ? obj 651 : () { auto o = obj.expectChild(getFieldName!Symbol); 652 objectsUsed[obj] ~= o; 653 return o; 654 }(); 655 656 foreach(fieldName; FieldNameTuple!T) 657 { 658 static if(isPublic!(T, fieldName) 659 && !hasUDA!(mixin("T."~fieldName), Ignore)) 660 { 661 mixin("alias FieldAlias = T.%s;".format(fieldName)); 662 663 static if(isInstanceOf!(Nullable, typeof(FieldAlias))) 664 { 665 alias FieldType = Unqual!(ReturnType!(FieldAlias.get)); 666 enum FieldRef = "tempValue"; 667 } 668 else 669 { 670 alias FieldType = typeof(FieldAlias); 671 enum FieldRef = "data."~fieldName; 672 } 673 674 static if(hasUDA!(Symbol, InheritSettings)) 675 enum ToInherit = InheritedSettings | getSettings!Symbol; 676 else 677 enum ToInherit = InheritedSettings; 678 679 try 680 { 681 // We can't pass the nullable itself by ref, so we have to store it in a temp value first 682 static if(isInstanceOf!(Nullable, typeof(FieldAlias))) 683 FieldType tempValue; 684 685 doDeserialise!(FieldType, MainSymbol, FieldAlias, cast(Settings)ToInherit)(mixin(FieldRef), structObj, objectsUsed); 686 687 static if(isInstanceOf!(Nullable, typeof(FieldAlias))) 688 mixin("data."~fieldName~" = tempValue;"); 689 } 690 catch(Exception ex) 691 { 692 static if(isInstanceOf!(Nullable, typeof(FieldAlias))) 693 mixin("data."~fieldName~".nullify();"); 694 else 695 throw ex; 696 } 697 } 698 } 699 } 700 } 701 } 702 703 // Nullable test 704 version(Jasterialise_Unittests) 705 unittest 706 { 707 import fluent.asserts; 708 import std.algorithm : canFind; 709 import jaster.serialise.sdlang; 710 711 struct A 712 { 713 Nullable!int a; 714 int b; 715 Nullable!int c; 716 } 717 718 A a; 719 a.a.nullify; 720 a.b = 200; 721 a.c = 400; 722 723 auto archive = new ArchiveSDL(); 724 Serialiser.serialise(a, archive.root); 725 726 archive.root["A"].getChild("a").should.beNull; 727 archive.root["A", "b"].expectValueAs!int(0).should.equal(200); 728 archive.root["A", "c"].expectValueAs!int(0).should.equal(400); 729 730 UsedObjectsT used; 731 A b = Serialiser.deserialise!A(archive.root, used); 732 assert(a == b); // Fluent asserts doesn't like nullable 733 734 assert(used[archive.root].canFind(archive.root["A"])); 735 assert(used[archive.root["A"]].canFind(archive.root["A", "b"])); 736 assert(used[archive.root["A"]].canFind(archive.root["A", "c"])); 737 } 738 739 // Enum test 740 version(Jasterialise_Unittests) 741 unittest 742 { 743 import jaster.serialise.sdlang; 744 import fluent.asserts; 745 746 enum E 747 { 748 A, 749 B = 69, 750 C 751 } 752 753 struct A 754 { 755 @Attribute 756 E a; 757 E b; 758 759 @Setting(Serialiser.Settings.EnumAsValue) 760 E c; 761 } 762 763 A a; 764 a.a = E.C; 765 a.b = E.A; 766 a.c = E.B; 767 768 auto archive = new ArchiveSDL(); 769 Serialiser.serialise(a, archive.root); 770 771 archive.root["A"].expectAttributeAs!string("a").should.equal("C"); 772 archive.root["A", "b"].expectValueAs!string(0).should.equal("A"); 773 archive.root["A", "c"].expectValueAs!int(0).should.equal(69); 774 775 A b = Serialiser.deserialise!A(archive.root); 776 a.should.equal(b); 777 } 778 779 /++ 780 + Enforces that all objects used as keys have had all of their children used (based on the values in `used). 781 + 782 + Params: 783 + used = A mapping of which children have been used for what objects. 784 + ++/ 785 void enforceAllChildrenUsed(UsedObjectsT used) 786 { 787 import std.format : format; 788 import std.exception : enforce; 789 790 foreach(parent, usedChildren; used) 791 { 792 foreach(child; parent.children) 793 { 794 bool wasUsed = false; 795 foreach(usedChild; usedChildren) 796 { 797 if(child == usedChild) 798 { 799 wasUsed = true; 800 break; 801 } 802 } 803 804 enforce(wasUsed, "Parent object '%s' has unused child object '%s'.".format(parent.name, child.name)); 805 } 806 } 807 } 808 809 // Best I can do at least... 810 private enum isPublic(T, string field) = is(typeof({T t = T.init; auto b = mixin("t."~field);})); 811 812 private string serialiseDebug(string name) 813 { 814 version(SERIALISER_DEBUG_OUTPUT) 815 { 816 return format(`writefln("Func:%%-15s | T:%%-15s | MainSymbol:%%-15s | Symbol:%%-15s | data:%%-15s | parent:%%-15s", 817 "%s", T.stringof, MainSymbol.stringof, Symbol.stringof, data, parent.name);`, name); 818 } 819 else 820 return ""; 821 }