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