mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
343 lines
11 KiB
C#
343 lines
11 KiB
C#
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<object, SerializedObject> _serializedClasses;
|
|
|
|
private Stack<string> _structScope = new();
|
|
|
|
private string _structScopeName = "";
|
|
|
|
private bool _isStatic;
|
|
|
|
private delegate void SerializeMethod(SerializedObject container, string fieldName, object? fieldValue, Type fieldType);
|
|
|
|
private static Dictionary<Type, SerializeMethod> _customSerializers = new();
|
|
|
|
public UUID Id => _id;
|
|
|
|
public bool IsStatic => _isStatic;
|
|
|
|
public SerializedObject(IntPtr internalContext, bool isStatic, UUID id, Dictionary<object, SerializedObject> 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<CustomSerializerAttribute>(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<SerializeMethod>();
|
|
|
|
_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<Bar> -> List<Bar>)
|
|
// Note: Foo<Bar> 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 (fieldType.IsSubclassOf(typeof(EngineObject)))
|
|
{
|
|
AddField(fieldName, SerializationType.EngineObjectReference, ((EngineObject?)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);
|
|
}
|
|
}
|
|
}
|
|
}
|