1 module mutils.serializer.binary; 2 3 import std.experimental.allocator; 4 import std.experimental.allocator.mallocator; 5 import std.meta; 6 import std.traits; 7 8 public import mutils.serializer.common; 9 import mutils.container.vector; 10 11 // COS==ContainerOrSlice 12 13 /** 14 * Serializer to save data in binary format (little endian) 15 * If serialized data have to be allocated it is not saved/loaded unless it has "malloc" UDA (@("malloc")) 16 */ 17 class BinarySerializer { 18 alias SliceElementType = ubyte; 19 __gshared static BinarySerializer instance = new BinarySerializer; 20 21 int beginObject(Load load, COS)(ref COS con) { 22 return 0; // Just to satisfy interface 23 } 24 25 void endObject(Load load, COS)(ref COS con, int begin) { 26 } 27 28 void serializeWithName(Load load, string name, bool useMalloc = false, T, COS)(ref T var, 29 ref COS con) { 30 serialize!(load, useMalloc)(var, con); 31 } 32 /** 33 * Function loads and saves data depending on compile time variable load 34 * If useMalloc is true pointers, arrays, classes will be saved and loaded using Mallocator (there is exception, if vairable is not null it won't be allocated) 35 * T is the serialized variable 36 * COS is ubyte[] when load==Load.yes 37 * COS container supplied by user in which data is stored when load==Load.no(save) 38 * If load==load.skip data is not loaded but slice is pushed fruther 39 */ 40 void serialize(Load load, bool useMalloc = false, T, COS)(ref T var, ref COS con) { 41 commonSerialize!(load, useMalloc)(this, var, con); 42 } 43 //support for rvalues during load 44 void serialize(Load load, bool useMalloc = false, T, COS)(ref T var, COS con) { 45 static assert(load == Load.yes); 46 serialize!(load, useMalloc)(var, con); 47 } 48 49 package: 50 51 void serializeImpl(Load load, bool useMalloc = false, T, COS)(ref T var, ref COS con) { 52 static assert((load == Load.skip 53 && is(Unqual!(ForeachType!COS) == ubyte)) || (load == Load.yes 54 && is(Unqual!(ForeachType!COS) == ubyte)) || (load == Load.no 55 && !isDynamicArray!COS)); 56 static assert(!is(T == union), "Type can not be union"); 57 static assert((!is(T == struct) && !is(T == class)) || !isNested!T, 58 "Type can not be nested"); 59 60 commonSerialize!(load, useMalloc)(this, var, con); 61 } 62 //----------------------------------------- 63 //--- Basic serializing methods 64 //----------------------------------------- 65 void serializeBasicVar(Load load, T, COS)(ref T var, ref COS con) { 66 static assert(isBasicType!T); 67 static if (load == Load.yes || load == Load.skip) { 68 T* tmp = cast(T*) con.ptr; 69 static if (load != Load.skip) { 70 //On ARM you cannot load floating point value from unaligned memory 71 static if (isFloatingPoint!T) { 72 ubyte[T.sizeof] t; 73 t[0 .. $] = (cast(ubyte*) tmp)[0 .. T.sizeof]; 74 var = *cast(T*) t; 75 } else { 76 var = tmp[0]; 77 } 78 } 79 con = con[T.sizeof .. $]; 80 } else { 81 ubyte* tmp = cast(ubyte*)&var; 82 con ~= tmp[0 .. T.sizeof]; 83 } 84 } 85 86 void serializeStruct(Load load, T, COS)(ref T var, ref COS con) { 87 static assert(is(T == struct)); 88 serializeClassOrStruct!(load)(var, con); 89 } 90 91 void serializeClass(Load load, T, COS)(ref T var, ref COS con) { 92 static assert(is(T == class)); 93 bool exists = var !is null; 94 serialize!(loadOrSkip!load)(exists, con); 95 if (!exists) { 96 return; 97 } 98 static if (load == Load.yes) { 99 if (var is null) 100 var = Mallocator.instance.make!(T); 101 } else static if (load == Load.skip) { 102 __gshared static T helperObj = new T; 103 T beforeVar = var; 104 if (var is null) 105 var = helperObj; 106 } 107 108 serializeClassOrStruct!(load)(var, con); 109 110 static if (load == Load.skip) { 111 var = beforeVar; 112 } 113 114 } 115 116 void serializeStaticArray(Load load, T, COS)(ref T var, ref COS con) { 117 static assert(isStaticArray!T); 118 foreach (i, ref a; var) { 119 serialize!(load)(a, con); 120 } 121 122 } 123 124 void serializeDynamicArray(Load load, T, COS)(ref T var, ref COS con) { 125 static assert(isDynamicArray!T); 126 alias ElementType = Unqual!(ForeachType!(T)); 127 uint dataLength = cast(uint)(var.length); 128 serialize!(loadOrSkip!load)(dataLength, con); 129 static if (load == Load.yes) { 130 ElementType[] arrData = Mallocator.instance.makeArray!(ElementType)(dataLength); 131 foreach (i, ref d; arrData) { 132 serialize!(load)(d, con); 133 } 134 var = cast(T) arrData; 135 } else static if (load == Load.skip) { 136 T tmp; 137 foreach (i; 0 .. dataLength) { 138 serialize!(load)(tmp, con); 139 } 140 } else { 141 foreach (i, ref d; var) { 142 serialize!(load)(d, con); 143 } 144 } 145 146 } 147 148 void serializeString(Load load, T, COS)(ref T var, ref COS con) { 149 serializeCustomVector!(load)(var, con); 150 } 151 152 void serializeCustomVector(Load load, T, COS)(ref T var, ref COS con) { 153 alias ElementType = Unqual!(ForeachType!(T)); 154 uint dataLength = cast(uint)(var.length); 155 serialize!(loadOrSkip!load)(dataLength, con); 156 static if (load == Load.yes) { 157 static if (hasMember!(T, "initialize")) { 158 if (load != Load.skip) 159 var.initialize(); 160 } 161 static if (hasMember!(T, "reserve")) { 162 if (load != Load.skip) 163 var.reserve(dataLength); 164 } 165 static if (isBasicType!ElementType) { 166 ElementType[] arr = (cast(ElementType*) con)[0 .. dataLength]; 167 if (load != Load.skip) 168 var = arr; 169 con = con[dataLength * ElementType.sizeof .. $]; 170 } else { 171 foreach (i; 0 .. dataLength) { 172 ElementType element; 173 serialize!(load)(element, con); 174 if (load != Load.skip) 175 var ~= element; 176 } 177 } 178 179 } else { 180 foreach (ref d; var) { 181 serialize!(load)(d, con); 182 } 183 } 184 } 185 186 void serializeCustomMap(Load load, T, COS)(ref T var, ref COS con) { 187 static assert(isCustomMap!T); 188 189 uint dataLength = cast(uint)(var.length); 190 serialize!(loadOrSkip!load)(dataLength, con); 191 192 static if (load == Load.yes) { 193 static if (hasMember!(T, "initialize")) { 194 if (load != Load.skip) 195 var.initialize(); 196 } 197 static if (hasMember!(T, "reserve")) { 198 if (load != Load.skip) 199 var.reserve(dataLength); 200 } 201 foreach (i; 0 .. dataLength) { 202 T.Key key; 203 T.Value value; 204 serialize!(load)(key, con); 205 serialize!(load)(value, con); 206 if (load != Load.skip) 207 var.add(key, value); 208 } 209 } else { 210 foreach (ref key, ref value; &var.byKeyValue) { 211 serialize!(load)(key, con); 212 serialize!(load)(value, con); 213 } 214 } 215 } 216 217 void serializePointer(Load load, bool useMalloc, T, COS)(ref T var, ref COS con) { 218 commonSerializePointer!(load, useMalloc)(this, var, con); 219 } 220 221 //----------------------------------------- 222 //--- Helper methods 223 //----------------------------------------- 224 void serializeClassOrStruct(Load load, T, COS)(ref T var, ref COS con) { 225 static assert(is(T == struct) || is(T == class)); 226 foreach (i, ref a; var.tupleof) { 227 alias TP = AliasSeq!(__traits(getAttributes, var.tupleof[i])); 228 enum bool doSerialize = !hasNoserializeUda!(TP); 229 enum bool useMalloc = hasMallocUda!(TP); 230 static if (doSerialize) { 231 serialize!(load, useMalloc)(a, con); 232 } 233 } 234 235 } 236 237 } 238 239 //----------------------------------------- 240 //--- Tests 241 //----------------------------------------- 242 243 // Helper to avoid GC 244 private T[n] s(T, size_t n)(auto ref T[n] array) pure nothrow @nogc @safe { 245 return array; 246 } 247 248 // test basic types + endianness 249 unittest { 250 int a = 1; 251 ubyte b = 3; 252 253 Vector!ubyte container; 254 //ubyte[] container; 255 ubyte[] dataSlice; 256 257 //save 258 BinarySerializer serializer = BinarySerializer.instance; 259 serializer.serialize!(Load.no)(a, container); 260 serializer.serialize!(Load.no)(b, container); 261 assert(container[0] == 1); //little endian 262 assert(container[1] == 0); 263 assert(container[2] == 0); 264 assert(container[3] == 0); 265 assert(container[4] == 3); 266 assert(container.length == 5); 267 268 //reset var 269 a = 0; 270 b = 0; 271 272 //load 273 dataSlice = container[]; 274 serializer.serialize!(Load.yes)(a, dataSlice); 275 serializer.serialize!(Load.yes)(b, dataSlice); 276 assert(a == 1); 277 assert(b == 3); 278 assert(dataSlice.length == 0); 279 } 280 281 // test structs 282 unittest { 283 static struct TestStruct { 284 int a; 285 ulong b; 286 char c; 287 } 288 289 TestStruct test = TestStruct(1, 2, 'c'); 290 291 Vector!ubyte container; 292 293 //save 294 BinarySerializer serializer = BinarySerializer.instance; 295 serializer.serialize!(Load.no)(test, container); 296 assert(container[0 .. 4] == [1, 0, 0, 0].s); 297 assert(container[4 .. 12] == [2, 0, 0, 0, 0, 0, 0, 0].s); 298 assert(container[12] == 'c'); 299 assert(container.length == 13); 300 301 //reset var 302 test = TestStruct.init; 303 304 //load 305 serializer.serialize!(Load.yes)(test, container[]); 306 assert(test.a == 1); 307 assert(test.b == 2); 308 assert(test.c == 'c'); 309 } 310 311 // test static array 312 unittest { 313 static struct SomeStruct { 314 ulong a; 315 ubyte b; 316 } 317 318 static struct TestStruct { 319 SomeStruct[3] a; 320 } 321 322 TestStruct test = TestStruct([SomeStruct(1, 1), SomeStruct(2, 2), SomeStruct(3, 3)]); 323 324 Vector!ubyte container; 325 326 //save 327 BinarySerializer serializer = BinarySerializer.instance; 328 serializer.serialize!(Load.no)(test, container); 329 assert(container.length == 3 * 9); 330 331 //reset var 332 test = TestStruct.init; 333 334 //load 335 serializer.serialize!(Load.yes)(test, container[]); 336 assert(test.a == [SomeStruct(1, 1), SomeStruct(2, 2), SomeStruct(3, 3)]); 337 } 338 339 // test dynamic Arrays 340 unittest { 341 static struct TestStruct { 342 string str; 343 @("malloc") string strMalloc; 344 @("malloc") int[] intMalloc; 345 } 346 347 static TestStruct test = TestStruct("xx", "ab", [1, 2, 3].s); 348 349 Vector!ubyte container; 350 351 //save 352 BinarySerializer serializer = BinarySerializer.instance; 353 serializer.serialize!(Load.no)(test, container); 354 assert(container.length == 0 + (4 + 2) + (4 + 3 * 4)); 355 356 //reset var 357 test = TestStruct.init; 358 //load 359 serializer.serialize!(Load.yes)(test, container[]); 360 assert(test.str is null); 361 assert(test.strMalloc == "ab"); 362 assert(test.intMalloc == [1, 2, 3].s); 363 Mallocator.instance.dispose(cast(char[]) test.strMalloc); 364 Mallocator.instance.dispose(test.intMalloc); 365 } 366 367 // test class 368 unittest { 369 static class TestClass { 370 int a; 371 } 372 373 static struct TestStruct { 374 TestClass ttt; 375 @("malloc") TestClass testClass; 376 @("malloc") TestClass testClassNull; 377 } 378 379 __gshared static TestClass testClass = new TestClass; //nogc 380 TestStruct test = TestStruct(testClass, testClass, null); 381 test.testClass.a = 4; 382 383 Vector!ubyte container; 384 385 //save 386 BinarySerializer serializer = BinarySerializer.instance; 387 serializer.serialize!(Load.no)(test, container); 388 assert(container.length == 0 + (1 + 4) + 1); 389 390 //reset var 391 test = TestStruct.init; 392 //load 393 serializer.serialize!(Load.yes)(test, container[]); 394 assert(test.ttt is null); 395 assert(test.testClass.a == 4); 396 assert(test.testClassNull is null); 397 Mallocator.instance.dispose(test.testClass); 398 } 399 400 // test pointer 401 unittest { 402 static struct TestStructB { 403 int a; 404 } 405 406 static struct TestStruct { 407 @("malloc") TestStructB* pointer; 408 @("malloc") TestStructB* pointerNull; 409 @("malloc") int[4]* pointerArr; 410 } 411 412 int[4]* arr = Mallocator.instance.make!(int[4]); 413 TestStructB testTmp; 414 TestStruct test = TestStruct(&testTmp, null, arr); 415 test.pointer.a = 10; 416 417 Vector!ubyte container; 418 419 //save 420 BinarySerializer serializer = BinarySerializer.instance; 421 serializer.serialize!(Load.no)(test, container); 422 assert(container.length == (1 + 4) + 1 + (1 + 16)); 423 424 //reset var 425 Mallocator.instance.dispose(arr); 426 test = TestStruct.init; 427 //load 428 serializer.serialize!(Load.yes)(test, container[]); 429 assert(test.pointer.a == 10); 430 assert(test.pointerNull is null); 431 Mallocator.instance.dispose(test.pointer); 432 Mallocator.instance.dispose(test.pointerArr); 433 } 434 435 // test custom vector 436 unittest { 437 static struct TestStruct { 438 int a; 439 void customSerialize(Load load, Serializer, COS)(Serializer serializer, ref COS con) { 440 int tmp = a / 3; 441 serializer.serialize!(load)(tmp, con); 442 serializer.serialize!(load)(tmp, con); 443 serializer.serialize!(load)(tmp, con); 444 if (load == Load.yes) { 445 a = tmp * 3; 446 } 447 } 448 } 449 450 TestStruct test; 451 test.a = 3; 452 Vector!ubyte container; 453 454 //save 455 BinarySerializer serializer = BinarySerializer.instance; 456 serializer.serialize!(Load.no)(test, container); 457 assert(container.length == 3 * 4); 458 459 //reset var 460 test = TestStruct.init; 461 //load 462 serializer.serialize!(Load.yes)(test, container[]); 463 assert(test.a == 3); 464 } 465 466 // test custom map 467 unittest { 468 import mutils.container.hash_map; 469 470 static struct TestStruct { 471 int a; 472 int b; 473 } 474 475 HashMap!(int, TestStruct) map; 476 map.add(1, TestStruct(1, 11)); 477 map.add(7, TestStruct(7, 77)); 478 479 Vector!ubyte container; 480 481 //save 482 BinarySerializer serializer = BinarySerializer.instance; 483 serializer.serialize!(Load.no)(map, container); 484 assert(container.length == 4 + 2 * 4 + 2 * 8); // length + 2*key + 2* value 485 486 //reset var 487 map = map.init; 488 //load 489 serializer.serialize!(Load.yes)(map, container[]); 490 assert(map.get(1) == TestStruct(1, 11)); 491 assert(map.get(7) == TestStruct(7, 77)); 492 } 493 494 // test beforeSerialize and afterSerialize 495 unittest { 496 497 static struct TestStruct { 498 ubyte a; 499 void beforeSerialize(Load load, Serializer, COS)(Serializer serializer, ref COS con) { 500 ubyte tmp = 10; 501 serializer.serialize!(load)(tmp, con); 502 } 503 504 void afterSerialize(Load load, Serializer, COS)(Serializer serializer, ref COS con) { 505 ubyte tmp = 7; 506 serializer.serialize!(load)(tmp, con); 507 } 508 } 509 510 TestStruct test; 511 test.a = 3; 512 Vector!ubyte container; 513 514 //save 515 BinarySerializer serializer = BinarySerializer.instance; 516 serializer.serialize!(Load.no)(test, container); 517 assert(container[] == [10, 3, 7].s); 518 519 //reset var 520 test = TestStruct.init; 521 //load 522 serializer.serialize!(Load.yes)(test, container[]); 523 assert(test.a == 3); 524 } 525 526 // test noserialzie 527 unittest { 528 529 static struct TestStruct { 530 @("noserialize") int a; 531 } 532 533 static class TestClass { 534 @("noserialize") int a; 535 } 536 537 __gshared static TestClass testClass = new TestClass; 538 TestStruct testStruct; 539 Vector!ubyte container; 540 541 //save 542 BinarySerializer serializer = BinarySerializer.instance; 543 serializer.serialize!(Load.no)(testStruct, container); 544 serializer.serialize!(Load.no, true)(testClass, container); 545 assert(container.length == 1); //1 because there is check if class exist 546 547 //reset var 548 testStruct = TestStruct.init; 549 testClass = TestClass.init; 550 //load 551 serializer.serialize!(Load.yes)(testStruct, container[]); 552 serializer.serialize!(Load.yes, true)(testClass, container[]); 553 assert(testStruct.a == 0); 554 assert(testClass.a == 0); 555 } 556 557 // test skip 558 unittest { 559 static class TestClass { 560 int a; 561 } 562 563 static struct TestStructB { 564 int a; 565 int b; 566 } 567 568 static struct TestStruct { 569 @("malloc") TestClass a; 570 @("malloc") TestStructB* b; 571 } 572 573 TestClass testClass = Mallocator.instance.make!(TestClass); 574 TestStructB testTmp; 575 TestStruct test = TestStruct(testClass, &testTmp); 576 testClass.a = 1; 577 testTmp.a = 11; 578 testTmp.b = 22; 579 580 Vector!ubyte container; 581 582 //save 583 BinarySerializer serializer = BinarySerializer.instance; 584 serializer.serialize!(Load.no)(test, container); 585 assert(container.length == (1 + 4) + (1 + 8)); 586 587 //reset var 588 Mallocator.instance.dispose(testClass); 589 test = TestStruct.init; 590 test.b = cast(TestStructB*) 123; 591 //skip 592 ubyte[] slice = container[]; 593 serializer.serialize!(Load.skip)(test, slice); 594 assert(test.a is null); 595 assert(test.b == cast(TestStructB*) 123); 596 assert(slice.length == 0); 597 598 //reset var 599 test = TestStruct.init; 600 //load 601 serializer.serialize!(Load.yes)(test, container[]); 602 assert(test.a.a == 1); 603 assert(test.b.a == 11); 604 assert(test.b.b == 22); 605 Mallocator.instance.dispose(test.a); 606 Mallocator.instance.dispose(test.b); 607 }