1 module unecht.core.serialization.serializer;
2 
3 import std.conv;
4 import std.uuid;
5 import std.traits:isPointer,Unqual,BaseClassesTuple;
6 
7 import unecht.core.component;
8 import unecht.meta.uda;
9 import unecht.core.entity;
10 import unecht.core.object;
11 import sdlang;
12 
13 import std..string:format;
14 
15 enum isSerializerBaseType(T) = 
16         is( T : bool         ) ||
17         is( T : string       ) ||
18         is( T : dchar        ) ||
19         is( T : int          ) ||
20         is( T : long         ) ||
21         is( T : double       ) ||
22         is( T : real         ) ||
23         is( T : ubyte[]      )
24         ;
25 
26 enum isExactSerializerBaseType(T) = 
27     is( T == bool         ) ||
28         is( T == string       ) ||
29         is( T == dchar        ) ||
30         is( T == int          ) ||
31         is( T == long         ) ||
32         is( T == double       ) ||
33         is( T == real         ) ||
34         is( T == ubyte[]      )
35         ;
36         
37 mixin template generateSerializeFunc(alias Func)
38 {
39     void iterateAllSerializables(T)(ref T v, Tag tag)
40     {
41         //pragma (msg, "----------------------------------------");
42         //pragma (msg, T.stringof);
43         //pragma (msg, __traits(derivedMembers, T));
44         
45         foreach(m; __traits(derivedMembers, T))
46         {
47             enum isMemberVariable = is(typeof(() {
48                         __traits(getMember, v, m) = __traits(getMember, v, m).init;
49                     }));
50 
51             enum isMethod = is(typeof(() {
52                         __traits(getMember, v, m)();
53                     }));
54 
55             enum isNonStatic = !is(typeof(mixin("&T."~m)));
56 
57             //pragma(msg, .format("- %s (%s,%s,%s)",m,isMemberVariable,isNonStatic,isMethod));
58             
59             static if(isMemberVariable && isNonStatic && !isMethod) {
60                 
61                 enum isPublic = __traits(getProtection, __traits(getMember, v, m)) == "public";
62                 
63                 enum hasSerializeUDA = hasUDA!(mixin("T."~m), Serialize);
64                 
65                 //pragma(msg, .format("> %s (%s,%s,%s)",m,isPublic,hasSerializeUDA,hasNonSerializeUDA));
66                 
67                 static if(isPublic || hasSerializeUDA)
68                 {
69                     //pragma(msg, "-> "~m);
70 
71                     Func(__traits(getMember, v, m), tag, m);
72                 }
73             }
74         }
75     }
76 }
77 
78 ///
79 struct UESerializer
80 {
81     package Tag content;
82 
83     ///
84     public UUID[] blacklist;
85     ///
86     public UUID[] externals;
87 
88     mixin generateSerializeFunc!serializeMemberWithName;
89 
90     ///
91     public void serializeObjectMember(T,M)(T obj, string name, ref M member, UECustomFuncSerialize!M customFunc=null)
92         if(is(T : UEObject))
93     {
94         if(!content)
95         {
96             content = new Tag();
97             content.name = "content";
98         }
99 
100         serializeTo!(T,M)(obj, name, member, content, customFunc);
101     }
102 
103     private void serializeMemberWithName(T)(T v, Tag tag, string membername)
104     {
105         Tag memberTag = new Tag(tag);
106         memberTag.name = membername;
107         
108         serializeMember(v, memberTag);
109     }
110 
111     private Tag getTag(string id, string type, Tag parent)
112     {
113         Tag idTag = getInstanceTag(id);
114 
115         if(idTag is null)
116         {
117             idTag = new Tag(parent);
118             idTag.name = "obj";
119             idTag.add(new Attribute("id", Value(id)));
120         }
121 
122         Tag typeTag;
123 
124         if(!(type in idTag.all.tags))
125         {
126             typeTag = new Tag(idTag);
127             typeTag.name = type;
128         }
129         else
130             typeTag = idTag.all.tags[type][0];
131 
132         return typeTag;
133     }
134 
135     private void serializeTo(T,M)(T v, string name, ref M member, Tag parent, UECustomFuncSerialize!M func=null)
136     {
137         auto componentTag = getTag(v.instanceId.toString(), Unqual!(T).stringof, parent);
138 
139         if(name in componentTag.all.tags)
140             return;
141 
142         Tag memberTag = new Tag(componentTag);
143         memberTag.name = name;
144 
145         if(func)
146             func(member,this,memberTag);
147         else
148             serializeMember(member, memberTag);
149     }
150                                                             
151     private bool isBlacklisted(in UUID id) const
152     {
153         import std.algorithm:countUntil;
154         return countUntil(blacklist, id) != -1;
155     }
156 
157     private bool isExternal(in UUID id) const
158     {
159         import std.algorithm:countUntil;
160         return countUntil(externals, id) != -1;
161     }
162 
163     private Tag getInstanceTag(string id)
164     {
165         foreach(o; content.all.tags)
166         {
167             auto attribute = o.attributes["id"][0];
168             if(attribute.value.get!string == id)
169                 return o;
170         }
171 
172         return null;
173     }
174 
175     ///
176     public void serializeMember(T)(T val, Tag parent)
177         if(is(T : UEObject))
178     {
179         if(val !is null)
180         {
181             if(isBlacklisted(val.instanceId) || val.hideFlags.isSet(HideFlags.dontSaveInScene))
182             {
183                 parent.remove();
184                 return;
185             }
186 
187             string instanceId = val.instanceId.toString();
188 
189             if(!isExternal(val.instanceId) && !getInstanceTag(instanceId))
190                 val.serialize(this);
191                 
192             parent.add(Value(instanceId));
193             parent.add(new Attribute("type", Value(typeid(val).toString())));
194         }
195     }
196 
197     ///
198     public static void serializeMember(T)(in ref T val, Tag parent)
199         if(is(T == enum))
200     {
201         parent.add(Value(cast(int)val));
202     }
203 
204     ///
205     public void serializeMember(T)(T val, Tag parent)
206         if(__traits(isStaticArray, T))
207     {
208         foreach(v; val)
209         {
210             auto t = new Tag(parent);
211             serializeMember(v,t);
212         }
213     }
214 
215     ///
216     public void serializeMember(T)(T[] val, Tag parent)
217         if( (isSerializerBaseType!T && !is(T : char)) ||
218             (is(T:UEComponent) || is(T:UEEntity)))
219     {
220         foreach(v; val)
221         {
222             auto t = new Tag(parent);
223             serializeMember(v,t);
224         }
225     }
226 
227     ///
228     public void serializeMember(T)(T v, Tag parent)
229         if(is(T == struct))
230     {
231         iterateAllSerializables!(T)(v, parent);
232     }
233 
234     ///
235     public static void serializeMember(T)(T val, Tag parent)
236         if( isSerializerBaseType!T && !is(T == enum) && !__traits(isStaticArray,T))
237     {
238         static if(isExactSerializerBaseType!T)
239             parent.add(Value(val));
240         else
241             parent.add(Value(to!string(val)));
242     }
243 
244     ///
245     public string toString()
246     {
247         auto root = new Tag;
248 
249         root.add(content);
250 
251         return root.toSDLDocument();
252     }
253 }
254 
255 ///
256 struct UEDeserializer
257 {
258     import unecht.core.components.sceneNode;
259 
260     struct LoadedObject
261     {
262         UEObject o;
263         string uid;
264     }
265 
266     private Tag content;
267     package Tag root;
268     public UEObject[] externalObjects;
269     public LoadedObject[] objectsLoaded;
270 
271     mixin generateSerializeFunc!deserializeFromMemberName;
272 
273     ///
274     this(string input)
275     {
276         import std.stdio;
277         root =  parseSource(input);
278 
279         content = root.all.tags["content"][0];
280         assert(content !is null);
281     }
282 
283     ///
284     public void addExternalObj(UEObject obj)
285     {
286         externalObjects ~= obj;
287     }
288 
289     /// renew each id of every loaded object
290     public void createNewIds()
291     {
292         foreach(o; objectsLoaded)
293         {
294             o.o.newInstanceId();
295         }
296     }
297 
298     ///
299     public auto deserializeFirst(T)()
300         if(is(T:UEObject))
301     {
302         auto result = new T;
303         storeLoadedRef(result, findFirstID);
304         result.deserialize(this, findFirstID);
305         return result;
306     }
307 
308     private string findFirstID()
309     {
310         auto contentRoot = content.all.tags.front;
311 
312         return contentRoot.attributes["id"][0].value.get!string; 
313     }
314 
315     ///
316     public void deserializeObjectMember(T,M)(T obj, string uid, string membername, ref M member, UECustomFuncDeserialize!M customFunc=null)
317         if(is(T : UEObject))
318     {
319         if(uid is null || uid.length == 0)
320         {
321             assert(false);
322         }
323         else
324         {
325             auto tag = findObject(uid);
326             assert(tag, format("obj not found: '%s' (%s)",T.stringof, uid));
327 
328             if(!findObject(uid))
329                 storeLoadedRef(obj,uid);
330             
331             deserializeFromTag!(T,M)(obj, membername, member, tag, customFunc);
332         }
333     }
334 
335     ///
336     public bool hasObjectId(string objectId)
337     {
338         return findObject(objectId) !is null;
339     }
340 
341     private Tag findObject(string objectId)
342     {
343         auto objects = content.all.tags["obj"];
344         foreach(Tag o; objects)
345         {
346             auto uid = o.attributes["id"];
347 
348             if(!uid.empty && uid[0].value == objectId)
349             {
350                 return o;
351             }
352         }
353 
354         return null;
355     }
356 
357     private void deserializeFromTag(T,M)(T obj, string membername, ref M member, Tag parent, UECustomFuncDeserialize!M customFunc=null)
358     {
359         auto tags = parent.all.tags[Unqual!(T).stringof];
360 
361         if(tags.empty)
362             return;
363 
364         auto typeTag = tags[0];
365 
366         if(!(membername in typeTag.all.tags))
367             return;
368 
369         auto membertags = typeTag.all.tags[membername];
370 
371         if(membertags.empty)
372             return;
373             
374         if(customFunc)
375             customFunc(member, this, membertags[0]);
376         else
377             deserializeMember(member, membertags[0]);
378     }
379 
380     private void deserializeFromMemberName(T)(ref T v, Tag tag, string membername)
381     {
382         auto memberTag = tag.all.tags[membername][0];
383         assert(memberTag);
384         
385         deserializeMember(v, memberTag);
386     }
387     
388     ///
389     public void deserializeMember(T)(ref T val, Tag parent)
390         if(is(T : UEObject))
391     {
392         if(parent.values.length == 0)
393             return;
394             
395         assert(parent.values.length == 1, format("[%s] wrong value count %s",T.stringof,parent.values.length));
396 
397         const uid = parent.values[0].get!string;
398         assert(uid.length > 0);
399 
400         auto r = findRef(uid);
401         if(r)
402         {
403             val = cast(T)r;
404             assert(val);
405         }
406         else
407         {
408             auto typename = parent.attributes["type"][0].value.get!string;
409             val = cast(T)Object.factory(typename);
410             assert(val, format("could not create: %s",typename));
411             
412             storeLoadedRef(val,uid);
413 
414             val.deserialize(this, uid);
415         }
416     }
417 
418     ///
419     package UEObject findRef(string uid)
420     {
421         auto loaded = findLoadedRef(uid);
422         if(loaded)
423             return loaded;
424 
425         return findExternalRef(uid);
426     }
427 
428     ///
429     package UEObject findLoadedRef(string uid)
430     {
431         alias objArray = objectsLoaded;
432 
433         foreach(o; objArray)
434         {
435             if(o.uid == uid)
436             {
437                 return o.o;
438             }
439         }
440 
441         return null;
442     }
443 
444     ///
445     package UEObject findExternalRef(string uid)
446     {
447         foreach(o; externalObjects)
448         {
449             if(o.instanceId.toString() == uid)
450             {
451                 return o;
452             }
453         }
454 
455         return null;
456     }
457 
458     ///
459     package void storeLoadedRef(UEObject v, string uid)
460     {
461         assert(v !is null);
462 
463         assert(!findRef(uid));
464 
465         objectsLoaded ~= LoadedObject(v,uid);
466     }
467     
468     ///
469     public static void deserializeMember(T)(ref T val, Tag parent)
470         if(is(T == enum))
471     {
472         val = cast(T)parent.values[0].get!int;
473     }
474 
475     ///
476     public void deserializeMember(T)(ref T val, Tag parent)
477         if(__traits(isStaticArray,T))
478     {
479         assert(parent.all.tags.length == T.length);
480         size_t idx=0;
481         foreach(tag; parent.all.tags)
482         {
483             deserializeMember(val[idx++],tag);
484         }
485     }
486 
487     ///
488     public void deserializeMember(T)(ref T[] val, Tag parent)
489         if((isSerializerBaseType!T && !is(T : char)) ||
490             (is(T:UEComponent) || is(T:UEEntity) ))
491     {
492         val.length = parent.all.tags.length;
493         size_t idx=0;
494         foreach(tag; parent.all.tags)
495         {
496             deserializeMember(val[idx++],tag);
497         }
498     }
499 
500     ///
501     public void deserializeMember(T)(ref T v, Tag parent)
502         if(is(T == struct))
503     {
504         iterateAllSerializables(v, parent);
505     }
506 
507     ///
508     public static void deserializeMember(T)(ref T val, Tag parent)
509         if( isSerializerBaseType!T && !is(T == enum) && !__traits(isStaticArray,T))
510     {
511         if(parent.values.length > 0)
512         {
513             assert(parent.values.length == 1, format("deserializeMember!(%s)('%s'): %s",T.stringof, parent.name, parent.values.length));
514 
515             static if(isExactSerializerBaseType!T)
516                 val = parent.values[0].get!T;
517             else
518                 val = to!T(parent.values[0].get!string);
519         }
520     }
521 }
522 
523 /// UDA to mark serialization fields
524 struct Serialize{}
525 
526 ///
527 alias UECustomFuncSerialize(T) = void function(ref T, ref UESerializer, Tag);
528 ///
529 alias UECustomFuncDeserialize(T) = void function(ref T, ref UEDeserializer, Tag);
530 
531 /// UDA to mark a type that contains custom serialization methods
532 struct CustomSerializer
533 {
534     string serializerTypeName;
535 }