From 16f4dd349bfaeb110563cc2dc578058711d4f298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Mon, 1 Jan 2024 13:03:34 +0100 Subject: [PATCH] Serialize and Deserialize Lists and 1D-Arrays --- .../Serialization/DeserializationObject.cs | 157 +++++++++++++----- ScriptCore/Serialization/SerializedObject.cs | 61 +++++-- 2 files changed, 168 insertions(+), 50 deletions(-) diff --git a/ScriptCore/Serialization/DeserializationObject.cs b/ScriptCore/Serialization/DeserializationObject.cs index e1e27a5..478ad77 100644 --- a/ScriptCore/Serialization/DeserializationObject.cs +++ b/ScriptCore/Serialization/DeserializationObject.cs @@ -1,8 +1,10 @@ using GlitchyEngine.Core; using GlitchyEngine.Extensions; using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.Tracing; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; @@ -82,20 +84,6 @@ internal class DeserializationObject DeserializedClasses.Add(id, context); - ScriptGlue.Serialization_GetObjectTypeName(context._internalContext, out string fullTypeName); - - Type type = GetTypeFromName(fullTypeName); - - if (type == null) - return context; - - context._instance = ActivatorExtension.CreateInstanceSafe(type); - - if (context._instance == null) - return context; - - context.DeserializeFields(context._instance); - return context; } @@ -127,6 +115,11 @@ internal class DeserializationObject public EngineObjectReferenceHelper EngineObjectReference; } + private T GetFieldValue(string fieldName, SerializationType serializationType) + { + return (T)GetFieldValue(fieldName, serializationType); + } + private unsafe object GetFieldValue(string fieldName, SerializationType serializationType) { string completeFieldName = $"{_structScopeName}{fieldName}"; @@ -218,40 +211,43 @@ internal class DeserializationObject if (!EntitySerializer.SerializeField(field)) continue; - changed |= DeserializeField(obj, field); + object currentValue = field.GetValue(obj); + + object deserializeValue = DeserializeField(currentValue, field.FieldType, field.Name); + + if (deserializeValue != NoValueDeserialized) + { + field.SetValue(obj, deserializeValue); + changed = true; + } } return changed; } - private bool DeserializeField(object targetInstance, FieldInfo field) + private object DeserializeField(object fieldValue, Type fieldType, string fieldName) { - Type fieldType = field.FieldType; - - object newFieldValue = NoValueDeserialized; - if (fieldType.IsPrimitive) { - newFieldValue = DeserializePrimitive(field.Name, fieldType); + return DeserializePrimitive(fieldName, fieldType); } else if (fieldType == typeof(string)) { - newFieldValue = GetFieldValue(field.Name, SerializationType.String); + return GetFieldValue(fieldName, SerializationType.String); } else if (fieldType.IsEnum) { - newFieldValue = DeserializeEnum(field.Name, fieldType); + return DeserializeEnum(fieldName, fieldType); } else if (fieldType.IsArray) { - // Log.Error($"Array serialization not yet implemented"); + return DeserializeList(fieldName, fieldType, fieldType.GetElementType()); } else if (fieldType.IsGenericType) { if (fieldType.GetGenericTypeDefinition() == typeof(List<>)) { - //SerializeList(fieldName, type, o); - Log.Error($"List serialization not yet implemented"); + return DeserializeList(fieldName, fieldType, fieldType.GetGenericArguments()[0]); } //else if (fieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) //{ @@ -265,26 +261,18 @@ internal class DeserializationObject } else if (fieldType.IsValueType) { - object structValue = field.GetValue(targetInstance); - - newFieldValue = DeserializeStruct(field.Name, structValue); + return DeserializeStruct(fieldName, fieldValue); } else if (fieldType.IsClass) - { - newFieldValue = DeserializeClass(field.Name, fieldType); + { + return DeserializeClass(fieldName, fieldType); } else { Log.Error($"Encountered unhandled type \"{fieldType}\" while serializing."); } - if (newFieldValue != NoValueDeserialized) - { - field.SetValue(targetInstance, newFieldValue); - return true; - } - - return false; + return NoValueDeserialized; } private object DeserializePrimitive(string fieldName, Type fieldType) @@ -327,6 +315,89 @@ internal class DeserializationObject return GetFieldValue(fieldName, expectedType); } + /// + /// Gets the type that was originally stored in the container, or null, if the type doesn't exist. + /// + public Type StoredType + { + get + { + ScriptGlue.Serialization_GetObjectTypeName(_internalContext, out string fullTypeName); + + return GetTypeFromName(fullTypeName); + } + } + + private object DeserializeList(string fieldName, Type fieldType, Type elementType) + { + UUID id = (UUID)GetFieldValue(fieldName, SerializationType.ObjectReference); + + if (id == UUID.Zero) + return null; + + // Get serialization container for the instance + DeserializationObject deserializedObject = GetDeserializedObject(id); + + Type type = deserializedObject.StoredType; + + if (type == null || !type.IsAssignableTo(fieldType)) + return null; + + int count = deserializedObject.GetFieldValue("Count", SerializationType.Int32); + + Array array = null; + IList list = null; + + if (type.IsArray) + { + array = Array.CreateInstance(elementType, count); + + deserializedObject._instance = array; + } + else + { + Debug.Assert(fieldType.IsAssignableTo(typeof(IList))); + + if (type.GetGenericTypeDefinition() == typeof(List<>)) + { + list = (IList)ActivatorExtension.CreateInstanceSafe(type, count); + } + else + { + list = (IList)ActivatorExtension.CreateInstanceSafe(type); + } + + if (list == null) + return null; + + if (list.IsFixedSize) + { + Log.Error("Cannot deserialize fixed length lists."); + + return null; + } + + deserializedObject._instance = list; + } + + for (int i = 0; i < count; i++) + { + object elementValue = ActivatorExtension.CreateInstanceSafe(elementType); + + object newValue = deserializedObject.DeserializeField(elementValue, elementType, $"{i}"); + + if (newValue == NoValueDeserialized) + newValue = elementValue; + + if (array != null) + array.SetValue(newValue, i); + else + list.Add(newValue); + } + + return deserializedObject._instance; + } + private object DeserializeEnum(string fieldName, Type enumType) { if (GetFieldValue(fieldName, SerializationType.Enum) is not string valueName) @@ -399,7 +470,17 @@ internal class DeserializationObject if (id == UUID.Zero) return null; + // Get serialization container for the instance DeserializationObject deserializedObject = GetDeserializedObject(id); + + Type type = deserializedObject.StoredType; + + if (type == null) + return null; + + deserializedObject._instance = ActivatorExtension.CreateInstanceSafe(type); + + deserializedObject.DeserializeFields(deserializedObject._instance); return deserializedObject._instance; } diff --git a/ScriptCore/Serialization/SerializedObject.cs b/ScriptCore/Serialization/SerializedObject.cs index 77f87f0..53ebeba 100644 --- a/ScriptCore/Serialization/SerializedObject.cs +++ b/ScriptCore/Serialization/SerializedObject.cs @@ -1,5 +1,6 @@ using GlitchyEngine.Core; using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; @@ -12,31 +13,31 @@ internal class SerializedObject private UUID _id; + private Dictionary _serializedClasses; + private Stack _structScope = new(); private string _structScopeName; - - public Dictionary SerializedClasses; - + public SerializedObject(IntPtr internalContext, UUID id, Dictionary serializedClasses) { _internalContext = internalContext; _id = id; - SerializedClasses = serializedClasses; + _serializedClasses = serializedClasses; } - + private (SerializedObject context, bool newContext) GetSerializedObject(object o) { SerializedObject context; - if (SerializedClasses.TryGetValue(o, out 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); + context = new SerializedObject(contextPtr, id, _serializedClasses); - SerializedClasses.Add(o, context); + _serializedClasses.Add(o, context); return (context, true); } @@ -98,14 +99,20 @@ internal class SerializedObject } else if (fieldType.IsArray) { - Log.Error($"Array serialization not yet implemented"); + Array myArray = fieldValue as Array; + + if (myArray?.Rank > 1) + { + throw new NotImplementedException("Serializing multidimensional arrays is not yet supported."); + } + + SerializeList(fieldName, fieldValue, fieldType, fieldType.GetElementType()); } else if (fieldType.IsGenericType) { if (fieldType.GetGenericTypeDefinition() == typeof(List<>)) { - //SerializeList(fieldName, type, o); - Log.Error($"List serialization not yet implemented"); + SerializeList(fieldName, fieldValue, fieldType, fieldType.GetGenericArguments()[0]); } //else if (fieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) //{ @@ -131,6 +138,36 @@ internal class SerializedObject } } + private void SerializeList(string fieldName, object listObject, Type fieldType, Type elementType) + { + if (listObject == null) + { + AddField(fieldName, SerializationType.ObjectReference, UUID.Zero); + } + else + { + Debug.Assert(listObject is IList); + + var (context, newContext) = GetSerializedObject(listObject); + + if (newContext) + { + IList list = (IList)listObject; + + context.AddField("Count", SerializationType.Int32, list.Count); + + for (int i = 0; i < list.Count; i++) + { + object element = list[i]; + + context.SerializeField($"{i}", element, element?.GetType() ?? elementType); + } + } + + AddField(fieldName, SerializationType.ObjectReference, context._id); + } + } + private void SerializePrimitive(string fieldName, object fieldValue, Type fieldType) { Debug.Assert(fieldType.IsPrimitive, $"{fieldType} is not a primitive type."); @@ -214,4 +251,4 @@ internal class SerializedObject } } } -} \ No newline at end of file +}