From 50518297530aa6e6d9a30076a5ee6a7927ee0430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Thu, 28 Dec 2023 22:50:24 +0100 Subject: [PATCH] Split up, clean up and internalize EntitySerializer --- .../Serialization/DeserializationObject.cs | 413 ++++++++ ScriptCore/Serialization/EntitySerializer.cs | 882 +----------------- ScriptCore/Serialization/SerializedObject.cs | 217 +++++ 3 files changed, 638 insertions(+), 874 deletions(-) create mode 100644 ScriptCore/Serialization/DeserializationObject.cs create mode 100644 ScriptCore/Serialization/SerializedObject.cs diff --git a/ScriptCore/Serialization/DeserializationObject.cs b/ScriptCore/Serialization/DeserializationObject.cs new file mode 100644 index 0000000..d121304 --- /dev/null +++ b/ScriptCore/Serialization/DeserializationObject.cs @@ -0,0 +1,413 @@ +using GlitchyEngine.Core; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace GlitchyEngine.Serialization; + +internal class DeserializationObject +{ + private IntPtr _internalContext; + + private UUID _id; + + private Stack _structScope = new(); + + private string _structScopeName; + + public Dictionary DeserializedClasses; + + private object _instance; + + public DeserializationObject(IntPtr internalContext, UUID id, Dictionary deserializedClasses) + { + _internalContext = internalContext; + _id = id; + DeserializedClasses = deserializedClasses; + } + + private Dictionary _fullNameToType = new(); + + private Type FindType(string fullName) + { + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies().Reverse()) + { + Type type = assembly.GetType(fullName); + + if (type != null) + return type; + } + + return null; + } + + private Type GetTypeFromName(string fullName) + { + if (_fullNameToType.TryGetValue(fullName, out Type storedType)) + return storedType; + + Type type = FindType(fullName); + + _fullNameToType.Add(fullName, type); + + return type; + } + + private DeserializationObject GetDeserializedObject(UUID id) + { + DeserializationObject context; + + if (DeserializedClasses.TryGetValue(id, out context)) + return context; + + ScriptGlue.Serialization_GetObject(_internalContext, id, out IntPtr contextPtr); + + context = new DeserializationObject(contextPtr, id, DeserializedClasses); + + DeserializedClasses.Add(id, context); + + ScriptGlue.Serialization_GetObjectTypeName(context._internalContext, out string fullTypeName); + + Type type = GetTypeFromName(fullTypeName); + + if (type == null) + return context; + + try + { + context._instance = Activator.CreateInstance(type, true); + } + catch (MissingMethodException e) + { + Log.Error($"Failed to create instance of type \"{type}\": The type doesn't contain a constructor with zero parameters.\nMake sure the type has a constructor that takes no arguments (It can be private!).\n{e}"); + } + catch (MethodAccessException e) + { + Log.Error($"Failed to create instance of type \"{type}\": The default constructor is not accessible.\n{e}"); + } + catch (Exception e) + { + Log.Error(e); + + return context; + } + + if (context._instance == null) + return context; + + context.DeserializeFields(context._instance); + + return context; + } + + private void PushScope(string name) + { + _structScope.Push(name); + + _structScopeName += $"{name}."; + } + + private void PopScope() + { + string scopeToRemove = _structScope.Pop(); + _structScopeName = _structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1); + } + + [StructLayout(LayoutKind.Explicit)] + private unsafe struct DataHelper + { + [StructLayout(LayoutKind.Sequential)] + public struct EngineObjectReferenceHelper + { + public byte* FullTypeName; + public long FullTypeNameLength; + public UUID Id; + } + + [FieldOffset(0)] + public EngineObjectReferenceHelper EngineObjectReference; + } + + 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[sizeof(DataHelper)]; + //byte* rawData = (byte*)&backingFieldOnStack; + + ref DataHelper dataHelper = ref Unsafe.AsRef(rawData); + + ScriptGlue.Serialization_DeserializeField(_internalContext, serializationType, completeFieldName, rawData); + + string GetString() + { + // rawData contains a Pointer and a string length! + byte* utf8Ptr = *(byte**)rawData; + + if (utf8Ptr == null) + return null; + + ulong length = *(ulong*)(rawData + 8); + + if (length == 0) + return string.Empty; + + 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: + return GetString(); + case SerializationType.EntityReference: + case SerializationType.ComponentReference: + return dataHelper.EngineObjectReference; + case SerializationType.ObjectReference: + return *(UUID*)rawData; + default: + return null; + } + } + + public void Deserialize(Entity entity) + { + _instance = entity; + + DeserializeFields(entity); + } + + private bool DeserializeFields(object obj) + { + Type type = obj.GetType(); + + bool changed = false; + + foreach (FieldInfo field in type.GetFields()) + { + if (!EntitySerializer.SerializeField(field)) + continue; + + changed |= DeserializeField(obj, field); + } + + return changed; + } + + private bool DeserializeField(object targetInstance, FieldInfo field) + { + Type fieldType = field.FieldType; + + object newFieldValue = null; + + if (fieldType.IsPrimitive) + { + newFieldValue = DeserializePrimitive(field.Name, fieldType); + } + else if (fieldType == typeof(string)) + { + newFieldValue = GetFieldValue(field.Name, SerializationType.String); + } + else if (fieldType.IsEnum) + { + newFieldValue = DeserializeEnum(field.Name, fieldType); + } + else if (fieldType.IsArray) + { + // Log.Error($"Array serialization not yet implemented"); + } + else if (fieldType.IsGenericType) + { + if (fieldType.GetGenericTypeDefinition() == typeof(List<>)) + { + //SerializeList(fieldName, type, o); + Log.Error($"List serialization not yet implemented"); + } + //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 (fieldType.IsValueType) + { + object structValue = field.GetValue(targetInstance); + + newFieldValue = DeserializeStruct(field.Name, structValue); + } + else if (fieldType.IsClass) + { + newFieldValue = DeserializeClass(field.Name, fieldType); + } + else + { + Log.Error($"Encountered unhandled type \"{fieldType}\" while serializing."); + } + + if (newFieldValue != null) + { + field.SetValue(targetInstance, newFieldValue); + return true; + } + + return false; + } + + private object DeserializePrimitive(string fieldName, Type fieldType) + { + Debug.Assert(fieldType.IsPrimitive, $"{fieldType} is not a primitive type."); + + SerializationType expectedType = SerializationType.None; + + 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 + { + Log.Error($"The primitive {fieldType} is not implemented."); + } + + return GetFieldValue(fieldName, expectedType); + } + + private object DeserializeEnum(string fieldName, Type enumType) + { + if (GetFieldValue(fieldName, SerializationType.Enum) is not string valueName) + return null; + + try + { + return Enum.Parse(enumType, valueName); + } + catch + { + Log.Error($"Failed to parse \"{valueName}\" as enum-type \"{enumType}\""); + } + + return null; + } + + private object DeserializeStruct(string fieldName, object targetInstance) + { + PushScope(fieldName); + + bool changed = DeserializeFields(targetInstance); + + PopScope(); + + return changed ? targetInstance : null; + } + + private unsafe object DeserializeClass(string fieldName, Type fieldType) + { + bool isEntity = typeof(Entity).IsAssignableFrom(fieldType); + bool isComponent = fieldType.IsSubclassOf(typeof(Component)); + + if (isEntity || isComponent) + { + var data = (DataHelper.EngineObjectReferenceHelper)GetFieldValue(fieldName, isEntity ? SerializationType.EntityReference : SerializationType.ComponentReference); + + UUID id = data.Id; + + if (id == UUID.Zero) + return null; + + string fullTypeName = Encoding.UTF8.GetString(data.FullTypeName, (int)data.FullTypeNameLength); + + Type type = GetTypeFromName(fullTypeName); + + if (type == null) + return null; + + if (isEntity) + { + // We have to differentiate between simple Entity references and script instances + // (because we decided to use the same type for both, so we could have a field Entity which contains a script instance instead of an Entity reference) + if (type == typeof(Entity)) + return new Entity(id); + + return Entity.GetScriptReference(id, type); + } + else + { + Entity entity = new Entity(id); + + return entity.GetComponent(type); + } + } + else + { + UUID id = (UUID)GetFieldValue(fieldName, SerializationType.ObjectReference); + + if (id == UUID.Zero) + return null; + + DeserializationObject deserializedObject = GetDeserializedObject(id); + + return deserializedObject._instance; + } + } +} diff --git a/ScriptCore/Serialization/EntitySerializer.cs b/ScriptCore/Serialization/EntitySerializer.cs index a466616..a78e609 100644 --- a/ScriptCore/Serialization/EntitySerializer.cs +++ b/ScriptCore/Serialization/EntitySerializer.cs @@ -1,19 +1,13 @@ using GlitchyEngine.Editor; using System; -using System.Collections; using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; using GlitchyEngine.Core; using GlitchyEngine.Extensions; namespace GlitchyEngine.Serialization; -public enum SerializationType : int +internal enum SerializationType : int { None, @@ -43,634 +37,24 @@ public enum SerializationType : int ObjectReference } -public static class EntitySerializer +internal static class EntitySerializer { - public class SerializedObject - { - private IntPtr _internalContext; - - private UUID _id; - - private Stack _structScope = new(); - - private string _structScopeName; - - public Dictionary SerializedClasses; - - public SerializedObject(IntPtr internalContext, UUID id, Dictionary serializedClasses) - { - _internalContext = internalContext; - _id = id; - SerializedClasses = serializedClasses; - } - - private (SerializedObject context, bool newContext) GetSerializedObject(object o) - { - SerializedObject context; - - if (SerializedClasses.TryGetValue(o, out context)) - return (context, false); - - ScriptGlue.Serialization_CreateObject(_internalContext, o.GetType().FullName, 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 = _structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1); - } - - private void AddField(string fieldName, SerializationType serializationType, object value, string fullTypeName = null) - { - string completeFieldName = $"{_structScopeName}{fieldName}"; - - ScriptGlue.Serialization_SerializeField(_internalContext, serializationType, completeFieldName, value, fullTypeName); - } - - public void Serialize(Entity entity) - { - SerializeFields(entity); - } - - private void SerializeFields(object obj) - { - Type type = obj.GetType(); - - foreach (FieldInfo field in type.GetFields()) - { - if (!EntitySerializer.SerializeField(field)) - continue; - - object fieldValue = field.GetValue(obj); - Type fieldType = fieldValue?.GetType() ?? field.FieldType; - - SerializeField(field.Name, fieldValue, fieldType); - } - } - - private void SerializeField(string fieldName, object fieldValue, Type fieldType) - { - if (fieldType.IsPrimitive) - { - SerializePrimitive(fieldName, fieldValue, fieldType); - } - else if (fieldType == typeof(string)) - { - AddField(fieldName, SerializationType.String, fieldValue); - } - else if (fieldType.IsEnum) - { - SerializeEnum(fieldName, fieldValue, fieldType); - } - else if (fieldType.IsArray) - { - Log.Error($"Array serialization not yet implemented"); - } - else if (fieldType.IsGenericType) - { - if (fieldType.GetGenericTypeDefinition() == typeof(List<>)) - { - //SerializeList(fieldName, type, o); - Log.Error($"List serialization not yet implemented"); - } - //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 (fieldType.IsValueType) - { - SerializeStruct(fieldName, fieldValue, fieldType); - } - else if (fieldType.IsClass) - { - SerializeClass(fieldName, fieldValue, fieldType); - } - else - { - Log.Error($"Encountered unhandled type \"{fieldType}\" while serializing."); - } - } - - private void SerializePrimitive(string fieldName, object fieldValue, Type fieldType) - { - Debug.Assert(fieldType.IsPrimitive, $"{fieldType} is not a primitive type."); - - SerializationType type = SerializationType.None; - - if (fieldType == typeof(bool)) - type = SerializationType.Bool; - else if (fieldType == typeof(char)) - type = SerializationType.Char; - else if (fieldType == typeof(byte)) - type = SerializationType.UInt8; - else if (fieldType == typeof(sbyte)) - type = SerializationType.Int8; - else if (fieldType == typeof(ushort)) - type = SerializationType.UInt16; - else if (fieldType == typeof(short)) - type = SerializationType.Int16; - else if (fieldType == typeof(uint)) - type = SerializationType.UInt32; - else if (fieldType == typeof(int)) - type = SerializationType.Int32; - else if (fieldType == typeof(ulong)) - type = SerializationType.UInt64; - else if (fieldType == typeof(long)) - type = SerializationType.Int64; - else if (fieldType == typeof(float)) - type = SerializationType.Float; - else if (fieldType == typeof(double)) - type = SerializationType.Double; - else if (fieldType == typeof(decimal)) - type = SerializationType.Decimal; - else - { - Log.Error($"The primitive {fieldType} is not implemented."); - } - - AddField(fieldName, type, fieldValue); - } - - private void SerializeEnum(string fieldName, object fieldValue, Type fieldType) - { - AddField(fieldName, SerializationType.Enum, fieldValue.ToString()); - } - - private void SerializeStruct(string fieldName, object fieldValue, Type fieldType) - { - PushScope(fieldName); - - SerializeFields(fieldValue); - - PopScope(); - } - - private void SerializeClass(string fieldName, object fieldValue, Type fieldType) - { - if (typeof(Entity).IsAssignableFrom(fieldType)) - { - AddField(fieldName, SerializationType.EntityReference, ((Entity)fieldValue)?.UUID ?? UUID.Zero, fieldValue?.GetType().FullName); - } - else if (fieldType.IsSubclassOf(typeof(Component))) - { - AddField(fieldName, SerializationType.ComponentReference, ((Component)fieldValue)?.UUID ?? UUID.Zero, fieldValue?.GetType().FullName); - } - 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 class DeserializationObject - { - private IntPtr _internalContext; - - private UUID _id; - - private Stack _structScope = new(); - - private string _structScopeName; - - public Dictionary DeserializedClasses; - - private object _instance; - - public DeserializationObject(IntPtr internalContext, UUID id, Dictionary deserializedClasses) - { - _internalContext = internalContext; - _id = id; - DeserializedClasses = deserializedClasses; - } - - private Dictionary _fullNameToType = new(); - - private Type FindType(string fullName) - { - foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies().Reverse()) - { - Type type = assembly.GetType(fullName); - - if (type != null) - return type; - } - - return null; - } - - private Type GetTypeFromName(string fullName) - { - if (_fullNameToType.TryGetValue(fullName, out Type storedType)) - return storedType; - - Type type = FindType(fullName); - - _fullNameToType.Add(fullName, type); - - return type; - } - - private DeserializationObject GetDeserializedObject(UUID id) - { - DeserializationObject context; - - if (DeserializedClasses.TryGetValue(id, out context)) - return context; - - ScriptGlue.Serialization_GetObject(_internalContext, id, out IntPtr contextPtr); - - context = new DeserializationObject(contextPtr, id, DeserializedClasses); - - DeserializedClasses.Add(id, context); - - ScriptGlue.Serialization_GetObjectTypeName(context._internalContext, out string fullTypeName); - - Type type = GetTypeFromName(fullTypeName); - - if (type == null) - return context; - - try - { - context._instance = Activator.CreateInstance(type, true); - } - catch (MissingMethodException e) - { - Log.Error($"Failed to create instance of type \"{type}\": The type doesn't contain a constructor with zero parameters.\nMake sure the type has a constructor that takes no arguments (It can be private!).\n{e}"); - } - catch (MethodAccessException e) - { - Log.Error($"Failed to create instance of type \"{type}\": The default constructor is not accessible.\n{e}"); - } - catch (Exception e) - { - Log.Error(e); - - return context; - } - - if (context._instance == null) - return context; - - context.DeserializeFields(context._instance); - - return context; - } - - private void PushScope(string name) - { - _structScope.Push(name); - - _structScopeName += $"{name}."; - } - - private void PopScope() - { - string scopeToRemove = _structScope.Pop(); - _structScopeName = _structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1); - } - - [StructLayout(LayoutKind.Explicit)] - private unsafe struct DataHelper - { - [StructLayout(LayoutKind.Sequential)] - public struct EngineObjectReferenceHelper - { - public byte* FullTypeName; - public long FullTypeNameLength; - public UUID Id; - } - - [FieldOffset(0)] - public EngineObjectReferenceHelper EngineObjectReference; - } - - 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[sizeof(DataHelper)]; - //byte* rawData = (byte*)&backingFieldOnStack; - - ref DataHelper dataHelper = ref Unsafe.AsRef(rawData); - - ScriptGlue.Serialization_DeserializeField(_internalContext, serializationType, completeFieldName, rawData); - - string GetString() - { - // rawData contains a Pointer and a string length! - byte* utf8Ptr = *(byte**)rawData; - - if (utf8Ptr == null) - return null; - - ulong length = *(ulong*)(rawData + 8); - - if (length == 0) - return string.Empty; - - 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: - return GetString(); - case SerializationType.EntityReference: - case SerializationType.ComponentReference: - return dataHelper.EngineObjectReference; - case SerializationType.ObjectReference: - return *(UUID*)rawData; - default: - return null; - } - } - - public void Deserialize(Entity entity) - { - _instance = entity; - - DeserializeFields(entity); - } - - private bool DeserializeFields(object obj) - { - Type type = obj.GetType(); - - bool changed = false; - - foreach (FieldInfo field in type.GetFields()) - { - if (!EntitySerializer.SerializeField(field)) - continue; - - changed |= DeserializeField(obj, field); - } - - return changed; - } - - private bool DeserializeField(object targetInstance, FieldInfo field) - { - Type fieldType = field.FieldType; - - object newFieldValue = null; - - if (fieldType.IsPrimitive) - { - newFieldValue = DeserializePrimitive(field.Name, fieldType); - } - else if (fieldType == typeof(string)) - { - newFieldValue = GetFieldValue(field.Name, SerializationType.String); - } - else if (fieldType.IsEnum) - { - newFieldValue = DeserializeEnum(field.Name, fieldType); - } - else if (fieldType.IsArray) - { - // Log.Error($"Array serialization not yet implemented"); - } - else if (fieldType.IsGenericType) - { - if (fieldType.GetGenericTypeDefinition() == typeof(List<>)) - { - //SerializeList(fieldName, type, o); - Log.Error($"List serialization not yet implemented"); - } - //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 (fieldType.IsValueType) - { - object structValue = field.GetValue(targetInstance); - - newFieldValue = DeserializeStruct(field.Name, structValue); - } - else if (fieldType.IsClass) - { - newFieldValue = DeserializeClass(field.Name, fieldType); - } - else - { - Log.Error($"Encountered unhandled type \"{fieldType}\" while serializing."); - } - - if (newFieldValue != null) - { - field.SetValue(targetInstance, newFieldValue); - return true; - } - - return false; - } - - private object DeserializePrimitive(string fieldName, Type fieldType) - { - Debug.Assert(fieldType.IsPrimitive, $"{fieldType} is not a primitive type."); - - SerializationType expectedType = SerializationType.None; - - 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 - { - Log.Error($"The primitive {fieldType} is not implemented."); - } - - return GetFieldValue(fieldName, expectedType); - } - - private object DeserializeEnum(string fieldName, Type enumType) - { - if (GetFieldValue(fieldName, SerializationType.Enum) is not string valueName) - return null; - - try - { - return Enum.Parse(enumType, valueName); - } - catch - { - Log.Error($"Failed to parse \"{valueName}\" as enum-type \"{enumType}\""); - } - - return null; - } - - private object DeserializeStruct(string fieldName, object targetInstance) - { - PushScope(fieldName); - - bool changed = DeserializeFields(targetInstance); - - PopScope(); - - return changed ? targetInstance : null; - } - - private unsafe object DeserializeClass(string fieldName, Type fieldType) - { - bool isEntity = typeof(Entity).IsAssignableFrom(fieldType); - bool isComponent = fieldType.IsSubclassOf(typeof(Component)); - - if (isEntity || isComponent) - { - var data = (DataHelper.EngineObjectReferenceHelper)GetFieldValue(fieldName, isEntity ? SerializationType.EntityReference : SerializationType.ComponentReference); - - UUID id = data.Id; - - if (id == UUID.Zero) - return null; - - string fullTypeName = Encoding.UTF8.GetString(data.FullTypeName, (int)data.FullTypeNameLength); - - Type type = GetTypeFromName(fullTypeName); - - if (type == null) - return null; - - if (isEntity) - { - // We have to differentiate between simple Entity references and script instances - // (because we decided to use the same type for both, so we could have a field Entity which contains a script instance instead of an Entity reference) - if (type == typeof(Entity)) - return new Entity(id); - - return Entity.GetScriptReference(id, type); - } - else - { - Entity entity = new Entity(id); - - return entity.GetComponent(type); - } - } - else - { - UUID id = (UUID)GetFieldValue(fieldName, SerializationType.ObjectReference); - - if (id == UUID.Zero) - return null; - - DeserializationObject deserializedObject = GetDeserializedObject(id); - - return deserializedObject._instance; - } - } - } - - - public static void Serialize(Entity entity, IntPtr internalContext) + internal 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) + internal 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) + /// + /// Returns whether or not the field described by the given should be serialized or not. + /// + internal static bool SerializeField(FieldInfo fieldInfo) { // TODO: We want to be able to serialize static fields in the future! if (fieldInfo.IsStatic) @@ -698,253 +82,3 @@ 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(); - - } - } - - * - */ diff --git a/ScriptCore/Serialization/SerializedObject.cs b/ScriptCore/Serialization/SerializedObject.cs new file mode 100644 index 0000000..77f87f0 --- /dev/null +++ b/ScriptCore/Serialization/SerializedObject.cs @@ -0,0 +1,217 @@ +using GlitchyEngine.Core; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; + +namespace GlitchyEngine.Serialization; + +internal class SerializedObject +{ + private IntPtr _internalContext; + + private UUID _id; + + private Stack _structScope = new(); + + private string _structScopeName; + + public Dictionary SerializedClasses; + + public SerializedObject(IntPtr internalContext, UUID id, Dictionary serializedClasses) + { + _internalContext = internalContext; + _id = id; + SerializedClasses = serializedClasses; + } + + private (SerializedObject context, bool newContext) GetSerializedObject(object o) + { + SerializedObject context; + + if (SerializedClasses.TryGetValue(o, out context)) + return (context, false); + + ScriptGlue.Serialization_CreateObject(_internalContext, o.GetType().FullName, 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 = _structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1); + } + + private void AddField(string fieldName, SerializationType serializationType, object value, string fullTypeName = null) + { + string completeFieldName = $"{_structScopeName}{fieldName}"; + + ScriptGlue.Serialization_SerializeField(_internalContext, serializationType, completeFieldName, value, fullTypeName); + } + + public void Serialize(Entity entity) + { + SerializeFields(entity); + } + + private void SerializeFields(object obj) + { + Type type = obj.GetType(); + + foreach (FieldInfo field in type.GetFields()) + { + if (!EntitySerializer.SerializeField(field)) + continue; + + object fieldValue = field.GetValue(obj); + Type fieldType = fieldValue?.GetType() ?? field.FieldType; + + SerializeField(field.Name, fieldValue, fieldType); + } + } + + private void SerializeField(string fieldName, object fieldValue, Type fieldType) + { + if (fieldType.IsPrimitive) + { + SerializePrimitive(fieldName, fieldValue, fieldType); + } + else if (fieldType == typeof(string)) + { + AddField(fieldName, SerializationType.String, fieldValue); + } + else if (fieldType.IsEnum) + { + SerializeEnum(fieldName, fieldValue, fieldType); + } + else if (fieldType.IsArray) + { + Log.Error($"Array serialization not yet implemented"); + } + else if (fieldType.IsGenericType) + { + if (fieldType.GetGenericTypeDefinition() == typeof(List<>)) + { + //SerializeList(fieldName, type, o); + Log.Error($"List serialization not yet implemented"); + } + //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 (fieldType.IsValueType) + { + SerializeStruct(fieldName, fieldValue, fieldType); + } + else if (fieldType.IsClass) + { + SerializeClass(fieldName, fieldValue, fieldType); + } + else + { + Log.Error($"Encountered unhandled type \"{fieldType}\" while serializing."); + } + } + + private void SerializePrimitive(string fieldName, object fieldValue, Type fieldType) + { + Debug.Assert(fieldType.IsPrimitive, $"{fieldType} is not a primitive type."); + + SerializationType type = SerializationType.None; + + if (fieldType == typeof(bool)) + type = SerializationType.Bool; + else if (fieldType == typeof(char)) + type = SerializationType.Char; + else if (fieldType == typeof(byte)) + type = SerializationType.UInt8; + else if (fieldType == typeof(sbyte)) + type = SerializationType.Int8; + else if (fieldType == typeof(ushort)) + type = SerializationType.UInt16; + else if (fieldType == typeof(short)) + type = SerializationType.Int16; + else if (fieldType == typeof(uint)) + type = SerializationType.UInt32; + else if (fieldType == typeof(int)) + type = SerializationType.Int32; + else if (fieldType == typeof(ulong)) + type = SerializationType.UInt64; + else if (fieldType == typeof(long)) + type = SerializationType.Int64; + else if (fieldType == typeof(float)) + type = SerializationType.Float; + else if (fieldType == typeof(double)) + type = SerializationType.Double; + else if (fieldType == typeof(decimal)) + type = SerializationType.Decimal; + else + { + Log.Error($"The primitive {fieldType} is not implemented."); + } + + AddField(fieldName, type, fieldValue); + } + + private void SerializeEnum(string fieldName, object fieldValue, Type fieldType) + { + AddField(fieldName, SerializationType.Enum, fieldValue.ToString()); + } + + private void SerializeStruct(string fieldName, object fieldValue, Type fieldType) + { + PushScope(fieldName); + + SerializeFields(fieldValue); + + PopScope(); + } + + private void SerializeClass(string fieldName, object fieldValue, Type fieldType) + { + if (typeof(Entity).IsAssignableFrom(fieldType)) + { + AddField(fieldName, SerializationType.EntityReference, ((Entity)fieldValue)?.UUID ?? UUID.Zero, fieldValue?.GetType().FullName); + } + else if (fieldType.IsSubclassOf(typeof(Component))) + { + AddField(fieldName, SerializationType.ComponentReference, ((Component)fieldValue)?.UUID ?? UUID.Zero, fieldValue?.GetType().FullName); + } + 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); + } + } + } +} \ No newline at end of file