From d0f0a34247070c846468e92e33c09dca0b485edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Sun, 24 Dec 2023 01:30:01 +0100 Subject: [PATCH] Start of Script Instance deserialization - Also deserialize when stopping the game to restore pre play state - Fixed ancient bug where ClearDictionaryAndDeleteKeys falsly deletes the dictionary - Added ClearDictionaryAndDeleteValues helper mixin --- GlitchyEditor/src/EditorLayer.bf | 26 +- GlitchyEngine/src/Helper.bf | 12 +- GlitchyEngine/src/Scripting/ScriptEngine.bf | 6 +- GlitchyEngine/src/Scripting/ScriptGlue.bf | 14 + .../src/Scripting/ScriptInstanceSerializer.bf | 50 +- ScriptCore/ScriptGlue.cs | 3 + ScriptCore/Serialization/EntitySerializer.cs | 672 +++++++++++++----- 7 files changed, 579 insertions(+), 204 deletions(-) diff --git a/GlitchyEditor/src/EditorLayer.bf b/GlitchyEditor/src/EditorLayer.bf index 9a6252f..04b12f8 100644 --- a/GlitchyEditor/src/EditorLayer.bf +++ b/GlitchyEditor/src/EditorLayer.bf @@ -863,12 +863,14 @@ namespace GlitchyEditor ImGui.FocusWindow(window); } + append Dictionary _prePlaySerializedData = .() ~ ClearDictionaryAndDeleteValues!(_prePlaySerializedData); + /// Starts the play mode for the current scene private void OnScenePlay() { - Dictionary serializedData = scope .(); + Log.EngineLogger.AssertDebug(_prePlaySerializedData.Count == 0, "Somehow some entities are serialized."); - ScriptEngine.SerializeScriptInstances(serializedData); + ScriptEngine.SerializeScriptInstances(_prePlaySerializedData); _editor.SceneViewportWindow.EditorMode = false; _sceneState = .Play; @@ -881,18 +883,13 @@ namespace GlitchyEditor SetActiveScene(runtimeScene, startRuntime: true, startSimulation: true); - ScriptEngine.DeserializeScriptInstances(serializedData); + ScriptEngine.DeserializeScriptInstances(_prePlaySerializedData); } _editor.CurrentScene = _activeScene; if (Application.Instance.Settings.EditorSettings.SwitchToPlayerOnPlay) SwitchToPlayWindow(); - - for (let v in serializedData) - { - delete v.value; - } } /// Activates the given scene. @@ -987,11 +984,24 @@ namespace GlitchyEditor if (Application.Instance.Settings.EditorSettings.SwitchToEditorOnStop) SwitchToEditorWindow(); + + // Reconstruct state before play + ScriptEngine.DeserializeScriptInstances(_prePlaySerializedData); + + ClearPrePlaySerializedData(); + } + + /// Clears the serialized data cache. + private void ClearPrePlaySerializedData() + { + ClearDictionaryAndDeleteValues!(_prePlaySerializedData); } /// Stops the scene and cleans up the subsystems to allow loading another scene. private void CloseCurrentScene() { + // Clear serialized data, so that we don't waste time deserializing it. + ClearPrePlaySerializedData(); OnSceneStop(); ScriptEngine.ClearEntityScriptFields(); diff --git a/GlitchyEngine/src/Helper.bf b/GlitchyEngine/src/Helper.bf index 1248450..690d7f7 100644 --- a/GlitchyEngine/src/Helper.bf +++ b/GlitchyEngine/src/Helper.bf @@ -67,7 +67,17 @@ namespace GlitchyEngine { for (var value in dictionary) delete value.key; - delete dictionary; + dictionary.Clear(); + } + } + + public static mixin ClearDictionaryAndDeleteValues(var dictionary) + { + if (dictionary != null) + { + for (var value in dictionary) + delete value.value; + dictionary.Clear(); } } diff --git a/GlitchyEngine/src/Scripting/ScriptEngine.bf b/GlitchyEngine/src/Scripting/ScriptEngine.bf index 2e6a508..eb22be2 100644 --- a/GlitchyEngine/src/Scripting/ScriptEngine.bf +++ b/GlitchyEngine/src/Scripting/ScriptEngine.bf @@ -960,11 +960,7 @@ static class ScriptEngine if (script.ScriptClass.ClassName != "SerializationTest") continue; - SerializedObject object = new SerializedObject(allObjects); - - object.Id = script.EntityId; - object.AllObjects = allObjects; - + SerializedObject object = new SerializedObject(allObjects, script.EntityId); object.Serialize(script); } } diff --git a/GlitchyEngine/src/Scripting/ScriptGlue.bf b/GlitchyEngine/src/Scripting/ScriptGlue.bf index 9bd208b..0826ae0 100644 --- a/GlitchyEngine/src/Scripting/ScriptGlue.bf +++ b/GlitchyEngine/src/Scripting/ScriptGlue.bf @@ -559,6 +559,20 @@ static class ScriptGlue newContext = Internal.UnsafeCastToPtr(newObject); newId = newObject.Id; } + + [RegisterCall("ScriptGlue::Serialization_DeserializeField")] + public static void Serialization_DeserializeField(void* internalContext, SerializationType expectedType, MonoString* fieldName, uint8* target) + { + SerializedObject context = Internal.UnsafeCastToObject(internalContext) as SerializedObject; + + Log.EngineLogger.AssertDebug(context != null); + + char8* name = Mono.mono_string_to_utf8(fieldName); + + context.GetField(StringView(name), expectedType, target); + + Mono.mono_free(name); + } #endregion diff --git a/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf b/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf index 7e931e7..6d90e62 100644 --- a/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf +++ b/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf @@ -58,6 +58,10 @@ public enum SerializationType : int32 return 16; case .EntityReference, .ComponentReference, .ObjectReference: return sizeof(UUID); + case .String: + return sizeof(StringView); + case .Enum: + return sizeof(StringView); default: return 0; } @@ -74,16 +78,23 @@ class SerializedObject public append Dictionary Fields = .(); - public this(Dictionary allObjects) + public this(Dictionary allObjects, UUID? id = null) { AllObjects = allObjects; - // Make sure we have no duplicate keys - repeat + if (id == null) { - Id = UUID.Create(); + // Make sure we have no duplicate keys + repeat + { + Id = UUID.Create(); + } + while (AllObjects.ContainsKey(Id)); + } + else + { + Id = id.Value; } - while (AllObjects.ContainsKey(Id)); AllObjects.Add(Id, this); } @@ -140,6 +151,33 @@ class SerializedObject Fields.Add(nameCopy, (primitiveType, data)); } + public void GetField(StringView fieldName, SerializationType expectedType, uint8* target) + { + if (!Fields.TryGetValue(fieldName, let field)) + return; + + if (expectedType != field.PrimitiveType) + return; + + switch (field.PrimitiveType) + { + case .String, .Enum: +#unwarn + StringView view = *(StringView*)&field.Data; + + char8* stringPtr = view.Ptr; + int stringLen = view.Length; + + // We just pass the raw utf8-Pointer and length to C# + Internal.MemCpy(target, &stringPtr, sizeof(void*)); + Internal.MemCpy(target + 8, &stringLen, sizeof(int)); + default: + // Most values can simply be copied, the conversion will be done in C# +#unwarn + Internal.MemCpy(target, &field.Data, 16); + } + } + typealias SerializeMethod = function MonoObject*(MonoObject* entity, void* contextPtr, MonoException** exception); typealias DeserializeMethod = function MonoObject*(MonoObject* entity, void* contextPtr, MonoException** exception); @@ -158,7 +196,7 @@ class SerializedObject public void Deserialize(ScriptInstance scriptInstance) { - DeserializeMethod deserialize = (DeserializeMethod)ScriptEngine.Classes.EntitySerializer.GetMethodThunk("Serialize", 2); + DeserializeMethod deserialize = (DeserializeMethod)ScriptEngine.Classes.EntitySerializer.GetMethodThunk("Deserialize", 2); void* thisPtr = Internal.UnsafeCastToPtr(this); diff --git a/ScriptCore/ScriptGlue.cs b/ScriptCore/ScriptGlue.cs index ba1ae92..bdc4553 100644 --- a/ScriptCore/ScriptGlue.cs +++ b/ScriptCore/ScriptGlue.cs @@ -144,6 +144,9 @@ internal static class ScriptGlue [MethodImpl(MethodImplOptions.InternalCall)] internal static extern void Serialization_CreateObject(IntPtr currentContext, out IntPtr context, out UUID id); + + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern unsafe void Serialization_DeserializeField(IntPtr internalContext, SerializationType expectedType, string fieldName, byte* value); #endregion } diff --git a/ScriptCore/Serialization/EntitySerializer.cs b/ScriptCore/Serialization/EntitySerializer.cs index df300ce..51e2ac3 100644 --- a/ScriptCore/Serialization/EntitySerializer.cs +++ b/ScriptCore/Serialization/EntitySerializer.cs @@ -4,6 +4,8 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; using GlitchyEngine.Core; using GlitchyEngine.Extensions; @@ -251,263 +253,315 @@ public static class EntitySerializer } } } - - public class SerializationContext + + public class DeserializationObject { - StringBuilder _builder = new(); + private IntPtr _internalContext; - public Dictionary SerializedClasses = new(); + private UUID _id; - public UUID ID = UUID.CreateNew(); + private Stack _structScope = new(); - private int _indentLevel = 0; + private string _structScopeName; - private void BeginLine() + public Dictionary SerializedClasses; + + public DeserializationObject(IntPtr internalContext, UUID id, Dictionary serializedClasses) { - for (int i = 0; i < _indentLevel; i++) - _builder.Append('\t'); + _internalContext = internalContext; + _id = id; + SerializedClasses = serializedClasses; } - - private void WriteLine(string line) + + private (SerializedObject context, bool newContext) GetSerializedObject(object o) { - BeginLine(); - - _builder.AppendLine(line); - } - - private void WriteField(string type, string name, string value) - { - BeginLine(); - - _builder.AppendFormat("{0} {1} = {2}", type, name, value); - _builder.Append('\n'); - } - - private void StartBlock() - { - WriteLine("{"); - _indentLevel++; - } - - private void EndBlock() - { - Debug.Assert(_indentLevel > 0); - _indentLevel--; - WriteLine("}"); - } - - /// - /// - /// - /// - /// A serialization context for the given object. And true, if the context was used before, false otherwise. - private (SerializationContext context, bool newContext) GetReferenceTypeContext(object o) - { - SerializationContext context; + SerializedObject context; if (SerializedClasses.TryGetValue(o, out context)) return (context, false); - context = new SerializationContext - { - SerializedClasses = SerializedClasses, - // The indentation is arbitrary anyway, this is just for style - _indentLevel = 1 - }; + ScriptGlue.Serialization_CreateObject(_internalContext, out IntPtr contextPtr, out UUID id); + + context = new SerializedObject(contextPtr, id, SerializedClasses); SerializedClasses.Add(o, context); return (context, true); } + + private void PushScope(string name) + { + _structScope.Push(name); + + _structScopeName += $"{name}."; + } + + private void PopScope() + { + string scopeToRemove = _structScope.Pop(); + _structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1); + } + + private unsafe object GetFieldValue(string fieldName, SerializationType serializationType) + { + string completeFieldName = $"{_structScopeName}{fieldName}"; + + // Decimal is the larges primitive we store so we use a decimal as stack allocated memory (because stackalloc doesn't seem to work :( + //decimal backingFieldOnStack = 0.0m; + byte* rawData = stackalloc byte[16]; + //byte* rawData = (byte*)&backingFieldOnStack; + + ScriptGlue.Serialization_DeserializeField(_internalContext, serializationType, completeFieldName, rawData); + + string GetString() + { + // rawData contains a Pointer and a string length! + byte* utf8Ptr = *(byte**)rawData; + ulong length = *(ulong*)(rawData + 8); + + return Encoding.UTF8.GetString(utf8Ptr, (int)length); + } + + switch (serializationType) + { + case SerializationType.Bool: + return *(bool*)rawData; + case SerializationType.Char: + return *(char*)rawData; + case SerializationType.String: + return GetString(); + case SerializationType.Int8: + return *(sbyte*)rawData; + case SerializationType.Int16: + return *(short*)rawData; + case SerializationType.Int32: + return *(int*)rawData; + case SerializationType.Int64: + return *(long*)rawData; + case SerializationType.UInt8: + return *(byte*)rawData; + case SerializationType.UInt16: + return *(ushort*)rawData; + case SerializationType.UInt32: + return *(uint*)rawData; + case SerializationType.UInt64: + return *(ulong*)rawData; + + case SerializationType.Float: + return *(float*)rawData; + case SerializationType.Double: + return *(double*)rawData; + case SerializationType.Decimal: + return *(decimal*)rawData; + + case SerializationType.Enum: + string value = GetString(); + // TODO! + return null; + + case SerializationType.EntityReference: + case SerializationType.ComponentReference: + case SerializationType.ObjectReference: + return *(UUID*)rawData; + default: + return null; + } + } + + public void Deserialize(Entity entity) + { + DeserializeFields(entity); + } - private void SerializeArray(string fieldName, Type fieldType, object o) + private bool DeserializeFields(object obj) { - Type type = o.GetType(); + Type type = obj.GetType(); - Debug.Assert(type.IsArray); + bool changed = false; - Type elementType = type.GetElementType(); - - Debug.Assert(elementType != null); - - WriteField(type.ToString(), fieldName, ""); - - StartBlock(); - - Array array = (Array)o; - - foreach (var element in array) + foreach (FieldInfo field in type.GetFields()) { - Serialize("", elementType, element); - } - - EndBlock(); - } - - public void SerializeList(string fieldName, Type fieldType, object o) - { - Type type = o.GetType(); - - Debug.Assert(type.IsGenericType); - - Type elementType = type.GetGenericArguments()[0]; - - WriteField(type.ToString(), fieldName, ""); - - StartBlock(); - - IList list = (IList)o; - - foreach (var element in list) - { - Serialize("", elementType, element); - } - - EndBlock(); - } - void SerializeFields(Type objectType, object instance) - { - foreach (FieldInfo fieldInfo in objectType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) - { - if (!SerializeField(fieldInfo)) + if (!EntitySerializer.SerializeField(field)) continue; - object value = fieldInfo.GetValue(instance); - Serialize(fieldInfo.Name, fieldInfo.FieldType, value); + changed |= DeserializeField(obj, field); } + + return changed; } - - /// - /// - /// - /// - /// If true, entities will only be serialized as their UUID - public void Serialize(string fieldName, Type fieldType, object o, bool shallowEntities = true) + private bool DeserializeField(object targetInstance, FieldInfo field) { - // TODO: is this shortcut valid? - if (o == null) - { - WriteField(fieldType.ToString(), fieldName, "null"); - return; - } + Type fieldType = field.FieldType; - Type type = o.GetType(); + if (fieldType.IsPrimitive) + { + object value = DeserializePrimitive(field.Name, fieldType); - if (typeof(Entity).IsAssignableFrom(type)) - { - if (shallowEntities) - WriteField(type.ToString(), fieldName, ((Entity)o).UUID.ToString()); - else - SerializeClass(fieldName, type, o); - //SerializeFields(type, o); + if (value != null) + { + field.SetValue(targetInstance, value); + return true; + } } - else if (typeof(Component).IsAssignableFrom(type)) + else if (fieldType == typeof(string)) { - WriteField(type.ToString(), fieldName, ((Component)o).UUID.ToString()); + object value = GetFieldValue(field.Name, SerializationType.String); + + if (value != null) + { + field.SetValue(targetInstance, value); + return true; + } } - else if (type == typeof(string)) + else if (fieldType.IsEnum) { - WriteField(type.ToString(), fieldName, $"\"{(string)o}\""); + // SerializeEnum(fieldName, fieldValue, fieldType); } - else if (type.IsPrimitive) + else if (fieldType.IsArray) { - WriteField(type.ToString(), fieldName, o.ToString()); + // Log.Error($"Array serialization not yet implemented"); } - else if (type.IsEnum) - { - WriteField(type.ToString(), fieldName, o.ToString()); - } - else if (type.IsArray) - { - SerializeArray(fieldName, type, o); - } - else if (type.IsGenericType) + else if (fieldType.IsGenericType) { if (fieldType.GetGenericTypeDefinition() == typeof(List<>)) { - SerializeList(fieldName, type, o); + //SerializeList(fieldName, type, o); + Log.Error($"List serialization not yet implemented"); } - //else if (fieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + //else if (fieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) //{ // ImGui.Text($"{fieldName} Dictionary"); //} else { // TODO: what to do? + Log.Error($"Generic class serialization not yet implemented"); } } - else if (type.IsValueType) + else if (fieldType.IsValueType) { - SerializeStruct(fieldName, type, o); + object structValue = field.GetValue(targetInstance); + + bool changed = DeserializeStruct(field.Name, structValue); + + if (changed) + { + field.SetValue(targetInstance, structValue); + return true; + } } - else if (type.IsClass) + else if (fieldType.IsClass) { - // TODO: Use custom serializer, if available - - SerializeClass(fieldName, type, o); + // SerializeClass(fieldName, fieldValue, fieldType); } else { - Log.Error($"Encountered unhandled type \"{type}\" while serializing."); + Log.Error($"Encountered unhandled type \"{fieldType}\" while serializing."); } + + return false; } - - private void SerializeStruct(string fieldName, Type type, object o) + + private object DeserializePrimitive(string fieldName, Type fieldType) { - WriteField(type.ToString(), fieldName, "{"); - StartBlock(); + Debug.Assert(fieldType.IsPrimitive, $"{fieldType} is not a primitive type."); + + SerializationType expectedType = SerializationType.None; - SerializeFields(type, o); - - EndBlock(); - } - - private void SerializeClass(string fieldName, Type type, object o) - { - (SerializationContext context, bool newContext) = GetReferenceTypeContext(o); - - // Only serialize the class, if it wasn't serialized before, obviously... - if (newContext) - context.SerializeFields(type, o); - - WriteField(type.ToString(), fieldName, context.ID.ToString()); - } - - public override string ToString() - { - StringBuilder finalBuilder = new(); - - foreach (var context in SerializedClasses) + if (fieldType == typeof(bool)) + expectedType = SerializationType.Bool; + else if (fieldType == typeof(char)) + expectedType = SerializationType.Char; + else if (fieldType == typeof(byte)) + expectedType = SerializationType.UInt8; + else if (fieldType == typeof(sbyte)) + expectedType = SerializationType.Int8; + else if (fieldType == typeof(ushort)) + expectedType = SerializationType.UInt16; + else if (fieldType == typeof(short)) + expectedType = SerializationType.Int16; + else if (fieldType == typeof(uint)) + expectedType = SerializationType.UInt32; + else if (fieldType == typeof(int)) + expectedType = SerializationType.Int32; + else if (fieldType == typeof(ulong)) + expectedType = SerializationType.UInt64; + else if (fieldType == typeof(long)) + expectedType = SerializationType.Int64; + else if (fieldType == typeof(float)) + expectedType = SerializationType.Float; + else if (fieldType == typeof(double)) + expectedType = SerializationType.Double; + else if (fieldType == typeof(decimal)) + expectedType = SerializationType.Decimal; + else { - finalBuilder.Append($"{context.Value.ID} = {{\n"); - - finalBuilder.Append(context.Value._builder); - - finalBuilder.Append("}\n\n"); + Log.Error($"The primitive {fieldType} is not implemented."); } - - finalBuilder.Append("Object:"); - finalBuilder.Append(_builder); + return GetFieldValue(fieldName, expectedType); + } - return finalBuilder.ToString(); + private void SerializeEnum(string fieldName, object fieldValue, Type fieldType) + { + //AddField(fieldName, SerializationType.Enum, fieldValue.ToString()); + } + private bool DeserializeStruct(string fieldName, object targetInstance) + { + PushScope(fieldName); + + bool changed = DeserializeFields(targetInstance); + + PopScope(); + + return changed; + } + + private void SerializeClass(string fieldName, object fieldValue, Type fieldType) + { + //if (typeof(Entity).IsAssignableFrom(fieldType)) + //{ + // AddField(fieldName, SerializationType.EntityReference, ((Entity)fieldValue)?.UUID ?? UUID.Zero); + //} + //else if (typeof(Component).IsAssignableFrom(fieldType)) + //{ + // AddField(fieldName, SerializationType.ComponentReference, ((Component)fieldValue)?.UUID ?? UUID.Zero); + //} + //else + //{ + // if (fieldValue == null) + // { + // AddField(fieldName, SerializationType.ObjectReference, UUID.Zero); + // } + // else + // { + // var (context, newContext) = GetSerializedObject(fieldValue); + + // if (newContext) + // { + // context.SerializeFields(fieldValue); + // } + + // AddField(fieldName, SerializationType.ObjectReference, context._id); + // } + //} } } - //public static void Serialize(Entity entity, SerializationContext context) - //{ - // context ??= new SerializationContext(); - - // context.Serialize($"Entity: {entity.UUID}", typeof(Entity), entity, false); - //} public static void Serialize(Entity entity, IntPtr internalContext) { SerializedObject obj = new SerializedObject(internalContext, UUID.Zero, new Dictionary()); obj.Serialize(entity); } + + public static void Deserialize(Entity entity, IntPtr internalContext) + { + DeserializationObject obj = new DeserializationObject(internalContext, UUID.Zero, new Dictionary()); + obj.Deserialize(entity); + } public static bool SerializeField(FieldInfo fieldInfo) { @@ -533,3 +587,253 @@ public static class EntitySerializer return false; } } + +/* + * + * + public class SerializationContext + { + StringBuilder _builder = new(); + + public Dictionary SerializedClasses = new(); + + public UUID ID = UUID.CreateNew(); + + private int _indentLevel = 0; + + private void BeginLine() + { + for (int i = 0; i < _indentLevel; i++) + _builder.Append('\t'); + } + + private void WriteLine(string line) + { + BeginLine(); + + _builder.AppendLine(line); + } + + private void WriteField(string type, string name, string value) + { + BeginLine(); + + _builder.AppendFormat("{0} {1} = {2}", type, name, value); + _builder.Append('\n'); + } + + private void StartBlock() + { + WriteLine("{"); + _indentLevel++; + } + + private void EndBlock() + { + Debug.Assert(_indentLevel > 0); + _indentLevel--; + WriteLine("}"); + } + + /// + /// + /// + /// + /// A serialization context for the given object. And true, if the context was used before, false otherwise. + private (SerializationContext context, bool newContext) GetReferenceTypeContext(object o) + { + SerializationContext context; + + if (SerializedClasses.TryGetValue(o, out context)) + return (context, false); + + context = new SerializationContext + { + SerializedClasses = SerializedClasses, + // The indentation is arbitrary anyway, this is just for style + _indentLevel = 1 + }; + + SerializedClasses.Add(o, context); + + return (context, true); + } + + private void SerializeArray(string fieldName, Type fieldType, object o) + { + Type type = o.GetType(); + + Debug.Assert(type.IsArray); + + Type elementType = type.GetElementType(); + + Debug.Assert(elementType != null); + + WriteField(type.ToString(), fieldName, ""); + + StartBlock(); + + Array array = (Array)o; + + foreach (var element in array) + { + Serialize("", elementType, element); + } + + EndBlock(); + } + + public void SerializeList(string fieldName, Type fieldType, object o) + { + Type type = o.GetType(); + + Debug.Assert(type.IsGenericType); + + Type elementType = type.GetGenericArguments()[0]; + + WriteField(type.ToString(), fieldName, ""); + + StartBlock(); + + IList list = (IList)o; + + foreach (var element in list) + { + Serialize("", elementType, element); + } + + EndBlock(); + } + void SerializeFields(Type objectType, object instance) + { + foreach (FieldInfo fieldInfo in objectType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) + { + if (!SerializeField(fieldInfo)) + continue; + + object value = fieldInfo.GetValue(instance); + Serialize(fieldInfo.Name, fieldInfo.FieldType, value); + } + } + + + /// + /// + /// + /// + /// If true, entities will only be serialized as their UUID + public void Serialize(string fieldName, Type fieldType, object o, bool shallowEntities = true) + { + // TODO: is this shortcut valid? + if (o == null) + { + WriteField(fieldType.ToString(), fieldName, "null"); + return; + } + + Type type = o.GetType(); + + if (typeof(Entity).IsAssignableFrom(type)) + { + if (shallowEntities) + WriteField(type.ToString(), fieldName, ((Entity)o).UUID.ToString()); + else + SerializeClass(fieldName, type, o); + //SerializeFields(type, o); + } + else if (typeof(Component).IsAssignableFrom(type)) + { + WriteField(type.ToString(), fieldName, ((Component)o).UUID.ToString()); + } + else if (type == typeof(string)) + { + WriteField(type.ToString(), fieldName, $"\"{(string)o}\""); + } + else if (type.IsPrimitive) + { + WriteField(type.ToString(), fieldName, o.ToString()); + } + else if (type.IsEnum) + { + WriteField(type.ToString(), fieldName, o.ToString()); + } + else if (type.IsArray) + { + SerializeArray(fieldName, type, o); + } + else if (type.IsGenericType) + { + if (fieldType.GetGenericTypeDefinition() == typeof(List<>)) + { + SerializeList(fieldName, type, o); + } + //else if (fieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + //{ + // ImGui.Text($"{fieldName} Dictionary"); + //} + else + { + // TODO: what to do? + } + } + else if (type.IsValueType) + { + SerializeStruct(fieldName, type, o); + } + else if (type.IsClass) + { + // TODO: Use custom serializer, if available + + SerializeClass(fieldName, type, o); + } + else + { + Log.Error($"Encountered unhandled type \"{type}\" while serializing."); + } + } + + private void SerializeStruct(string fieldName, Type type, object o) + { + WriteField(type.ToString(), fieldName, "{"); + StartBlock(); + + SerializeFields(type, o); + + EndBlock(); + } + + private void SerializeClass(string fieldName, Type type, object o) + { + (SerializationContext context, bool newContext) = GetReferenceTypeContext(o); + + // Only serialize the class, if it wasn't serialized before, obviously... + if (newContext) + context.SerializeFields(type, o); + + WriteField(type.ToString(), fieldName, context.ID.ToString()); + } + + public override string ToString() + { + StringBuilder finalBuilder = new(); + + foreach (var context in SerializedClasses) + { + finalBuilder.Append($"{context.Value.ID} = {{\n"); + + finalBuilder.Append(context.Value._builder); + + finalBuilder.Append("}\n\n"); + } + + finalBuilder.Append("Object:"); + + finalBuilder.Append(_builder); + + return finalBuilder.ToString(); + + } + } + + * + */