diff --git a/ScriptCore/Editor/EntityEditor.cs b/ScriptCore/Editor/EntityEditor.cs
index dc7b0e7..7af0d12 100644
--- a/ScriptCore/Editor/EntityEditor.cs
+++ b/ScriptCore/Editor/EntityEditor.cs
@@ -296,7 +296,7 @@ internal class EntityEditor
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))
{
@@ -818,20 +818,6 @@ internal class EntityEditor
return newList;
}
- ///
- /// Enumerates all types that derive from the given type.
- ///
- ///
- ///
- public static IEnumerable 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(string name)
{
FieldInfo field = typeof(T).GetField(name, BindingFlags.Public | BindingFlags.Static);
diff --git a/ScriptCore/Extensions/KeyValuePairExtension.cs b/ScriptCore/Extensions/KeyValuePairExtension.cs
new file mode 100644
index 0000000..2fbc0b7
--- /dev/null
+++ b/ScriptCore/Extensions/KeyValuePairExtension.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace GlitchyEngine.Extensions;
+
+public static class KeyValuePairExtension
+{
+ public static void Deconstruct(this KeyValuePair tuple, out T1 key, out T2 value)
+ {
+ key = tuple.Key;
+ value = tuple.Value;
+ }
+}
diff --git a/ScriptCore/Extensions/TypeExtension.cs b/ScriptCore/Extensions/TypeExtension.cs
index b6fc91a..7f538cd 100644
--- a/ScriptCore/Extensions/TypeExtension.cs
+++ b/ScriptCore/Extensions/TypeExtension.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Reflection;
using System.Text;
namespace GlitchyEngine.Extensions;
@@ -15,4 +16,53 @@ public static class TypeExtension
{
return targetType.IsAssignableFrom(type);
}
+
+ ///
+ /// Enumerates all types in all assemblies.
+ ///
+ public static IEnumerable EnumerateAllTypes()
+ {
+ foreach (Assembly domainAssembly in AppDomain.CurrentDomain.GetAssemblies())
+ foreach (Type type in domainAssembly.GetTypes())
+ {
+ yield return type;
+ }
+ }
+
+ ///
+ /// Enumerates all types that derive from the given type.
+ ///
+ ///
+ ///
+ public static IEnumerable 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;
+ }
+ }
+
+ ///
+ /// Returns whether or not the type has an of the specified .
+ ///
+ /// The type of the attribute
+ /// if the type has the specified ; otherwise.
+ public static bool HasCustomAttribute(this Type type) where T: Attribute
+ {
+ return type.GetCustomAttribute() != null;
+ }
+
+ ///
+ /// Returns whether or not the type has an of the specified .
+ ///
+ /// The type of the attribute
+ /// The attribute, or , if the type hasn't got the attribute specified.
+ /// if the type has the specified ; otherwise.
+ public static bool TryGetCustomAttribute(this Type type, out T attribute) where T: Attribute
+ {
+ attribute = type.GetCustomAttribute();
+
+ return attribute != null;
+ }
}
diff --git a/ScriptCore/Serialization/CustomSerializerAttribute.cs b/ScriptCore/Serialization/CustomSerializerAttribute.cs
new file mode 100644
index 0000000..ad932d1
--- /dev/null
+++ b/ScriptCore/Serialization/CustomSerializerAttribute.cs
@@ -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;
+ }
+}
diff --git a/ScriptCore/Serialization/DeserializationObject.cs b/ScriptCore/Serialization/DeserializationObject.cs
index 478ad77..c13b350 100644
--- a/ScriptCore/Serialization/DeserializationObject.cs
+++ b/ScriptCore/Serialization/DeserializationObject.cs
@@ -10,12 +10,13 @@ using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
+using GlitchyEngine.Editor;
namespace GlitchyEngine.Serialization;
-internal class DeserializationObject
+public class DeserializationObject
{
- private class NoObject
+ public class NoObject
{
}
@@ -23,7 +24,7 @@ internal class DeserializationObject
///
/// Object used to specify, that no particular value was deserialized for the field and thus the value currently stored shall not be changed.
///
- private static readonly NoObject NoValueDeserialized = new ();
+ public static readonly NoObject NoValueDeserialized = new ();
private IntPtr _internalContext;
@@ -36,6 +37,24 @@ internal class DeserializationObject
public Dictionary DeserializedClasses;
private object _instance;
+
+ private Dictionary _fullNameToType = new();
+
+ private static Dictionary _customDeserializers = new();
+
+
+ ///
+ /// 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);
+ }
+ }
public DeserializationObject(IntPtr internalContext, UUID id, Dictionary deserializedClasses)
{
@@ -43,10 +62,30 @@ internal class DeserializationObject
_id = id;
DeserializedClasses = deserializedClasses;
}
+
+ static DeserializationObject()
+ {
+ foreach (Type type in TypeExtension.EnumerateAllTypes())
+ {
+ if (type.TryGetCustomAttribute(out var attribute))
+ {
+ MethodInfo deserializeMethod = type.GetMethod("Deserialize", BindingFlags.Static | BindingFlags.Public,
+ null,
+ new []{ typeof(DeserializationObject), typeof(string), typeof(Type) }, null);
- private Dictionary _fullNameToType = new();
+ if (deserializeMethod == null)
+ {
+ Log.Error($"No Deserialize-method found for type {type}");
+ }
+ else
+ {
+ _customDeserializers.Add(attribute.Type, deserializeMethod);
+ }
+ }
+ }
+ }
- private Type FindType(string fullName)
+ public Type FindType(string fullName)
{
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies().Reverse())
{
@@ -59,7 +98,7 @@ internal class DeserializationObject
return null;
}
- private Type GetTypeFromName(string fullName)
+ public Type GetTypeFromName(string fullName)
{
if (_fullNameToType.TryGetValue(fullName, out Type storedType))
return storedType;
@@ -71,7 +110,7 @@ internal class DeserializationObject
return type;
}
- private DeserializationObject GetDeserializedObject(UUID id)
+ public DeserializationObject GetDeserializedObject(UUID id)
{
DeserializationObject context;
@@ -87,14 +126,14 @@ internal class DeserializationObject
return context;
}
- private void PushScope(string name)
+ public void PushScope(string name)
{
_structScope.Push(name);
_structScopeName += $"{name}.";
}
- private void PopScope()
+ public void PopScope()
{
string scopeToRemove = _structScope.Pop();
_structScopeName = _structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1);
@@ -115,12 +154,12 @@ internal class DeserializationObject
public EngineObjectReferenceHelper EngineObjectReference;
}
- private T GetFieldValue(string fieldName, SerializationType serializationType)
+ public T GetFieldValue(string fieldName, SerializationType 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}";
@@ -200,7 +239,7 @@ internal class DeserializationObject
DeserializeFields(entity);
}
- private bool DeserializeFields(object obj)
+ public bool DeserializeFields(object obj)
{
Type type = obj.GetType();
@@ -224,8 +263,35 @@ internal class DeserializationObject
return changed;
}
+
+ private bool TryCustomDeserializer(string fieldName, Type fieldType, out object deserializedValue)
+ {
+ deserializedValue = NoValueDeserialized;
- private object DeserializeField(object fieldValue, Type fieldType, string fieldName)
+ try
+ {
+ // Try to match the concrete type first (e.g. Foo -> Foo and Foo -> List)
+ // Note: Foo 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)
{
@@ -245,6 +311,11 @@ internal class DeserializationObject
}
else if (fieldType.IsGenericType)
{
+ if (TryCustomDeserializer(fieldName, fieldType, out object deserializedValue))
+ {
+ return deserializedValue;
+ }
+
if (fieldType.GetGenericTypeDefinition() == typeof(List<>))
{
return DeserializeList(fieldName, fieldType, fieldType.GetGenericArguments()[0]);
@@ -261,10 +332,20 @@ internal class DeserializationObject
}
else if (fieldType.IsValueType)
{
+ if (TryCustomDeserializer(fieldName, fieldType, out object deserializedValue))
+ {
+ return deserializedValue;
+ }
+
return DeserializeStruct(fieldName, fieldValue);
}
else if (fieldType.IsClass)
{
+ if (TryCustomDeserializer(fieldName, fieldType, out object deserializedValue))
+ {
+ return deserializedValue;
+ }
+
return DeserializeClass(fieldName, fieldType);
}
else
@@ -275,7 +356,7 @@ internal class DeserializationObject
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.");
@@ -315,20 +396,7 @@ 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)
+ public object DeserializeList(string fieldName, Type fieldType, Type elementType)
{
UUID id = (UUID)GetFieldValue(fieldName, SerializationType.ObjectReference);
@@ -398,7 +466,7 @@ internal class DeserializationObject
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)
return NoValueDeserialized;
@@ -415,7 +483,7 @@ internal class DeserializationObject
return NoValueDeserialized;
}
- private object DeserializeStruct(string fieldName, object targetInstance)
+ public object DeserializeStruct(string fieldName, object targetInstance)
{
PushScope(fieldName);
@@ -426,7 +494,7 @@ internal class DeserializationObject
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 isComponent = fieldType.IsSubclassOf(typeof(Component));
diff --git a/ScriptCore/Serialization/DictionarySerializer.cs b/ScriptCore/Serialization/DictionarySerializer.cs
new file mode 100644
index 0000000..94b6038
--- /dev/null
+++ b/ScriptCore/Serialization/DictionarySerializer.cs
@@ -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;
+
+///
+/// Provides Serialization and Deserialization for Dictionaries.
+///
+[CustomSerializer(typeof(Dictionary<,>))]
+public static class DictionarySerializer
+{
+ ///
+ /// Serializes a given Dictionary as a field of .
+ ///
+ /// The parent object.
+ /// Name of the field in
+ /// The dictionary
+ /// Type of or the type of the field if is
+ 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);
+ }
+
+ ///
+ /// Deserializes a dictionary that is a field of the given .
+ ///
+ /// The parent object whose field with is a dictionary.
+ /// The name of the field.
+ /// The type of the field.
+ ///
+ public static object? Deserialize(DeserializationObject container, string fieldName, Type fieldType)
+ {
+ UUID id = container.GetFieldValue(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("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;
+ }
+}
diff --git a/ScriptCore/Serialization/EntitySerializer.cs b/ScriptCore/Serialization/EntitySerializer.cs
index a78e609..0168661 100644
--- a/ScriptCore/Serialization/EntitySerializer.cs
+++ b/ScriptCore/Serialization/EntitySerializer.cs
@@ -7,7 +7,7 @@ using GlitchyEngine.Extensions;
namespace GlitchyEngine.Serialization;
-internal enum SerializationType : int
+public enum SerializationType : int
{
None,
diff --git a/ScriptCore/Serialization/SerializedObject.cs b/ScriptCore/Serialization/SerializedObject.cs
index 53ebeba..96c39d7 100644
--- a/ScriptCore/Serialization/SerializedObject.cs
+++ b/ScriptCore/Serialization/SerializedObject.cs
@@ -4,10 +4,11 @@ using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
+using GlitchyEngine.Extensions;
namespace GlitchyEngine.Serialization;
-internal class SerializedObject
+public class SerializedObject
{
private IntPtr _internalContext;
@@ -18,6 +19,10 @@ internal class SerializedObject
private Stack _structScope = new();
private string _structScopeName;
+
+ private static Dictionary _customSerializers = new();
+
+ public UUID Id => _id;
public SerializedObject(IntPtr internalContext, UUID id, Dictionary