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 }