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 }