1 /// Serialize a D struct, class or array to a message buffer.
2 module serialize;
3 
4 import std.conv, std.traits, std.outbuffer, std.string;
5 
6 import leb128, common, oneof;
7 
8 /// Serialize top-level, either array, associative array or struct.
9 /// For aggregate types like structs, serialize each member.
10 auto toMsgBuffer(MsgBufferType E = MsgBufferType.Var, T)(const ref T val) {
11 	return toMsgBuffer!(E, T)(val, new OutBuffer);
12 }	// toMsgBuffer()
13 
14 // Serialise a number as integer value.
15 // pragma(inline, true)
16 void serializeInt(MsgBufferType E = MsgBufferType.Var, T)(const T val, OutBuffer buf) {
17 	static if (E == MsgBufferType.Flat) {
18 		buf.alignSize(T.alignof);
19 		buf.write(to!T(val));
20 	} else {
21 		toLEB128(buf, to!T(val));
22 	}
23 }	// serializeInt()
24 
25 // Serialize a single value, either a string, a floating point value or a scalar, e.g.
26 // int, bool, long, etc. If it is neither, then forward to top-level serialize.
27 auto serializeValue(MsgBufferType E = MsgBufferType.Var, T, C = int)(const ref T val, OutBuffer buf, void delegate(const ref C val, OutBuffer buf) cb = null ) {
28 	static if (isSomeString!(T)) {
29 		serializeInt!(E)(to!uint(val.length), buf);
30 		buf.write(val);
31 	} else static if (isBoolean!(T)) {
32 		static assert(ubyte.sizeof == bool.sizeof);
33 		buf.write(to!ubyte(val));
34 	} else static if (is(T == enum)) {
35 		static if (__traits(compiles, to!int(EnumMembers!T[0]))) {
36 			static if (E == MsgBufferType.Flat) {
37 				buf.alignSize(T.alignof);
38 				buf.write(to!int(val));
39 			} else {
40 				toLEB128(buf, to!int(val));
41 			}
42 		} else {
43 			static assert(0, "only integral enums supported for enum " ~ T.stringof);
44 		}
45 	} else static if (isScalarType!(T) || isFloatingPoint!(T)) {
46 		static if (!isFloatingPoint!(T) && T.sizeof > 1 && E == MsgBufferType.Var) {
47 			toLEB128!T(buf, val);
48 		} else {
49 			buf.alignSize(T.alignof);
50 			buf.write(val);
51 		}
52 	} else static if (isArray!(T)) {
53 		serializeInt!(E)(to!uint(val.length), buf);
54 		static if (isScalarType!(typeof(val[0])) || isFloatingPoint!(typeof(val[0]))) {
55 			// Performance optimisation for scalar/floating point arrays
56 			static if (!isFloatingPoint!(typeof(val[0])) && typeof(val[0]).sizeof > 1 && E == MsgBufferType.Var) {
57 				toLEB128!T(buf, val);
58 			} else {
59 				buf.reserve(val.length * typeof(val[0]).sizeof);
60 				buf.alignSize(T.alignof);
61 				buf.write(cast(ubyte[])val);
62 			}
63 		} else {
64 			static foreach (k; val)
65 				serializeValue!(E, typeof(k))(k, buf);
66 		}
67 	} else static if (isAssociativeArray!(T)) {
68 		// TODO: the performance for AAs is underwhelming, maybe there is a way to make "forbidden"
69 		// optimisations, given the implementation details of AAs here:
70 		// https://github.com/dlang/dmd/blob/7f4620f4e1fe29641b28648c5c4c93d9fafdf90f/src/dmd/backend/aarray.d (dmd)
71 		// or
72 		// https://github.com/ldc-developers/ldc/blob/196a9eb9ad6374c0837588588f36715a3cf8e302/dmd/root/aav.d (ldc2)
73 		// However, this might not work for all compilers, so maybe there is another way I have not seen yet...
74 		serializeInt!(E)(to!uint(val.length), buf);
75 		foreach (k; val.keys) {
76 			serializeValue!(E, typeof(k))(k, buf);
77 			serializeValue!(E, typeof(val[k]))(val[k], buf);
78 		}
79 	} else static if (is(T == struct) && T.stringof.startsWith("Oneof")) {
80 		bool hasValue;
81 		static foreach (idx, s; T.tupleof) {{
82 			alias OneofMemberType = typeof(s);
83 			enum OneofMemberName = __traits(identifier, s);
84 
85 			static if (OneofMemberName.startsWith("___data_field")) {
86 
87 				if (has!(OneofMemberType)(val)) {
88 					immutable oneOfValue = get!(OneofMemberType)(val);
89 					serializeInt!(E)(to!ubyte(idx+1), buf);
90 					serializeValue!(E, OneofMemberType)(oneOfValue, buf);
91 					hasValue = true;
92 				}
93 			}
94 		}}
95 		if (!hasValue) {
96 			// Oneof was empty.
97 			serializeInt!(E)(to!ubyte(0), buf);
98 		}
99 	} else {
100 		toMsgBuffer!(E, T)(val, buf);
101 	}
102 }	// serializeValue()
103 
104 auto toMsgBuffer(MsgBufferType E = MsgBufferType.Var, T)(const ref T val, OutBuffer buf) {
105 	static if (isAggregateType!(T) && !is(T == class)) {
106 		static foreach (v; T.tupleof)
107 			serializeValue!(E, typeof(v))(mixin("val." ~ __traits(identifier, v)), buf);
108 	} else static if (isArray!(T) || isAssociativeArray!(T)) {
109 		serializeValue!(E, T)(val, buf);
110 	} else {
111 		static assert(0, "expected struct or array, not " ~ T.stringof);
112 	}
113 	return buf;
114 }	// toMsgBuffer()