mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 21:01:52 +00:00
Custom Serializers
- Dictionary serializer
This commit is contained in:
@@ -296,7 +296,7 @@ internal class EntityEditor
|
|||||||
|
|
||||||
if (ImGui.BeginCombo("Create", "Create", ImGuiComboFlags.HeightSmall | ImGuiComboFlags.NoArrowButton))
|
if (ImGui.BeginCombo("Create", "Create", ImGuiComboFlags.HeightSmall | ImGuiComboFlags.NoArrowButton))
|
||||||
{
|
{
|
||||||
foreach (Type t in FindDerivedTypes(fieldType))
|
foreach (Type t in TypeExtension.FindDerivedTypes(fieldType))
|
||||||
{
|
{
|
||||||
if (ImGui.Selectable(t.Name))
|
if (ImGui.Selectable(t.Name))
|
||||||
{
|
{
|
||||||
@@ -818,20 +818,6 @@ internal class EntityEditor
|
|||||||
return newList;
|
return newList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enumerates all types that derive from the given type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="baseType"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static IEnumerable<Type> FindDerivedTypes(Type baseType)
|
|
||||||
{
|
|
||||||
foreach (Assembly domainAssembly in AppDomain.CurrentDomain.GetAssemblies())
|
|
||||||
foreach (Type type in domainAssembly.GetTypes())
|
|
||||||
{
|
|
||||||
if (baseType.IsAssignableFrom(type) && !type.IsAbstract) yield return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static T ReadStaticField<T>(string name)
|
private static T ReadStaticField<T>(string name)
|
||||||
{
|
{
|
||||||
FieldInfo field = typeof(T).GetField(name, BindingFlags.Public | BindingFlags.Static);
|
FieldInfo field = typeof(T).GetField(name, BindingFlags.Public | BindingFlags.Static);
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.Extensions;
|
||||||
|
|
||||||
|
public static class KeyValuePairExtension
|
||||||
|
{
|
||||||
|
public static void Deconstruct<T1, T2>(this KeyValuePair<T1, T2> tuple, out T1 key, out T2 value)
|
||||||
|
{
|
||||||
|
key = tuple.Key;
|
||||||
|
value = tuple.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace GlitchyEngine.Extensions;
|
namespace GlitchyEngine.Extensions;
|
||||||
@@ -15,4 +16,53 @@ public static class TypeExtension
|
|||||||
{
|
{
|
||||||
return targetType.IsAssignableFrom(type);
|
return targetType.IsAssignableFrom(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates all types in all assemblies.
|
||||||
|
/// </summary>
|
||||||
|
public static IEnumerable<Type> EnumerateAllTypes()
|
||||||
|
{
|
||||||
|
foreach (Assembly domainAssembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
|
foreach (Type type in domainAssembly.GetTypes())
|
||||||
|
{
|
||||||
|
yield return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates all types that derive from the given type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IEnumerable<Type> FindDerivedTypes(Type baseType)
|
||||||
|
{
|
||||||
|
foreach (Assembly domainAssembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
|
foreach (Type type in domainAssembly.GetTypes())
|
||||||
|
{
|
||||||
|
if (baseType.IsAssignableFrom(type) && !type.IsAbstract) yield return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether or not the type has an <see cref="Attribute"/> of the specified <see cref="Type"/> <see cref="T"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the attribute</typeparam>
|
||||||
|
/// <returns><see langword="true"/> if the type has the specified <see cref="Attribute"/>; <see langword="true"/> otherwise.</returns>
|
||||||
|
public static bool HasCustomAttribute<T>(this Type type) where T: Attribute
|
||||||
|
{
|
||||||
|
return type.GetCustomAttribute<T>() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether or not the type has an <see cref="Attribute"/> of the specified <see cref="Type"/> <see cref="T"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the attribute</typeparam>
|
||||||
|
/// <param name="attribute">The attribute, or <seealso langword="null"/>, if the type hasn't got the attribute specified.</param>
|
||||||
|
/// <returns><see langword="true"/> if the type has the specified <see cref="Attribute"/>; <see langword="true"/> otherwise.</returns>
|
||||||
|
public static bool TryGetCustomAttribute<T>(this Type type, out T attribute) where T: Attribute
|
||||||
|
{
|
||||||
|
attribute = type.GetCustomAttribute<T>();
|
||||||
|
|
||||||
|
return attribute != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.Serialization;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
|
||||||
|
public sealed class CustomSerializerAttribute : Attribute
|
||||||
|
{
|
||||||
|
public Type Type { get; private set; }
|
||||||
|
|
||||||
|
public CustomSerializerAttribute(Type type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,12 +10,13 @@ using System.Reflection;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using GlitchyEngine.Editor;
|
||||||
|
|
||||||
namespace GlitchyEngine.Serialization;
|
namespace GlitchyEngine.Serialization;
|
||||||
|
|
||||||
internal class DeserializationObject
|
public class DeserializationObject
|
||||||
{
|
{
|
||||||
private class NoObject
|
public class NoObject
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -23,7 +24,7 @@ internal class DeserializationObject
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Object used to specify, that no particular value was deserialized for the field and thus the value currently stored shall not be changed.
|
/// Object used to specify, that no particular value was deserialized for the field and thus the value currently stored shall not be changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly NoObject NoValueDeserialized = new ();
|
public static readonly NoObject NoValueDeserialized = new ();
|
||||||
|
|
||||||
private IntPtr _internalContext;
|
private IntPtr _internalContext;
|
||||||
|
|
||||||
@@ -37,6 +38,24 @@ internal class DeserializationObject
|
|||||||
|
|
||||||
private object _instance;
|
private object _instance;
|
||||||
|
|
||||||
|
private Dictionary<string, Type> _fullNameToType = new();
|
||||||
|
|
||||||
|
private static Dictionary<Type, MethodInfo> _customDeserializers = new();
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type that was originally stored in the container, or null, if the type doesn't exist.
|
||||||
|
/// </summary>
|
||||||
|
public Type StoredType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
ScriptGlue.Serialization_GetObjectTypeName(_internalContext, out string fullTypeName);
|
||||||
|
|
||||||
|
return GetTypeFromName(fullTypeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public DeserializationObject(IntPtr internalContext, UUID id, Dictionary<UUID, DeserializationObject> deserializedClasses)
|
public DeserializationObject(IntPtr internalContext, UUID id, Dictionary<UUID, DeserializationObject> deserializedClasses)
|
||||||
{
|
{
|
||||||
_internalContext = internalContext;
|
_internalContext = internalContext;
|
||||||
@@ -44,9 +63,29 @@ internal class DeserializationObject
|
|||||||
DeserializedClasses = deserializedClasses;
|
DeserializedClasses = deserializedClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<string, Type> _fullNameToType = new();
|
static DeserializationObject()
|
||||||
|
{
|
||||||
|
foreach (Type type in TypeExtension.EnumerateAllTypes())
|
||||||
|
{
|
||||||
|
if (type.TryGetCustomAttribute<CustomSerializerAttribute>(out var attribute))
|
||||||
|
{
|
||||||
|
MethodInfo deserializeMethod = type.GetMethod("Deserialize", BindingFlags.Static | BindingFlags.Public,
|
||||||
|
null,
|
||||||
|
new []{ typeof(DeserializationObject), typeof(string), typeof(Type) }, null);
|
||||||
|
|
||||||
private Type FindType(string fullName)
|
if (deserializeMethod == null)
|
||||||
|
{
|
||||||
|
Log.Error($"No Deserialize-method found for type {type}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_customDeserializers.Add(attribute.Type, deserializeMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type FindType(string fullName)
|
||||||
{
|
{
|
||||||
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies().Reverse())
|
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies().Reverse())
|
||||||
{
|
{
|
||||||
@@ -59,7 +98,7 @@ internal class DeserializationObject
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Type GetTypeFromName(string fullName)
|
public Type GetTypeFromName(string fullName)
|
||||||
{
|
{
|
||||||
if (_fullNameToType.TryGetValue(fullName, out Type storedType))
|
if (_fullNameToType.TryGetValue(fullName, out Type storedType))
|
||||||
return storedType;
|
return storedType;
|
||||||
@@ -71,7 +110,7 @@ internal class DeserializationObject
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DeserializationObject GetDeserializedObject(UUID id)
|
public DeserializationObject GetDeserializedObject(UUID id)
|
||||||
{
|
{
|
||||||
DeserializationObject context;
|
DeserializationObject context;
|
||||||
|
|
||||||
@@ -87,14 +126,14 @@ internal class DeserializationObject
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PushScope(string name)
|
public void PushScope(string name)
|
||||||
{
|
{
|
||||||
_structScope.Push(name);
|
_structScope.Push(name);
|
||||||
|
|
||||||
_structScopeName += $"{name}.";
|
_structScopeName += $"{name}.";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PopScope()
|
public void PopScope()
|
||||||
{
|
{
|
||||||
string scopeToRemove = _structScope.Pop();
|
string scopeToRemove = _structScope.Pop();
|
||||||
_structScopeName = _structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1);
|
_structScopeName = _structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1);
|
||||||
@@ -115,12 +154,12 @@ internal class DeserializationObject
|
|||||||
public EngineObjectReferenceHelper EngineObjectReference;
|
public EngineObjectReferenceHelper EngineObjectReference;
|
||||||
}
|
}
|
||||||
|
|
||||||
private T GetFieldValue<T>(string fieldName, SerializationType serializationType)
|
public T GetFieldValue<T>(string fieldName, SerializationType serializationType)
|
||||||
{
|
{
|
||||||
return (T)GetFieldValue(fieldName, serializationType);
|
return (T)GetFieldValue(fieldName, serializationType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe object GetFieldValue(string fieldName, SerializationType serializationType)
|
public unsafe object GetFieldValue(string fieldName, SerializationType serializationType)
|
||||||
{
|
{
|
||||||
string completeFieldName = $"{_structScopeName}{fieldName}";
|
string completeFieldName = $"{_structScopeName}{fieldName}";
|
||||||
|
|
||||||
@@ -200,7 +239,7 @@ internal class DeserializationObject
|
|||||||
DeserializeFields(entity);
|
DeserializeFields(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool DeserializeFields(object obj)
|
public bool DeserializeFields(object obj)
|
||||||
{
|
{
|
||||||
Type type = obj.GetType();
|
Type type = obj.GetType();
|
||||||
|
|
||||||
@@ -225,7 +264,34 @@ internal class DeserializationObject
|
|||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private object DeserializeField(object fieldValue, Type fieldType, string fieldName)
|
private bool TryCustomDeserializer(string fieldName, Type fieldType, out object deserializedValue)
|
||||||
|
{
|
||||||
|
deserializedValue = NoValueDeserialized;
|
||||||
|
|
||||||
|
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 (_customDeserializers.TryGetValue(fieldType, out MethodInfo deserializeMethod))
|
||||||
|
{
|
||||||
|
deserializedValue = deserializeMethod.Invoke(null, new object[] { this, fieldName, fieldType });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldType.IsGenericType && _customDeserializers.TryGetValue(fieldType.GetGenericTypeDefinition(), out deserializeMethod))
|
||||||
|
{
|
||||||
|
deserializedValue = deserializeMethod.Invoke(null, new object[] { this, fieldName, fieldType });
|
||||||
|
} return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object DeserializeField(object fieldValue, Type fieldType, string fieldName)
|
||||||
{
|
{
|
||||||
if (fieldType.IsPrimitive)
|
if (fieldType.IsPrimitive)
|
||||||
{
|
{
|
||||||
@@ -245,6 +311,11 @@ internal class DeserializationObject
|
|||||||
}
|
}
|
||||||
else if (fieldType.IsGenericType)
|
else if (fieldType.IsGenericType)
|
||||||
{
|
{
|
||||||
|
if (TryCustomDeserializer(fieldName, fieldType, out object deserializedValue))
|
||||||
|
{
|
||||||
|
return deserializedValue;
|
||||||
|
}
|
||||||
|
|
||||||
if (fieldType.GetGenericTypeDefinition() == typeof(List<>))
|
if (fieldType.GetGenericTypeDefinition() == typeof(List<>))
|
||||||
{
|
{
|
||||||
return DeserializeList(fieldName, fieldType, fieldType.GetGenericArguments()[0]);
|
return DeserializeList(fieldName, fieldType, fieldType.GetGenericArguments()[0]);
|
||||||
@@ -261,10 +332,20 @@ internal class DeserializationObject
|
|||||||
}
|
}
|
||||||
else if (fieldType.IsValueType)
|
else if (fieldType.IsValueType)
|
||||||
{
|
{
|
||||||
|
if (TryCustomDeserializer(fieldName, fieldType, out object deserializedValue))
|
||||||
|
{
|
||||||
|
return deserializedValue;
|
||||||
|
}
|
||||||
|
|
||||||
return DeserializeStruct(fieldName, fieldValue);
|
return DeserializeStruct(fieldName, fieldValue);
|
||||||
}
|
}
|
||||||
else if (fieldType.IsClass)
|
else if (fieldType.IsClass)
|
||||||
{
|
{
|
||||||
|
if (TryCustomDeserializer(fieldName, fieldType, out object deserializedValue))
|
||||||
|
{
|
||||||
|
return deserializedValue;
|
||||||
|
}
|
||||||
|
|
||||||
return DeserializeClass(fieldName, fieldType);
|
return DeserializeClass(fieldName, fieldType);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -275,7 +356,7 @@ internal class DeserializationObject
|
|||||||
return NoValueDeserialized;
|
return NoValueDeserialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
private object DeserializePrimitive(string fieldName, Type fieldType)
|
public object DeserializePrimitive(string fieldName, Type fieldType)
|
||||||
{
|
{
|
||||||
Debug.Assert(fieldType.IsPrimitive, $"{fieldType} is not a primitive type.");
|
Debug.Assert(fieldType.IsPrimitive, $"{fieldType} is not a primitive type.");
|
||||||
|
|
||||||
@@ -315,20 +396,7 @@ internal class DeserializationObject
|
|||||||
return GetFieldValue(fieldName, expectedType);
|
return GetFieldValue(fieldName, expectedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public object DeserializeList(string fieldName, Type fieldType, Type elementType)
|
||||||
/// Gets the type that was originally stored in the container, or null, if the type doesn't exist.
|
|
||||||
/// </summary>
|
|
||||||
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);
|
UUID id = (UUID)GetFieldValue(fieldName, SerializationType.ObjectReference);
|
||||||
|
|
||||||
@@ -398,7 +466,7 @@ internal class DeserializationObject
|
|||||||
return deserializedObject._instance;
|
return deserializedObject._instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private object DeserializeEnum(string fieldName, Type enumType)
|
public object DeserializeEnum(string fieldName, Type enumType)
|
||||||
{
|
{
|
||||||
if (GetFieldValue(fieldName, SerializationType.Enum) is not string valueName)
|
if (GetFieldValue(fieldName, SerializationType.Enum) is not string valueName)
|
||||||
return NoValueDeserialized;
|
return NoValueDeserialized;
|
||||||
@@ -415,7 +483,7 @@ internal class DeserializationObject
|
|||||||
return NoValueDeserialized;
|
return NoValueDeserialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
private object DeserializeStruct(string fieldName, object targetInstance)
|
public object DeserializeStruct(string fieldName, object targetInstance)
|
||||||
{
|
{
|
||||||
PushScope(fieldName);
|
PushScope(fieldName);
|
||||||
|
|
||||||
@@ -426,7 +494,7 @@ internal class DeserializationObject
|
|||||||
return changed ? targetInstance : NoValueDeserialized;
|
return changed ? targetInstance : NoValueDeserialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe object DeserializeClass(string fieldName, Type fieldType)
|
public unsafe object DeserializeClass(string fieldName, Type fieldType)
|
||||||
{
|
{
|
||||||
bool isEntity = typeof(Entity).IsAssignableFrom(fieldType);
|
bool isEntity = typeof(Entity).IsAssignableFrom(fieldType);
|
||||||
bool isComponent = fieldType.IsSubclassOf(typeof(Component));
|
bool isComponent = fieldType.IsSubclassOf(typeof(Component));
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using GlitchyEngine.Core;
|
||||||
|
using GlitchyEngine.Extensions;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides Serialization and Deserialization for Dictionaries.
|
||||||
|
/// </summary>
|
||||||
|
[CustomSerializer(typeof(Dictionary<,>))]
|
||||||
|
public static class DictionarySerializer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes a given Dictionary as a field of <see cref="container"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="container">The parent object.</param>
|
||||||
|
/// <param name="fieldName">Name of the field in <see cref="container"/></param>
|
||||||
|
/// <param name="fieldValue">The dictionary</param>
|
||||||
|
/// <param name="fieldType">Type of <see cref="fieldValue"/> or the type of the field if <see cref="fieldValue"/> is <see langword="null"/></param>
|
||||||
|
public static void Serialize(SerializedObject container, string fieldName, object? fieldValue, Type fieldType)
|
||||||
|
{
|
||||||
|
if (fieldValue == null)
|
||||||
|
{
|
||||||
|
// Write a null pointer early out
|
||||||
|
container.AddField(fieldName, SerializationType.ObjectReference, UUID.Zero);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a container for our dictionary (Note: container is our parent object).
|
||||||
|
// isNewContext is true, if our dictionary wasn't serialized so far.
|
||||||
|
var (context, isNewContext) = container.GetSerializedObject(fieldValue);
|
||||||
|
|
||||||
|
if (isNewContext)
|
||||||
|
{
|
||||||
|
Type keyType = fieldType.GetGenericArguments()[0];
|
||||||
|
Type valueType = fieldType.GetGenericArguments()[1];
|
||||||
|
|
||||||
|
IDictionary dictionary = (IDictionary)fieldValue;
|
||||||
|
ICollection collection = (ICollection)fieldValue;
|
||||||
|
|
||||||
|
context.AddField("Count", SerializationType.Int32, collection.Count);
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
foreach (DictionaryEntry entry in dictionary)
|
||||||
|
{
|
||||||
|
context.PushScope(index.ToString());
|
||||||
|
|
||||||
|
context.SerializeField("Key", entry.Key, keyType);
|
||||||
|
context.SerializeField("Value", entry.Value, valueType);
|
||||||
|
|
||||||
|
context.PopScope();
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the reference to our dictionary into the field of the parent.
|
||||||
|
container.AddField(fieldName, SerializationType.ObjectReference, context.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deserializes a dictionary that is a field of the given <see cref="container"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="container">The parent object whose field with <see cref="fieldName"/> is a dictionary.</param>
|
||||||
|
/// <param name="fieldName">The name of the field.</param>
|
||||||
|
/// <param name="fieldType">The type of the field.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static object? Deserialize(DeserializationObject container, string fieldName, Type fieldType)
|
||||||
|
{
|
||||||
|
UUID id = container.GetFieldValue<UUID>(fieldName, SerializationType.ObjectReference);
|
||||||
|
|
||||||
|
if (id == UUID.Zero)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Get serialization container for the instance
|
||||||
|
DeserializationObject deserializedObject = container.GetDeserializedObject(id);
|
||||||
|
|
||||||
|
Type type = deserializedObject.StoredType;
|
||||||
|
|
||||||
|
if (type?.IsAssignableTo(fieldType) != true)
|
||||||
|
return DeserializationObject.NoValueDeserialized;
|
||||||
|
|
||||||
|
Type keyType = fieldType.GetGenericArguments()[0];
|
||||||
|
Type valueType = fieldType.GetGenericArguments()[1];
|
||||||
|
|
||||||
|
int count = deserializedObject.GetFieldValue<int>("Count", SerializationType.Int32);
|
||||||
|
|
||||||
|
// Create instance, pass in capacity
|
||||||
|
object? instance = ActivatorExtension.CreateInstanceSafe(type, count);
|
||||||
|
|
||||||
|
Debug.Assert(instance != null);
|
||||||
|
|
||||||
|
IDictionary dictionary = (IDictionary)instance!;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
deserializedObject.PushScope(i.ToString());
|
||||||
|
|
||||||
|
object? key = deserializedObject.DeserializeField(null, keyType, "Key");
|
||||||
|
object value = deserializedObject.DeserializeField(null, valueType, "Value");
|
||||||
|
|
||||||
|
if (key != null)
|
||||||
|
{
|
||||||
|
dictionary.Add(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializedObject.PopScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ using GlitchyEngine.Extensions;
|
|||||||
|
|
||||||
namespace GlitchyEngine.Serialization;
|
namespace GlitchyEngine.Serialization;
|
||||||
|
|
||||||
internal enum SerializationType : int
|
public enum SerializationType : int
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using GlitchyEngine.Extensions;
|
||||||
|
|
||||||
namespace GlitchyEngine.Serialization;
|
namespace GlitchyEngine.Serialization;
|
||||||
|
|
||||||
internal class SerializedObject
|
public class SerializedObject
|
||||||
{
|
{
|
||||||
private IntPtr _internalContext;
|
private IntPtr _internalContext;
|
||||||
|
|
||||||
@@ -19,6 +20,10 @@ internal class SerializedObject
|
|||||||
|
|
||||||
private string _structScopeName;
|
private string _structScopeName;
|
||||||
|
|
||||||
|
private static Dictionary<Type, MethodInfo> _customSerializers = new();
|
||||||
|
|
||||||
|
public UUID Id => _id;
|
||||||
|
|
||||||
public SerializedObject(IntPtr internalContext, UUID id, Dictionary<object, SerializedObject> serializedClasses)
|
public SerializedObject(IntPtr internalContext, UUID id, Dictionary<object, SerializedObject> serializedClasses)
|
||||||
{
|
{
|
||||||
_internalContext = internalContext;
|
_internalContext = internalContext;
|
||||||
@@ -26,7 +31,7 @@ internal class SerializedObject
|
|||||||
_serializedClasses = serializedClasses;
|
_serializedClasses = serializedClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
private (SerializedObject context, bool newContext) GetSerializedObject(object o)
|
public (SerializedObject context, bool newContext) GetSerializedObject(object o)
|
||||||
{
|
{
|
||||||
SerializedObject context;
|
SerializedObject context;
|
||||||
|
|
||||||
@@ -42,20 +47,42 @@ internal class SerializedObject
|
|||||||
return (context, true);
|
return (context, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PushScope(string name)
|
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
|
||||||
|
{
|
||||||
|
_customSerializers.Add(attribute.Type, serializeMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PushScope(string name)
|
||||||
{
|
{
|
||||||
_structScope.Push(name);
|
_structScope.Push(name);
|
||||||
|
|
||||||
_structScopeName += $"{name}.";
|
_structScopeName += $"{name}.";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PopScope()
|
public void PopScope()
|
||||||
{
|
{
|
||||||
string scopeToRemove = _structScope.Pop();
|
string scopeToRemove = _structScope.Pop();
|
||||||
_structScopeName = _structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1);
|
_structScopeName = _structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddField(string fieldName, SerializationType serializationType, object value, string fullTypeName = null)
|
public void AddField(string fieldName, SerializationType serializationType, object value, string fullTypeName = null)
|
||||||
{
|
{
|
||||||
string completeFieldName = $"{_structScopeName}{fieldName}";
|
string completeFieldName = $"{_structScopeName}{fieldName}";
|
||||||
|
|
||||||
@@ -67,7 +94,7 @@ internal class SerializedObject
|
|||||||
SerializeFields(entity);
|
SerializeFields(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SerializeFields(object obj)
|
public void SerializeFields(object obj)
|
||||||
{
|
{
|
||||||
Type type = obj.GetType();
|
Type type = obj.GetType();
|
||||||
|
|
||||||
@@ -83,7 +110,35 @@ internal class SerializedObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SerializeField(string fieldName, object fieldValue, Type 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 MethodInfo serializeMethod))
|
||||||
|
{
|
||||||
|
serializeMethod.Invoke(null, new[] { this, fieldName, fieldValue, fieldType });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldType.IsGenericType && _customSerializers.TryGetValue(fieldType.GetGenericTypeDefinition(), out serializeMethod))
|
||||||
|
{
|
||||||
|
serializeMethod.Invoke(null, new[] { this, fieldName, fieldValue, fieldType });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(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)
|
if (fieldType.IsPrimitive)
|
||||||
{
|
{
|
||||||
@@ -110,6 +165,9 @@ internal class SerializedObject
|
|||||||
}
|
}
|
||||||
else if (fieldType.IsGenericType)
|
else if (fieldType.IsGenericType)
|
||||||
{
|
{
|
||||||
|
if (TryCustomSerializer(fieldName, fieldValue, fieldType))
|
||||||
|
return;
|
||||||
|
|
||||||
if (fieldType.GetGenericTypeDefinition() == typeof(List<>))
|
if (fieldType.GetGenericTypeDefinition() == typeof(List<>))
|
||||||
{
|
{
|
||||||
SerializeList(fieldName, fieldValue, fieldType, fieldType.GetGenericArguments()[0]);
|
SerializeList(fieldName, fieldValue, fieldType, fieldType.GetGenericArguments()[0]);
|
||||||
@@ -126,10 +184,16 @@ internal class SerializedObject
|
|||||||
}
|
}
|
||||||
else if (fieldType.IsValueType)
|
else if (fieldType.IsValueType)
|
||||||
{
|
{
|
||||||
|
if (TryCustomSerializer(fieldName, fieldValue, fieldType))
|
||||||
|
return;
|
||||||
|
|
||||||
SerializeStruct(fieldName, fieldValue, fieldType);
|
SerializeStruct(fieldName, fieldValue, fieldType);
|
||||||
}
|
}
|
||||||
else if (fieldType.IsClass)
|
else if (fieldType.IsClass)
|
||||||
{
|
{
|
||||||
|
if (TryCustomSerializer(fieldName, fieldValue, fieldType))
|
||||||
|
return;
|
||||||
|
|
||||||
SerializeClass(fieldName, fieldValue, fieldType);
|
SerializeClass(fieldName, fieldValue, fieldType);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -138,7 +202,7 @@ internal class SerializedObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SerializeList(string fieldName, object listObject, Type fieldType, Type elementType)
|
public void SerializeList(string fieldName, object listObject, Type fieldType, Type elementType)
|
||||||
{
|
{
|
||||||
if (listObject == null)
|
if (listObject == null)
|
||||||
{
|
{
|
||||||
@@ -168,7 +232,7 @@ internal class SerializedObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SerializePrimitive(string fieldName, object fieldValue, Type fieldType)
|
public void SerializePrimitive(string fieldName, object fieldValue, Type fieldType)
|
||||||
{
|
{
|
||||||
Debug.Assert(fieldType.IsPrimitive, $"{fieldType} is not a primitive type.");
|
Debug.Assert(fieldType.IsPrimitive, $"{fieldType} is not a primitive type.");
|
||||||
|
|
||||||
@@ -208,12 +272,12 @@ internal class SerializedObject
|
|||||||
AddField(fieldName, type, fieldValue);
|
AddField(fieldName, type, fieldValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SerializeEnum(string fieldName, object fieldValue, Type fieldType)
|
public void SerializeEnum(string fieldName, object fieldValue, Type fieldType)
|
||||||
{
|
{
|
||||||
AddField(fieldName, SerializationType.Enum, fieldValue.ToString());
|
AddField(fieldName, SerializationType.Enum, fieldValue.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SerializeStruct(string fieldName, object fieldValue, Type fieldType)
|
public void SerializeStruct(string fieldName, object fieldValue, Type fieldType)
|
||||||
{
|
{
|
||||||
PushScope(fieldName);
|
PushScope(fieldName);
|
||||||
|
|
||||||
@@ -222,7 +286,7 @@ internal class SerializedObject
|
|||||||
PopScope();
|
PopScope();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SerializeClass(string fieldName, object fieldValue, Type fieldType)
|
public void SerializeClass(string fieldName, object fieldValue, Type fieldType)
|
||||||
{
|
{
|
||||||
if (typeof(Entity).IsAssignableFrom(fieldType))
|
if (typeof(Entity).IsAssignableFrom(fieldType))
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user