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