1 module mecca.lib.serialization; 2 3 // Licensed under the Boost license. Full copyright information in the AUTHORS file 4 5 import std.traits; 6 import std.meta; 7 8 alias LengthType = uint; 9 10 template ArrayElement(T) { 11 static if (isStaticArray!T && is(T == E[N], E, size_t N)) { 12 alias ArrayElement = E; 13 } 14 else static if (isDynamicArray!T && is(T == E[], E)) { 15 alias ArrayElement = E; 16 } 17 else { 18 static assert (false, T.stringof ~ " is not an array"); 19 } 20 } 21 22 template isBlittable(U) { 23 alias T = U; //Unqual!U; 24 static if (__traits(hasMember, T, "customDump")) { 25 enum isBlittable = false; 26 } 27 else static if (is(T == struct) || is(T == union)) { 28 static if (isNested!T) { 29 enum isBlittable = false; 30 } 31 else { 32 enum isBlittable = allSatisfy!(.isBlittable, typeof(T.tupleof)); 33 } 34 } 35 else static if (isStaticArray!T) { 36 alias E = ArrayElement!T; 37 enum isBlittable = is(E == void) || isBlittable!E; 38 } 39 else static if (staticIndexOf!(T, bool, char, wchar, dchar, byte, ubyte, short, ushort, int, uint, long, ulong, float, double, real) >= 0) { 40 enum isBlittable = true; 41 } 42 else { 43 enum isBlittable = false; 44 } 45 } 46 47 template isTopLevelBlittable(T) { 48 static if (isBlittable!T) { 49 enum isTopLevelBlittable = true; 50 } 51 else static if (isDynamicArray!T) { 52 enum isTopLevelBlittable = isBlittable!(ArrayElement!T); 53 } 54 else { 55 enum isTopLevelBlittable = false; 56 } 57 } 58 59 size_t calcSizeOf(T)(auto ref const T obj) { 60 static if (__traits(hasMember, T, "customDump")) { 61 obj.customCalcSize(); 62 } 63 else static if (isArray!T) { 64 size_t s; 65 static if (isDynamicArray!T) { 66 assert (obj.length < LengthType.max); 67 s = LengthType.sizeof; 68 } 69 static if (isBlittable!(ArrayElement!T)) { 70 s += ArrayElement!T.sizeof * obj.length; 71 } 72 else { 73 foreach(const ref item; obj) { 74 s += calcSizeOf(item); 75 } 76 } 77 return s; 78 } 79 else static if (isAssociativeArray!T) { 80 size_t s = LengthType.sizeof; 81 foreach(k, ref v; obj) { 82 s += calcSizeOf(k) + calcSizeOf(v); 83 } 84 return s; 85 } 86 else static if (isBlittable!T) { 87 return T.sizeof; 88 } 89 else static if (is(T == struct)) { 90 // we know the struct is not blittable 91 size_t s; 92 foreach(i, _; typeof(T.tupleof)) { 93 s += calcSizeOf(obj.tupleof[i]); 94 } 95 return s; 96 } 97 else { 98 static assert (false, "Cannot dump " ~ T.stringof); 99 } 100 } 101 102 void blitWrite(S, T)(ref S stream, ref const T data) { 103 static assert (isTopLevelBlittable!T); 104 static if (isDynamicArray!T) { 105 static if (is(S == ubyte[])) { 106 stream[0 .. data.length] = data; 107 stream = stream[data.length .. $]; 108 } 109 else { 110 stream.write(data); 111 } 112 } 113 else { 114 static if (is(S == ubyte[])) { 115 *(cast(T*)stream.ptr) = data; 116 stream = stream[T.sizeof .. $]; 117 } 118 else { 119 stream.write((cast(const(ubyte)*)&data)[0 .. T.sizeof]); 120 } 121 } 122 } 123 124 void blitRead(S, T)(ref S stream, auto ref T data) { 125 static assert (isTopLevelBlittable!T); 126 static if (isDynamicArray!T) { 127 static if (is(S == ubyte[])) { 128 data[] = stream[0 .. data.length]; 129 stream = stream[data.length .. $]; 130 } 131 else { 132 stream.read(data); 133 } 134 } 135 else { 136 static if (is(S == ubyte[])) { 137 data = *(cast(T*)stream.ptr); 138 stream = stream[T.sizeof .. $]; 139 } 140 else { 141 stream.write((cast(const(ubyte)*)&data)[0 .. T.sizeof]); 142 } 143 } 144 } 145 146 void dump(S, T)(ref S stream, auto ref const T obj) { 147 static if (__traits(hasMember, T, "customDump")) { 148 obj.customDump(stream); 149 } 150 else static if (isArray!T) { 151 static if (isDynamicArray!T) { 152 assert (obj.length < LengthType.max); 153 dump(stream, cast(LengthType)obj.length); 154 } 155 static if (isBlittable!(ArrayElement!T)) { 156 blitWrite(stream, cast(ubyte[])obj); 157 } 158 else { 159 foreach(const ref item; obj) { 160 dump(stream, item); 161 } 162 } 163 } 164 else static if (isAssociativeArray!T) { 165 assert (obj.length < LengthType.max); 166 dump(stream, cast(LengthType)obj.length); 167 foreach(k, ref v; obj) { 168 dump(stream, k); 169 dump(stream, v); 170 } 171 } 172 else static if (isBlittable!T) { 173 blitWrite(stream, obj); 174 } 175 else static if (is(T == struct)) { 176 // we know the struct is not blittable 177 foreach(i, _; typeof(T.tupleof)) { 178 dump(stream, obj.tupleof[i]); 179 } 180 } 181 else { 182 static assert (false, "Cannot dump " ~ T.stringof); 183 } 184 } 185 186 ubyte[] dump(T)(auto ref const T obj) { 187 ubyte[] buf = new ubyte[calcSizeOf(obj)]; 188 ubyte[] stream = buf; 189 dump(stream, obj); 190 assert (stream.length == 0); 191 return buf; 192 } 193 194 void load(S, T)(ref S stream, ref T obj) { 195 static if (__traits(hasMember, T, "customDump")) { 196 obj.customLoad(stream); 197 } 198 else static if (isArray!T) { 199 alias E = ArrayElement!T; 200 static if (isDynamicArray!T) { 201 LengthType len; 202 load(stream, len); 203 obj.length = len; 204 } 205 static if (isBlittable!E) { 206 blitRead(stream, cast(ubyte[])obj); 207 } 208 else static if (is(E == immutable(char)) || is(E == immutable(wchar)) || is(E == immutable(dchar))) { 209 blitRead(stream, cast(ubyte[])obj); 210 } 211 else { 212 foreach(ref item; obj) { 213 load(stream, item); 214 } 215 } 216 } 217 else static if (isAssociativeArray!T) { 218 LengthType len; 219 load(stream, len); 220 obj.clear(); 221 foreach(_; 0 .. len) { 222 KeyType!k; 223 ValueType!v; 224 load(stream, k); 225 load(stream, v); 226 obj[k] = v; 227 } 228 } 229 else static if (isBlittable!T) { 230 blitRead(stream, obj); 231 } 232 else static if (is(T == struct)) { 233 // we know the struct is not blittable 234 foreach(i, _; typeof(T.tupleof)) { 235 load(stream, obj.tupleof[i]); 236 } 237 } 238 else { 239 static assert (false, "Cannot dump " ~ T.stringof); 240 } 241 } 242 243 T load(T, S)(ref S stream) { 244 Unqual!T tmp; 245 load(stream, tmp); 246 return tmp; 247 } 248 249 250 unittest { 251 import std.stdio; 252 import std..string; 253 254 static void loadDumped(T)(T val) { 255 ubyte[] binary = dump(val); 256 ubyte[] stream = binary; 257 T val2; 258 load(stream, val2); 259 //writefln("Dumped %s as %s, loaded %s", val, binary, val2); 260 assert (stream.length == 0); 261 assert (val == val2, "Dumped %s as %s, loaded as %s".format(val, binary, val2)); 262 } 263 264 loadDumped("hello"); 265 loadDumped(16.25); 266 struct S {float x; uint y;} 267 loadDumped(S(16.25, 88)); 268 struct S2 {float x; uint y; string foo;} 269 loadDumped(S2(16.25, 88, "hello")); 270 } 271 272 273