ScriptEngine: Serialize static fields

This commit is contained in:
Simon Lübeß
2024-04-13 00:10:35 +02:00
parent df13aa8596
commit 949ab4ebb0
16 changed files with 504 additions and 244 deletions
@@ -0,0 +1,105 @@
using System;
using Mono;
using GlitchyEngine.Core;
using GlitchyEngine.Serialization;
namespace GlitchyEngine.Scripting;
using internal GlitchyEngine.Scripting;
class EntitySerializerWrapper : ScriptClass
{
function MonoObject* SerializeMethod(MonoObject* entity, void* contextPtr, MonoException** exception);
function MonoObject* DeserializeMethod(MonoObject* entity, void* contextPtr, MonoException** exception);
function MonoObject* SerializeStaticMethod(MonoReflectionType* type, void* contextPtr, MonoException** exception);
function MonoObject* DeserializeStaticMethod(MonoReflectionType* type, void* contextPtr, MonoException** exception);
private SerializeMethod _serializeMethod;
private DeserializeMethod _deserializeMethod;
private SerializeStaticMethod _serializeStaticMethod;
private DeserializeStaticMethod _deserializeStaticMethod;
[AllowAppend]
public this(StringView classNamespace, StringView className, MonoImage* image) : base(classNamespace, className, image)
{
_serializeMethod = (SerializeMethod)GetMethodThunk("Serialize", 2);
if (_serializeMethod == null)
{
Log.EngineLogger.Error("EntitySerializer has no \"Serialize\" method.");
}
_deserializeMethod = (DeserializeMethod)GetMethodThunk("Deserialize", 2);
if (_serializeMethod == null)
{
Log.EngineLogger.Error("EntitySerializer has no \"Deserialize\" method.");
}
_serializeStaticMethod = (SerializeStaticMethod)GetMethodThunk("SerializeStaticFields", 2);
if (_serializeStaticMethod == null)
{
Log.EngineLogger.Error("EntitySerializer has no \"SerializeStaticStatic\" method.");
}
_deserializeStaticMethod = (SerializeStaticMethod)GetMethodThunk("DeserializeStaticFields", 2);
if (_deserializeStaticMethod == null)
{
Log.EngineLogger.Error("EntitySerializer has no \"DeserializeStaticStatic\" method.");
}
}
public void Serialize(ScriptInstance instance, SerializedObject context)
{
void* thisPtr = Internal.UnsafeCastToPtr(context);
MonoException* exception = null;
_serializeMethod(instance.[Friend]_instance, thisPtr, &exception);
if (exception != null)
ScriptEngine.[Friend]HandleMonoException(exception, instance);
}
public void SerializeStatic(ScriptClass @class, SerializedObject context)
{
void* thisPtr = Internal.UnsafeCastToPtr(context);
MonoException* exception = null;
MonoType* type = Mono.mono_class_get_type(@class.[Friend]_monoClass);
MonoReflectionType* reflectionType = Mono.mono_type_get_object(ScriptEngine.[Friend]s_AppDomain, type);
_serializeStaticMethod(reflectionType, thisPtr, &exception);
if (exception != null)
ScriptEngine.[Friend]HandleMonoException(exception, null);
}
public void Deserialize(ScriptInstance instance, SerializedObject context)
{
void* thisPtr = Internal.UnsafeCastToPtr(context);
MonoException* exception = null;
_deserializeMethod(instance.[Friend]_instance, thisPtr, &exception);
if (exception != null)
ScriptEngine.[Friend]HandleMonoException(exception, instance);
}
public void DeserializeStatic(ScriptClass @class, SerializedObject context)
{
void* thisPtr = Internal.UnsafeCastToPtr(context);
MonoException* exception = null;
MonoType* type = Mono.mono_class_get_type(@class.[Friend]_monoClass);
MonoReflectionType* reflectionType = Mono.mono_type_get_object(ScriptEngine.[Friend]s_AppDomain, type);
_deserializeStaticMethod(reflectionType, thisPtr, &exception);
if (exception != null)
ScriptEngine.[Friend]HandleMonoException(exception, null);
}
}
@@ -231,23 +231,6 @@ class ScriptClass : SharpClass
return *(T*)Mono.mono_object_unbox(object); return *(T*)Mono.mono_object_unbox(object);
} }
public T GetFieldValue<T>(MonoObject* instance, MonoClassField* field)
{
T value = default;
Mono.mono_field_get_value(instance, field, &value);
return value;
}
public void SetFieldValue<T>(MonoObject* instance, MonoClassField* field, in T value)
{
Mono.mono_field_set_value(instance, field, &value);
}
public void SetFieldValue<T>(MonoObject* instance, MonoClassField* field, in T value) where T : struct*
{
Mono.mono_field_set_value(instance, field, value);
}
public MonoObject* BoxValue<T>(in T value) public MonoObject* BoxValue<T>(in T value)
{ {
return Mono.mono_value_box(ScriptEngine.[Friend]s_AppDomain, _monoClass, &value); return Mono.mono_value_box(ScriptEngine.[Friend]s_AppDomain, _monoClass, &value);
+4 -4
View File
@@ -22,7 +22,7 @@ class EngineClasses
private EntityEditorWrapper s_EntityEditor ~ _?.ReleaseRef(); private EntityEditorWrapper s_EntityEditor ~ _?.ReleaseRef();
private ScriptClass s_EntitySerializer ~ _?.ReleaseRef(); private EntitySerializerWrapper s_EntitySerializer ~ _?.ReleaseRef();
private ScriptClass s_SerializationContext ~ _?.ReleaseRef(); private ScriptClass s_SerializationContext ~ _?.ReleaseRef();
private ScriptClass s_Collision2D ~ _?.ReleaseRef(); private ScriptClass s_Collision2D ~ _?.ReleaseRef();
@@ -35,7 +35,7 @@ class EngineClasses
public EntityEditorWrapper EntityEditor => s_EntityEditor; public EntityEditorWrapper EntityEditor => s_EntityEditor;
public ScriptClass EntitySerializer => s_EntitySerializer; public EntitySerializerWrapper EntitySerializer => s_EntitySerializer;
public ScriptClass Collision2D => s_Collision2D; public ScriptClass Collision2D => s_Collision2D;
@@ -68,7 +68,7 @@ class EngineClasses
// Editor classes // Editor classes
s_EntityEditor = new EntityEditorWrapper("GlitchyEngine.Editor", "EntityEditor", image); s_EntityEditor = new EntityEditorWrapper("GlitchyEngine.Editor", "EntityEditor", image);
s_EntitySerializer = new ScriptClass("GlitchyEngine.Serialization", "EntitySerializer", image); s_EntitySerializer = new EntitySerializerWrapper("GlitchyEngine.Serialization", "EntitySerializer", image);
s_Collision2D = new ScriptClass("GlitchyEngine.Physics", "Collision2D", image); s_Collision2D = new ScriptClass("GlitchyEngine.Physics", "Collision2D", image);
@@ -116,7 +116,7 @@ static class ScriptEngine
private static FileSystemWatcher _userAssemblyWatcher ~ delete _; private static FileSystemWatcher _userAssemblyWatcher ~ delete _;
// TODO: This should be a global setting somewhere // TODO: This should be a global setting somewhere
private static bool _debuggingEnabled = false; private static bool _debuggingEnabled = true;
private static String _appAssemblyPath = new .() ~ delete _; private static String _appAssemblyPath = new .() ~ delete _;
+2 -2
View File
@@ -1143,7 +1143,7 @@ static class ScriptGlue
} }
[RegisterCall("ScriptGlue::Serialization_CreateObject")] [RegisterCall("ScriptGlue::Serialization_CreateObject")]
static void Serialization_CreateObject(void* currentContext, MonoString* typeName, out void* newContext, out UUID newId) static void Serialization_CreateObject(void* currentContext, bool isStatic, MonoString* typeName, out void* newContext, out UUID newId)
{ {
SerializedObject context = Internal.UnsafeCastToObject(currentContext) as SerializedObject; SerializedObject context = Internal.UnsafeCastToObject(currentContext) as SerializedObject;
@@ -1151,7 +1151,7 @@ static class ScriptGlue
char8* rawTypeName = Mono.mono_string_to_utf8(typeName); char8* rawTypeName = Mono.mono_string_to_utf8(typeName);
SerializedObject newObject = new SerializedObject(context.Serializer, StringView(rawTypeName)); SerializedObject newObject = new SerializedObject(context.Serializer, isStatic, StringView(rawTypeName));
Mono.mono_free(rawTypeName); Mono.mono_free(rawTypeName);
@@ -31,19 +31,31 @@ public class ScriptInstanceSerializer
{ {
Debug.Profiler.ProfileFunction!(); Debug.Profiler.ProfileFunction!();
for (let (id, script) in ScriptEngine._entityScriptInstances) for (let (id, scriptInstance) in ScriptEngine._entityScriptInstances)
{ {
SerializeScriptInstance(script); SerializeScriptInstance(scriptInstance);
}
for (let (name, scriptClass) in ScriptEngine.EntityClasses)
{
SerializeStaticScriptClassFields(scriptClass);
} }
} }
/// Serializes the given script instance. /// Serializes the given script instance.
public void SerializeScriptInstance(ScriptInstance script) public void SerializeScriptInstance(ScriptInstance script)
{ {
SerializedObject object = new SerializedObject(this, script.ScriptClass.FullName, script.EntityId); SerializedObject object = new SerializedObject(this, false, script.ScriptClass.FullName, script.EntityId);
object.Serialize(script); object.Serialize(script);
} }
/// Serializes the given script instance.
public void SerializeStaticScriptClassFields(ScriptClass scriptClass)
{
SerializedObject object = new SerializedObject(this, true, scriptClass.FullName, null);
object.SerializeStaticFields(scriptClass);
}
/// Deserializes this context into the instances currently managed by the script engine. /// Deserializes this context into the instances currently managed by the script engine.
public void DeserializeScriptInstances() public void DeserializeScriptInstances()
{ {
@@ -53,6 +65,11 @@ public class ScriptInstanceSerializer
{ {
DeserializeScriptInstance(id, script); DeserializeScriptInstance(id, script);
} }
for (let (name, scriptClass) in ScriptEngine.EntityClasses)
{
DeserializeStaticScriptClassFields(scriptClass);
}
} }
/// Deserializes the data into the given script instance, if there is data available. /// Deserializes the data into the given script instance, if there is data available.
@@ -69,6 +86,25 @@ public class ScriptInstanceSerializer
return false; return false;
} }
public bool DeserializeStaticScriptClassFields(ScriptClass scriptClass)
{
for (let object in _serializedData.Values)
{
if (object.IsStatic && object.TypeName == scriptClass.FullName)
{
// No data anyway, save some time.
if (object.Fields.Count == 0)
return false;
object.DeserializeStaticFields(scriptClass);
return true;
}
}
return false;
}
/// Replaces Entity references in the given serialized data using the specified translation table. /// Replaces Entity references in the given serialized data using the specified translation table.
public void FixupSerializedIds(Dictionary<UUID, UUID> originalToCopyIds) public void FixupSerializedIds(Dictionary<UUID, UUID> originalToCopyIds)
{ {
@@ -33,6 +33,8 @@ class SerializedObject
/// Todo: This sucks because we don't know all UUIDs beforehand and might accidentally assign the ID of an Entity to some class /// Todo: This sucks because we don't know all UUIDs beforehand and might accidentally assign the ID of an Entity to some class
public UUID Id; public UUID Id;
public bool IsStatic;
public String TypeName ~ delete:append _; public String TypeName ~ delete:append _;
public ScriptInstanceSerializer Serializer; public ScriptInstanceSerializer Serializer;
@@ -42,12 +44,14 @@ class SerializedObject
public append Dictionary<StringView, (SerializationType PrimitiveType, FieldData Data)> Fields = .(); public append Dictionary<StringView, (SerializationType PrimitiveType, FieldData Data)> Fields = .();
[AllowAppend] [AllowAppend]
public this(ScriptInstanceSerializer serializer, StringView? typeName, UUID? id = null) public this(ScriptInstanceSerializer serializer, bool isStatic, StringView? typeName, UUID? id = null)
{ {
String typeNameCopy = append String(typeName.Value); String typeNameCopy = append String(typeName.Value);
Serializer = serializer; Serializer = serializer;
IsStatic = isStatic;
if (id == null) if (id == null)
{ {
// Make sure we have no duplicate keys // Make sure we have no duplicate keys
@@ -200,33 +204,24 @@ class SerializedObject
} }
} }
typealias SerializeMethod = function MonoObject*(MonoObject* entity, void* contextPtr, MonoException** exception);
typealias DeserializeMethod = function MonoObject*(MonoObject* entity, void* contextPtr, MonoException** exception);
public void Serialize(ScriptInstance scriptInstance) public void Serialize(ScriptInstance scriptInstance)
{ {
SerializeMethod serialize = (SerializeMethod)ScriptEngine.Classes.EntitySerializer.GetMethodThunk("Serialize", 2); ScriptEngine.Classes.EntitySerializer.Serialize(scriptInstance, this);
void* thisPtr = Internal.UnsafeCastToPtr(this);
MonoException* exception = null;
serialize(scriptInstance.[Friend]_instance, thisPtr, &exception);
if (exception != null)
ScriptEngine.[Friend]HandleMonoException(exception, scriptInstance);
} }
public void Deserialize(ScriptInstance scriptInstance) public void Deserialize(ScriptInstance scriptInstance)
{ {
DeserializeMethod deserialize = (DeserializeMethod)ScriptEngine.Classes.EntitySerializer.GetMethodThunk("Deserialize", 2); ScriptEngine.Classes.EntitySerializer.Deserialize(scriptInstance, this);
}
void* thisPtr = Internal.UnsafeCastToPtr(this); public void SerializeStaticFields(ScriptClass scriptClass)
{
ScriptEngine.Classes.EntitySerializer.SerializeStatic(scriptClass, this);
}
MonoException* exception = null; public void DeserializeStaticFields(ScriptClass scriptClass)
deserialize(scriptInstance.[Friend]_instance, thisPtr, &exception); {
ScriptEngine.Classes.EntitySerializer.DeserializeStatic(scriptClass, this);
if (exception != null)
ScriptEngine.[Friend]HandleMonoException(exception, scriptInstance);
} }
static this static this
@@ -374,7 +369,7 @@ class SerializedObject
Try!(Deserialize.Value<UUID>(reader, "ID", let objectId, environment)); Try!(Deserialize.Value<UUID>(reader, "ID", let objectId, environment));
SerializedObject object = new SerializedObject(scriptSerializer, type, objectId); SerializedObject object = new SerializedObject(scriptSerializer, false, type, objectId);
while (reader.ObjectHasMore()) while (reader.ObjectHasMore())
{ {
+31 -7
View File
@@ -64,9 +64,6 @@ class SceneSerializer
using (writer.ObjectBlock()) using (writer.ObjectBlock())
{ {
// TODO: Scene name goes here!
Serialize.Value(writer, "Name", "Scene name here pls!!!");
Serialize.Value(writer, "Version", FileVersion); Serialize.Value(writer, "Version", FileVersion);
writer.Identifier("Entities"); writer.Identifier("Entities");
@@ -93,7 +90,30 @@ class SceneSerializer
{ {
for (UUID id in _objectsNotWritten) for (UUID id in _objectsNotWritten)
{ {
Serialize.Value(writer, _scriptSerializer.GetSerializedObject(id)); SerializedObject object = _scriptSerializer.GetSerializedObject(id);
if (object.IsStatic)
continue;
Serialize.Value(writer, object);
}
}
writer.EntryEnd();
writer.Identifier("StaticFields");
// Write all objects that don't belong to an entity. (referenced Arrays, Classes, etc...)
using (writer.ArrayBlock())
{
for (UUID id in _objectsNotWritten)
{
SerializedObject object = _scriptSerializer.GetSerializedObject(id);
if (!object.IsStatic || object.Fields.Count == 0)
continue;
Serialize.Value(writer, object);
} }
} }
@@ -346,7 +366,9 @@ class SceneSerializer
case "Entities": case "Entities":
Try!(DeserializeEntities(reader, loadAsPrefab)); Try!(DeserializeEntities(reader, loadAsPrefab));
case "ReferencedObjects": case "ReferencedObjects":
Try!(DeserializeReferencedObjects(reader)); Try!(DeserializeReferencedObjects(reader, false));
case "StaticFields":
Try!(DeserializeReferencedObjects(reader, true));
default: default:
Log.EngineLogger.Warning($"Encountered unexpected Identifier \"{identifier}\"."); Log.EngineLogger.Warning($"Encountered unexpected Identifier \"{identifier}\".");
} }
@@ -390,13 +412,15 @@ class SceneSerializer
return .Ok; return .Ok;
} }
private Result<void> DeserializeReferencedObjects(BonReader reader) private Result<void> DeserializeReferencedObjects(BonReader reader, bool isStatic)
{ {
Try!(reader.ArrayBlock()); Try!(reader.ArrayBlock());
while (reader.ArrayHasMore()) while (reader.ArrayHasMore())
{ {
Try!(SerializedObject.BonDeserialize(reader, _scriptSerializer, _fileVersion, gBonEnv)); SerializedObject object = Try!(SerializedObject.BonDeserialize(reader, _scriptSerializer, _fileVersion, gBonEnv));
object.IsStatic = isStatic;
TryEndEntry(reader); TryEndEntry(reader);
} }
+107 -75
View File
@@ -146,6 +146,22 @@ internal class EntityEditor
return false; return false;
} }
public static bool ShowButtonInEditor(MethodInfo methodInfo)
{
var showButton = methodInfo.GetCustomAttribute<ShowButtonAttribute>();
if (showButton == null)
return false;
if (Application.IsInEditMode && !showButton.Visibility.HasFlag(ButtonVisibility.InEditMode))
return false;
if (Application.IsInPlayMode && !showButton.Visibility.HasFlag(ButtonVisibility.InPlayMode))
return false;
return true;
}
/// <summary> /// <summary>
/// Entry point for the engine to show the editor for the given script instance. /// Entry point for the engine to show the editor for the given script instance.
/// </summary> /// </summary>
@@ -908,103 +924,119 @@ internal class EntityEditor
return newValue; return newValue;
} }
public static void ShowEditor(Type type, object? reference) private static void ShowEditor(Type type, object? reference)
{ {
int i = 0; int i = 0;
// Iterate all fields // Iterate all fields
foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) //foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
foreach (MemberInfo member in type.GetMembers(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance))
{ {
i++; i++;
ImGui.PushID(i); ImGui.PushID(i);
if (ShowFieldInEditor(field)) switch (member)
{ {
object value = field.GetValue(reference); case FieldInfo field:
ShowField(field, reference);
IEnumerable<Attribute> attributes = field.GetCustomAttributes(); break;
case MethodInfo method:
LabelAttribute? label = GetAttribute<LabelAttribute>(attributes); ShowButton(method, reference);
break;
string prettyName;
if (label != null)
{
prettyName = label.Label ?? field.Name;
}
else
{
prettyName = field.Name.ToPrettyName();
}
object? newValue = ShowFieldEditor(value, value?.GetType() ?? field.FieldType, prettyName, attributes);
if (newValue != DidNotChange)
{
// Reference was changed inside Field Editor -> write back to field (only necessary for value types)
field.SetValue(reference, newValue);
}
} }
ImGui.PopID(); ImGui.PopID();
} }
ShowButtons(type, reference); bool showedHeader = false;
foreach (FieldInfo field in type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static))
{
if (!showedHeader)
{
BeginNewRow();
if (!ImGui.TreeNodeEx("Static Fields", ImGuiTreeNodeFlags.AllowOverlap | ImGuiTreeNodeFlags.SpanAllColumns))
{
break;
}
showedHeader = true;
}
i++;
ImGui.PushID(i);
ShowField(field, null);
ImGui.PopID();
}
if (showedHeader)
{
ImGui.TreePop();
}
} }
/// <summary> private static void ShowField(FieldInfo field, object? reference)
/// Shows all buttons in the UI.
/// </summary>
private static void ShowButtons(Type type, object? reference)
{ {
ImGui.PushID("Buttons"); if (!ShowFieldInEditor(field))
return;
int i = 0; object value = field.GetValue(reference);
// Iterate all methods IEnumerable<Attribute> attributes = field.GetCustomAttributes();
foreach (MethodInfo method in type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
LabelAttribute? label = GetAttribute<LabelAttribute>(attributes);
string prettyName;
if (label != null)
{ {
var showButton = method.GetCustomAttribute<ShowButtonAttribute>(); prettyName = label.Label ?? field.Name;
}
if (showButton == null) else
continue; {
prettyName = field.Name.ToPrettyName();
if (Application.IsInEditMode && !showButton.Visibility.HasFlag(ButtonVisibility.InEditMode))
continue;
if (Application.IsInPlayMode && !showButton.Visibility.HasFlag(ButtonVisibility.InPlayMode))
continue;
i++;
ImGui.PushID(i);
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
if (method.GetParameters().Length > 0)
{
Log.Error($"Method {method.Name} cannot be executed from editor, because it expects arguments.");
}
else if (ImGui.Button(showButton.ButtonText))
{
try
{
method.Invoke(reference, null);
}
catch (TargetInvocationException ex)
{
Log.Exception(ex.InnerException ?? ex);
}
catch (Exception ex)
{
Log.Exception(ex);
}
}
ImGui.PopID();
} }
ImGui.PopID(); object? newValue = ShowFieldEditor(value, value?.GetType() ?? field.FieldType, prettyName, attributes);
if (newValue != DidNotChange)
{
// Reference was changed inside Field Editor -> write back to field (only necessary for value types)
field.SetValue(reference, newValue);
}
}
private static void ShowButton(MethodInfo method, object? reference)
{
if (!ShowButtonInEditor(method))
return;
var showButton = method.GetCustomAttribute<ShowButtonAttribute>();
BeginNewRow();
if (method.GetParameters().Length > 0)
{
Log.Error($"Method {method.Name} cannot be executed from editor, because it expects arguments.");
}
else if (ImGui.Button(showButton.ButtonText))
{
try
{
method.Invoke(reference, null);
}
catch (TargetInvocationException ex)
{
Log.Exception(ex.InnerException ?? ex);
}
catch (Exception ex)
{
Log.Exception(ex);
}
}
} }
struct ListPayload struct ListPayload
@@ -1,21 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace GlitchyEngine.Extensions;
/// <summary>
/// Extends the <see cref="FieldInfo"/>-class with useful methods.
/// </summary>
public static class FieldInfoExtension
{
/// <summary>
/// Returns <see langword="true"/> if the field has the specified attribute.
/// </summary>
/// <typeparam name="T">The type of the attribte.</typeparam>
public static bool HasCustomAttribute<T>(this FieldInfo fiedInfo) where T : Attribute
{
return fiedInfo.GetCustomAttribute<T>() != null;
}
}
@@ -0,0 +1,19 @@
using System;
using System.Reflection;
namespace GlitchyEngine.Extensions;
/// <summary>
/// Extends <see cref="MemberInfo"/> with useful methods.
/// </summary>
public static class MemberInfoExtension
{
/// <summary>
/// Returns <see langword="true"/> if the member has the specified attribute.
/// </summary>
/// <typeparam name="T">The type of the attribute.</typeparam>
public static bool HasCustomAttribute<T>(this MemberInfo memberInfo) where T : Attribute
{
return memberInfo.GetCustomAttribute<T>() != null;
}
}
+13
View File
@@ -227,4 +227,17 @@ public static class TypeExtension
return nameBuilder.ToString(); return nameBuilder.ToString();
} }
/// <summary>
/// Returns <see langword="true"/> if the type has at least one static field; <see langword="false"/> otherwise.
/// </summary>
public static bool HasStaticFields(this Type type)
{
foreach (FieldInfo _ in type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
{
return true;
}
return false;
}
} }
+1 -1
View File
@@ -318,7 +318,7 @@ internal static class ScriptGlue
internal static extern void Serialization_SerializeField(IntPtr serializationContext, SerializationType type, string name, object? value, string? fullTypeName = null); internal static extern void Serialization_SerializeField(IntPtr serializationContext, SerializationType type, string name, object? value, string? fullTypeName = null);
[MethodImpl(MethodImplOptions.InternalCall)] [MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Serialization_CreateObject(IntPtr currentContext, string fullTypeName, out IntPtr context, out UUID id); internal static extern void Serialization_CreateObject(IntPtr currentContext, bool isStatic, string fullTypeName, out IntPtr context, out UUID id);
[MethodImpl(MethodImplOptions.InternalCall)] [MethodImpl(MethodImplOptions.InternalCall)]
public static extern unsafe void Serialization_DeserializeField(IntPtr internalContext, SerializationType expectedType, string fieldName, byte* value, out SerializationType actualType); public static extern unsafe void Serialization_DeserializeField(IntPtr internalContext, SerializationType expectedType, string fieldName, byte* value, out SerializationType actualType);
@@ -28,6 +28,8 @@ public class DeserializationObject
private IntPtr _internalContext; private IntPtr _internalContext;
private bool _isStatic;
private UUID _id; private UUID _id;
private Stack<string> _structScope = new(); private Stack<string> _structScope = new();
@@ -57,9 +59,12 @@ public class DeserializationObject
} }
} }
public DeserializationObject(IntPtr internalContext, UUID id, Dictionary<UUID, DeserializationObject> deserializedClasses) public bool IsStatic => _isStatic;
public DeserializationObject(IntPtr internalContext, bool isStatic, UUID id, Dictionary<UUID, DeserializationObject> deserializedClasses)
{ {
_internalContext = internalContext; _internalContext = internalContext;
_isStatic = isStatic;
_id = id; _id = id;
DeserializedClasses = deserializedClasses; DeserializedClasses = deserializedClasses;
} }
@@ -114,7 +119,7 @@ public class DeserializationObject
return null; return null;
} }
context = new DeserializationObject(contextPtr, id, DeserializedClasses); context = new DeserializationObject(contextPtr, false, id, DeserializedClasses);
DeserializedClasses.Add(id, context); DeserializedClasses.Add(id, context);
@@ -251,6 +256,36 @@ public class DeserializationObject
DeserializeFields(entity); DeserializeFields(entity);
} }
public void Deserialize(Type type)
{
_instance = null;
DeserializeStaticFields(type);
}
public bool DeserializeStaticFields(Type type)
{
bool changed = false;
foreach (FieldInfo field in type.GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
{
if (!EntitySerializer.SerializeField(field, true))
continue;
object currentValue = field.GetValue(null);
object? deserializeValue = DeserializeField(currentValue, field.FieldType, field.Name);
if (deserializeValue != NoValueDeserialized)
{
field.SetValue(null, deserializeValue);
changed = true;
}
}
return changed;
}
public bool DeserializeFields(object obj) public bool DeserializeFields(object obj)
{ {
Type type = obj.GetType(); Type type = obj.GetType();
@@ -259,7 +294,7 @@ public class DeserializationObject
foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
{ {
if (!EntitySerializer.SerializeField(field)) if (!EntitySerializer.SerializeField(field, false))
continue; continue;
object currentValue = field.GetValue(obj); object currentValue = field.GetValue(obj);
+18 -80
View File
@@ -7,102 +7,40 @@ using GlitchyEngine.Extensions;
namespace GlitchyEngine.Serialization; namespace GlitchyEngine.Serialization;
[Flags]
public enum SerializationType : uint
{
None = 0,
Bool = 1u << 31,
TextTypes = 1 << 30,
Char = TextTypes | 1,
String = TextTypes | 2,
Number = 1 << 29,
Integer = Number | 1 << 28,
Int8 = Integer | 1,
Int16 = Integer | 2,
Int32 = Integer | 3,
Int64 = Integer | 4,
UInt8 = Integer | 5,
UInt16 = Integer | 6,
UInt32 = Integer | 7,
UInt64 = Integer | 8,
FloatingPoint = Number | 1 << 27,
Float = FloatingPoint | 1,
Double = FloatingPoint | 2,
Decimal = FloatingPoint | 3,
Enum = 1 << 26,
EntityReference = 1 << 25,
ComponentReference = 1 << 24,
ObjectReference = 1 << 23,
}
public static class SerializationTypeExtension
{
public static bool CanConvertTo(this SerializationType currentType, SerializationType targetType)
{
if (currentType.HasFlag(SerializationType.Number) && targetType.HasFlag(SerializationType.Number))
return true;
return false;
}
public static Type? GetTypeInstance(this SerializationType currentType)
{
return currentType switch
{
SerializationType.Bool => typeof(bool),
SerializationType.Char => typeof(char),
SerializationType.String => typeof(string),
SerializationType.Int8 => typeof(sbyte),
SerializationType.Int16 => typeof(short),
SerializationType.Int32 => typeof(int),
SerializationType.Int64 => typeof(long),
SerializationType.UInt8 => typeof(byte),
SerializationType.UInt16 => typeof(ushort),
SerializationType.UInt32 => typeof(uint),
SerializationType.UInt64 => typeof(ulong),
SerializationType.Float => typeof(float),
SerializationType.Double => typeof(double),
SerializationType.Decimal => typeof(decimal),
SerializationType.Enum => typeof(Enum),
SerializationType.EntityReference => typeof(UUID),
SerializationType.ComponentReference => typeof(UUID),
SerializationType.ObjectReference => typeof(UUID),
_ => null
};
}
}
internal static class EntitySerializer internal static class EntitySerializer
{ {
internal static void Serialize(Entity entity, IntPtr internalContext) internal static void Serialize(Entity entity, IntPtr internalContext)
{ {
SerializedObject obj = new SerializedObject(internalContext, UUID.Zero, new Dictionary<object, SerializedObject>()); SerializedObject obj = new SerializedObject(internalContext, false, UUID.Zero, new Dictionary<object, SerializedObject>());
obj.Serialize(entity); obj.Serialize(entity);
} }
internal static void Deserialize(Entity entity, IntPtr internalContext) internal static void Deserialize(Entity entity, IntPtr internalContext)
{ {
DeserializationObject obj = new DeserializationObject(internalContext, UUID.Zero, new Dictionary<UUID, DeserializationObject>()); DeserializationObject obj = new DeserializationObject(internalContext, false, UUID.Zero, new Dictionary<UUID, DeserializationObject>());
obj.Deserialize(entity); obj.Deserialize(entity);
} }
internal static void SerializeStaticFields(Type type, IntPtr internalContext)
{
SerializedObject obj = new SerializedObject(internalContext, true, UUID.Zero, new Dictionary<object, SerializedObject>());
obj.Serialize(type);
}
internal static void DeserializeStaticFields(Type type, IntPtr internalContext)
{
DeserializationObject obj = new DeserializationObject(internalContext, true, UUID.Zero, new Dictionary<UUID, DeserializationObject>());
obj.Deserialize(type);
}
/// <summary> /// <summary>
/// Returns whether or not the field described by the given <see cref="fieldInfo"/> should be serialized or not. /// Returns whether or not the field described by the given <see cref="fieldInfo"/> should be serialized or not.
/// </summary> /// </summary>
internal static bool SerializeField(FieldInfo fieldInfo) /// <param name="fieldInfo">The field info for which to check whether the field should be serialized or not.</param>
/// <param name="staticSerialization">If <see langword="true"/> the only Static fields can be serialized; if <see langword="false"/> only instance fields can be serialized.</param>
internal static bool SerializeField(FieldInfo fieldInfo, bool staticSerialization)
{ {
// TODO: We want to be able to serialize static fields in the future! if (staticSerialization != fieldInfo.IsStatic)
if (fieldInfo.IsStatic)
return false; return false;
var serializeField = fieldInfo.HasCustomAttribute<SerializeFieldAttribute>(); var serializeField = fieldInfo.HasCustomAttribute<SerializeFieldAttribute>();
@@ -0,0 +1,77 @@
using System;
using GlitchyEngine.Core;
namespace GlitchyEngine.Serialization;
[Flags]
public enum SerializationType : uint
{
None = 0,
Bool = 1u << 31,
TextTypes = 1 << 30,
Char = TextTypes | 1,
String = TextTypes | 2,
Number = 1 << 29,
Integer = Number | 1 << 28,
Int8 = Integer | 1,
Int16 = Integer | 2,
Int32 = Integer | 3,
Int64 = Integer | 4,
UInt8 = Integer | 5,
UInt16 = Integer | 6,
UInt32 = Integer | 7,
UInt64 = Integer | 8,
FloatingPoint = Number | 1 << 27,
Float = FloatingPoint | 1,
Double = FloatingPoint | 2,
Decimal = FloatingPoint | 3,
Enum = 1 << 26,
EntityReference = 1 << 25,
ComponentReference = 1 << 24,
ObjectReference = 1 << 23,
}
public static class SerializationTypeExtension
{
public static bool CanConvertTo(this SerializationType currentType, SerializationType targetType)
{
if (currentType.HasFlag(SerializationType.Number) && targetType.HasFlag(SerializationType.Number))
return true;
return false;
}
public static Type? GetTypeInstance(this SerializationType currentType) =>
currentType switch
{
SerializationType.Bool => typeof(bool),
SerializationType.Char => typeof(char),
SerializationType.String => typeof(string),
SerializationType.Int8 => typeof(sbyte),
SerializationType.Int16 => typeof(short),
SerializationType.Int32 => typeof(int),
SerializationType.Int64 => typeof(long),
SerializationType.UInt8 => typeof(byte),
SerializationType.UInt16 => typeof(ushort),
SerializationType.UInt32 => typeof(uint),
SerializationType.UInt64 => typeof(ulong),
SerializationType.Float => typeof(float),
SerializationType.Double => typeof(double),
SerializationType.Decimal => typeof(decimal),
SerializationType.Enum => typeof(Enum),
SerializationType.EntityReference => typeof(UUID),
SerializationType.ComponentReference => typeof(UUID),
SerializationType.ObjectReference => typeof(UUID),
_ => null
};
}
+32 -8
View File
@@ -20,15 +20,20 @@ public class SerializedObject
private string _structScopeName = ""; private string _structScopeName = "";
private bool _isStatic;
private delegate void SerializeMethod(SerializedObject container, string fieldName, object? fieldValue, Type fieldType); private delegate void SerializeMethod(SerializedObject container, string fieldName, object? fieldValue, Type fieldType);
private static Dictionary<Type, SerializeMethod> _customSerializers = new(); private static Dictionary<Type, SerializeMethod> _customSerializers = new();
public UUID Id => _id; public UUID Id => _id;
public SerializedObject(IntPtr internalContext, UUID id, Dictionary<object, SerializedObject> serializedClasses) public bool IsStatic => _isStatic;
public SerializedObject(IntPtr internalContext, bool isStatic, UUID id, Dictionary<object, SerializedObject> serializedClasses)
{ {
_internalContext = internalContext; _internalContext = internalContext;
_isStatic = isStatic;
_id = id; _id = id;
_serializedClasses = serializedClasses; _serializedClasses = serializedClasses;
} }
@@ -40,9 +45,9 @@ public class SerializedObject
if (_serializedClasses.TryGetValue(o, out context)) if (_serializedClasses.TryGetValue(o, out context))
return (context, false); return (context, false);
ScriptGlue.Serialization_CreateObject(_internalContext, o.GetType().ToString(), out IntPtr contextPtr, out UUID id); ScriptGlue.Serialization_CreateObject(_internalContext, false, o.GetType().ToString(), out IntPtr contextPtr, out UUID id);
context = new SerializedObject(contextPtr, id, _serializedClasses); context = new SerializedObject(contextPtr, false, id, _serializedClasses);
_serializedClasses.Add(o, context); _serializedClasses.Add(o, context);
@@ -95,16 +100,21 @@ public class SerializedObject
public void Serialize(Entity entity) public void Serialize(Entity entity)
{ {
SerializeFields(entity); SerializeInstanceFields(entity);
} }
public void SerializeFields(object obj) public void Serialize(Type type)
{
SerializeStaticTypeFields(type);
}
public void SerializeInstanceFields(object obj)
{ {
Type type = obj.GetType(); Type type = obj.GetType();
foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
{ {
if (!EntitySerializer.SerializeField(field)) if (!EntitySerializer.SerializeField(field, false))
continue; continue;
object? fieldValue = field.GetValue(obj); object? fieldValue = field.GetValue(obj);
@@ -114,6 +124,20 @@ public class SerializedObject
} }
} }
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) private bool TryCustomSerializer(string fieldName, object? fieldValue, Type fieldType)
{ {
try try
@@ -285,7 +309,7 @@ public class SerializedObject
{ {
PushScope(fieldName); PushScope(fieldName);
SerializeFields(fieldValue); SerializeInstanceFields(fieldValue);
PopScope(); PopScope();
} }
@@ -312,7 +336,7 @@ public class SerializedObject
if (newContext) if (newContext)
{ {
context.SerializeFields(fieldValue); context.SerializeInstanceFields(fieldValue);
} }
AddField(fieldName, SerializationType.ObjectReference, context._id); AddField(fieldName, SerializationType.ObjectReference, context._id);