using GlitchyEngine.Core; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using GlitchyEngine.Extensions; namespace GlitchyEngine.Serialization; public class SerializedObject { private IntPtr _internalContext; private UUID _id; private Dictionary _serializedClasses; private Stack _structScope = new(); private string _structScopeName = ""; private bool _isStatic; private delegate void SerializeMethod(SerializedObject container, string fieldName, object? fieldValue, Type fieldType); private static Dictionary _customSerializers = new(); public UUID Id => _id; public bool IsStatic => _isStatic; public SerializedObject(IntPtr internalContext, bool isStatic, UUID id, Dictionary serializedClasses) { _internalContext = internalContext; _isStatic = isStatic; _id = id; _serializedClasses = serializedClasses; } public (SerializedObject context, bool newContext) GetSerializedObject(object o) { SerializedObject context; if (_serializedClasses.TryGetValue(o, out context)) return (context, false); ScriptGlue.Serialization_CreateObject(_internalContext, false, o.GetType().ToString(), out IntPtr contextPtr, out UUID id); context = new SerializedObject(contextPtr, false, id, _serializedClasses); _serializedClasses.Add(o, context); return (context, true); } static SerializedObject() { foreach (Type type in TypeExtension.EnumerateAllTypes()) { if (type.TryGetCustomAttribute(out var attribute)) { MethodInfo? serializeMethod = type.GetMethod("Serialize", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(SerializedObject), typeof(string), typeof(object), typeof(Type) }, null); if (serializeMethod == null) { Log.Error($"No Serialize-method found for type {type}"); } else { SerializeMethod method = serializeMethod.GetDelegate(); _customSerializers.Add(attribute.Type, method); } } } } public void PushScope(string name) { _structScope.Push(name); _structScopeName += $"{name}."; } public void PopScope() { string scopeToRemove = _structScope.Pop(); _structScopeName = _structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1); } public 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) { SerializeInstanceFields(entity); } public void Serialize(Type type) { SerializeStaticTypeFields(type); } public void SerializeInstanceFields(object obj) { Type type = obj.GetType(); foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) { if (!EntitySerializer.SerializeField(field, false)) continue; object? fieldValue = field.GetValue(obj); Type fieldType = fieldValue?.GetType() ?? field.FieldType; SerializeField(field.Name, fieldValue, fieldType); } } public void SerializeStaticTypeFields(Type type) { foreach (FieldInfo field in type.GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)) { if (!EntitySerializer.SerializeField(field, true)) continue; object? fieldValue = field.GetValue(null); Type fieldType = fieldValue?.GetType() ?? field.FieldType; SerializeField(field.Name, fieldValue, fieldType); } } private bool TryCustomSerializer(string fieldName, object? fieldValue, Type fieldType) { try { // Try to match the concrete type first (e.g. Foo -> Foo and Foo -> List) // Note: Foo wont match a serializer for Foo<> if (_customSerializers.TryGetValue(fieldType, out SerializeMethod serializeMethod)) { serializeMethod(this, fieldName, fieldValue, fieldType); return true; } if (fieldType.IsGenericType && _customSerializers.TryGetValue(fieldType.GetGenericTypeDefinition(), out serializeMethod)) { serializeMethod(this, fieldName, fieldValue, fieldType); return true; } } catch (Exception e) { Log.Exception(e); // Don't attempt to use any other serializer after this error... return true; } return false; } public void SerializeField(string fieldName, object? fieldValue, Type fieldType) { if (fieldType.IsPrimitive || fieldType == typeof(decimal)) { // Primitives can't be null 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) { 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 (TryCustomSerializer(fieldName, fieldValue, fieldType)) return; if (fieldType.GetGenericTypeDefinition() == typeof(List<>)) { SerializeList(fieldName, fieldValue, fieldType, fieldType.GetGenericArguments()[0]); } //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) { if (TryCustomSerializer(fieldName, fieldValue, fieldType)) return; // Value Types cant be null SerializeStruct(fieldName, fieldValue!, fieldType); } else if (fieldType.IsClass) { if (TryCustomSerializer(fieldName, fieldValue, fieldType)) return; SerializeClass(fieldName, fieldValue, fieldType); } else { Log.Error($"Encountered unhandled type \"{fieldType}\" while serializing."); } } public 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); } } public void SerializePrimitive(string fieldName, object fieldValue, Type fieldType) { 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); } public void SerializeEnum(string fieldName, object? fieldValue, Type fieldType) { AddField(fieldName, SerializationType.Enum, fieldValue?.ToString()); } public void SerializeStruct(string fieldName, object fieldValue, Type fieldType) { PushScope(fieldName); SerializeInstanceFields(fieldValue); PopScope(); } public 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.SerializeInstanceFields(fieldValue); } AddField(fieldName, SerializationType.ObjectReference, context._id); } } } }