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 }