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