From c9116812b47855dd289050ad36c7408401708e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Sat, 23 Dec 2023 00:44:04 +0100 Subject: [PATCH] Serialize Data --- GlitchyEditor/src/EditorLayer.bf | 13 +- GlitchyEngine/src/Scripting/ScriptEngine.bf | 35 ++- GlitchyEngine/src/Scripting/ScriptGlue.bf | 31 +++ .../src/Scripting/ScriptInstanceSerializer.bf | 171 ++++++++++++ ScriptCore/ScriptGlue.cs | 11 + ScriptCore/Serialization/EntitySerializer.cs | 255 +++++++++++++++++- 6 files changed, 504 insertions(+), 12 deletions(-) create mode 100644 GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf diff --git a/GlitchyEditor/src/EditorLayer.bf b/GlitchyEditor/src/EditorLayer.bf index 0513743..9a6252f 100644 --- a/GlitchyEditor/src/EditorLayer.bf +++ b/GlitchyEditor/src/EditorLayer.bf @@ -866,8 +866,10 @@ namespace GlitchyEditor /// Starts the play mode for the current scene private void OnScenePlay() { - // TODO: Dictionary scriptData = _editorScene.SerializeScripts(); + Dictionary serializedData = scope .(); + ScriptEngine.SerializeScriptInstances(serializedData); + _editor.SceneViewportWindow.EditorMode = false; _sceneState = .Play; @@ -878,14 +880,19 @@ namespace GlitchyEditor _editorScene.CopyTo(runtimeScene, true); SetActiveScene(runtimeScene, startRuntime: true, startSimulation: true); - - // TODO: runtimeScene.DeserializeScripts(scriptData); + + ScriptEngine.DeserializeScriptInstances(serializedData); } _editor.CurrentScene = _activeScene; if (Application.Instance.Settings.EditorSettings.SwitchToPlayerOnPlay) SwitchToPlayWindow(); + + for (let v in serializedData) + { + delete v.value; + } } /// Activates the given scene. diff --git a/GlitchyEngine/src/Scripting/ScriptEngine.bf b/GlitchyEngine/src/Scripting/ScriptEngine.bf index f9037e5..2e6a508 100644 --- a/GlitchyEngine/src/Scripting/ScriptEngine.bf +++ b/GlitchyEngine/src/Scripting/ScriptEngine.bf @@ -65,6 +65,9 @@ class EngineClasses public ScriptClass EntityEditor => s_EntityEditor; + public ScriptClass EntitySerializer => s_EntitySerializer; + //public ScriptClass SerializationContext => s_SerializationContext; + internal void ReleaseAndNullify() { ReleaseRefAndNullify!(s_EngineObject); @@ -87,6 +90,9 @@ class EngineClasses // Editor classes s_EntityEditor = new ScriptClass("GlitchyEngine.Editor", "EntityEditor", image, .Class); + + s_EntitySerializer = new ScriptClass("GlitchyEngine.Serialization", "EntitySerializer", image, .Class); + //s_SerializationContext = new ScriptClass("GlitchyEngine.Serialization", "EntitySerializer.SerializationContext", image, .Class); } } @@ -947,13 +953,34 @@ static class ScriptEngine } /// Serializes all script instances - public static void SerializeScriptInstances(String output) + public static void SerializeScriptInstances(Dictionary allObjects) { - ScriptInstanceSerializer serializer = scope .(); - for (let (id, script) in _entityScriptInstances) { - serializer.Serialize(script); + if (script.ScriptClass.ClassName != "SerializationTest") + continue; + + SerializedObject object = new SerializedObject(allObjects); + + object.Id = script.EntityId; + object.AllObjects = allObjects; + + object.Serialize(script); + } + } + + /// Deserializes the given data into the script instances + public static void DeserializeScriptInstances(Dictionary allObjects) + { + for (let (id, script) in _entityScriptInstances) + { + if (script.ScriptClass.ClassName != "SerializationTest") + continue; + + if (allObjects.TryGetValue(id, let object)) + { + object.Deserialize(script); + } } } } diff --git a/GlitchyEngine/src/Scripting/ScriptGlue.bf b/GlitchyEngine/src/Scripting/ScriptGlue.bf index a32527d..9bd208b 100644 --- a/GlitchyEngine/src/Scripting/ScriptGlue.bf +++ b/GlitchyEngine/src/Scripting/ScriptGlue.bf @@ -529,6 +529,37 @@ static class ScriptGlue return ScriptEngine.ApplicationInfo.IsInPlayMode; } +#endregion + +#region Serialization + + [RegisterCall("ScriptGlue::Serialization_SerializeField")] + static void Serialization_SerializeField(void* serializationContext, SerializationType type, MonoString* nameObject, MonoObject* valueObject) + { + SerializedObject context = Internal.UnsafeCastToObject(serializationContext) as SerializedObject; + + Log.EngineLogger.AssertDebug(context != null); + + char8* name = Mono.mono_string_to_utf8(nameObject); + + context.AddField(StringView(name), type, valueObject); + + Mono.mono_free(name); + } + + [RegisterCall("ScriptGlue::Serialization_CreateObject")] + static void Serialization_CreateObject(void* currentContext, out void* newContext, out UUID newId) + { + SerializedObject context = Internal.UnsafeCastToObject(currentContext) as SerializedObject; + + Log.EngineLogger.AssertDebug(context != null); + + SerializedObject newObject = new SerializedObject(context.AllObjects); + + newContext = Internal.UnsafeCastToPtr(newObject); + newId = newObject.Id; + } + #endregion private static void RegisterCall(String name, T method) where T : var diff --git a/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf b/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf new file mode 100644 index 0000000..7e931e7 --- /dev/null +++ b/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf @@ -0,0 +1,171 @@ +using Mono; +using System.Collections; +using System; +using GlitchyEngine.Core; + +namespace GlitchyEngine.Scripting; + +public enum SerializationType : int32 +{ + case None; + + case Bool; + + case Char; + case String; + + case Int8; + case Int16; + case Int32; + case Int64; + case UInt8; + case UInt16; + case UInt32; + case UInt64; + + case Float; + case Double; + case Decimal; + + case Enum; + + case EntityReference; + case ComponentReference; + + case ObjectReference; + + public int GetSize() + { + switch (this) + { + case .Bool: + return 1; + case .Char: + return 1; + case .Int8, .UInt8: + return 1; + case .Int16, .UInt16: + return 2; + case .Int32, .UInt32: + return 4; + case .Int64, .UInt64: + return 8; + case .Float: + return 4; + case .Double: + return 8; + case .Decimal: + return 16; + case .EntityReference, .ComponentReference, .ObjectReference: + return sizeof(UUID); + default: + return 0; + } + } +} + +class SerializedObject +{ + public UUID Id; + + public Dictionary AllObjects; + + private List _ownedString = new List() ~ DeleteContainerAndItems!(_); + + public append Dictionary Fields = .(); + + public this(Dictionary allObjects) + { + AllObjects = allObjects; + + // Make sure we have no duplicate keys + repeat + { + Id = UUID.Create(); + } + while (AllObjects.ContainsKey(Id)); + + AllObjects.Add(Id, this); + } + + public void AddField(StringView name, SerializationType primitiveType, MonoObject* value) + { + uint8[16] data = .(); + + if (primitiveType == .String) + { + MonoString* string = (.)value; + + char8* rawStringValue = Mono.mono_string_to_utf8(string); + + String stringValue = new String(rawStringValue); + + _ownedString.Add(stringValue); + + Mono.mono_free(rawStringValue); + + StringView valueView = stringValue; + + Internal.MemCpy(&data, &valueView, sizeof(StringView)); + } + else if (primitiveType == .Enum) + { + MonoString* string = (.)value; + + char8* rawEnumValue = Mono.mono_string_to_utf8(string); + + String enumValue = new String(rawEnumValue); + + _ownedString.Add(enumValue); + + Mono.mono_free(rawEnumValue); + + StringView valueView = enumValue; + + Internal.MemCpy(&data, &valueView, sizeof(StringView)); + } + else + { + void* rawValue = Mono.mono_object_unbox(value); + + Internal.MemCpy(&data, rawValue, primitiveType.GetSize()); + + String nameCopy = new String(name); + _ownedString.Add(nameCopy); + } + + String nameCopy = new String(name); + _ownedString.Add(nameCopy); + + Fields.Add(nameCopy, (primitiveType, data)); + } + + 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) + { + SerializeMethod serialize = (SerializeMethod)ScriptEngine.Classes.EntitySerializer.GetMethodThunk("Serialize", 2); + + 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) + { + DeserializeMethod deserialize = (DeserializeMethod)ScriptEngine.Classes.EntitySerializer.GetMethodThunk("Serialize", 2); + + void* thisPtr = Internal.UnsafeCastToPtr(this); + + MonoException* exception = null; + deserialize(scriptInstance.[Friend]_instance, thisPtr, &exception); + + if (exception != null) + ScriptEngine.[Friend]HandleMonoException(exception, scriptInstance); + } +} diff --git a/ScriptCore/ScriptGlue.cs b/ScriptCore/ScriptGlue.cs index 10ae215..ba1ae92 100644 --- a/ScriptCore/ScriptGlue.cs +++ b/ScriptCore/ScriptGlue.cs @@ -2,6 +2,7 @@ using System; using System.Runtime.CompilerServices; using GlitchyEngine.Core; using GlitchyEngine.Math; +using GlitchyEngine.Serialization; namespace GlitchyEngine; @@ -135,4 +136,14 @@ internal static class ScriptGlue internal static extern bool Application_IsInPlayMode(); #endregion + + #region Serialization + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void Serialization_SerializeField(IntPtr serializationContext, SerializationType type, string name, object value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void Serialization_CreateObject(IntPtr currentContext, out IntPtr context, out UUID id); + + #endregion } diff --git a/ScriptCore/Serialization/EntitySerializer.cs b/ScriptCore/Serialization/EntitySerializer.cs index 4ae0f15..df300ce 100644 --- a/ScriptCore/Serialization/EntitySerializer.cs +++ b/ScriptCore/Serialization/EntitySerializer.cs @@ -7,12 +7,251 @@ using System.Reflection; using System.Text; using GlitchyEngine.Core; using GlitchyEngine.Extensions; -using System.Xml.Linq; namespace GlitchyEngine.Serialization; +public enum SerializationType : int +{ + None, + + Bool, + + Char, + String, + + Int8, + Int16, + Int32, + Int64, + UInt8, + UInt16, + UInt32, + UInt64, + + Float, + Double, + Decimal, + + Enum, + + EntityReference, + ComponentReference, + + ObjectReference +} + public static class EntitySerializer { + public class SerializedObject + { + private IntPtr _internalContext; + + private UUID _id; + + private Stack _structScope = new(); + + private string _structScopeName; + + public Dictionary SerializedClasses; + + public SerializedObject(IntPtr internalContext, UUID id, Dictionary serializedClasses) + { + _internalContext = internalContext; + _id = id; + SerializedClasses = serializedClasses; + } + + private (SerializedObject context, bool newContext) GetSerializedObject(object o) + { + SerializedObject context; + + if (SerializedClasses.TryGetValue(o, out context)) + return (context, false); + + ScriptGlue.Serialization_CreateObject(_internalContext, out IntPtr contextPtr, out UUID id); + + context = new SerializedObject(contextPtr, id, SerializedClasses); + + SerializedClasses.Add(o, context); + + return (context, true); + } + + private void PushScope(string name) + { + _structScope.Push(name); + + _structScopeName += $"{name}."; + } + + private void PopScope() + { + string scopeToRemove = _structScope.Pop(); + _structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1); + } + + private void AddField(string fieldName, SerializationType serializationType, object value) + { + string completeFieldName = $"{_structScopeName}{fieldName}"; + + ScriptGlue.Serialization_SerializeField(_internalContext, serializationType, completeFieldName, value); + } + + public void Serialize(Entity entity) + { + SerializeFields(entity); + } + + private void SerializeFields(object obj) + { + Type type = obj.GetType(); + + foreach (FieldInfo field in type.GetFields()) + { + if (!EntitySerializer.SerializeField(field)) + continue; + + object fieldValue = field.GetValue(obj); + Type fieldType = fieldValue?.GetType() ?? field.FieldType; + + SerializeField(field.Name, fieldValue, fieldType); + } + } + + private void SerializeField(string fieldName, object fieldValue, Type fieldType) + { + if (fieldType.IsPrimitive) + { + SerializePrimitive(fieldName, fieldValue, fieldType); + } + else if (fieldType == typeof(string)) + { + AddField(fieldName, SerializationType.String, fieldValue.ToString()); + } + else if (fieldType.IsEnum) + { + SerializeEnum(fieldName, fieldValue, fieldType); + } + else if (fieldType.IsArray) + { + Log.Error($"Array serialization not yet implemented"); + } + else if (fieldType.IsGenericType) + { + if (fieldType.GetGenericTypeDefinition() == typeof(List<>)) + { + //SerializeList(fieldName, type, o); + Log.Error($"List serialization not yet implemented"); + } + //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) + { + SerializeStruct(fieldName, fieldValue, fieldType); + } + else if (fieldType.IsClass) + { + SerializeClass(fieldName, fieldValue, fieldType); + } + else + { + Log.Error($"Encountered unhandled type \"{fieldType}\" while serializing."); + } + } + + private void SerializePrimitive(string fieldName, object fieldValue, Type fieldType) + { + Debug.Assert(fieldType.IsPrimitive, $"{fieldType} is not a primitive type."); + + 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); + } + + private void SerializeEnum(string fieldName, object fieldValue, Type fieldType) + { + AddField(fieldName, SerializationType.Enum, fieldValue.ToString()); + } + + private void SerializeStruct(string fieldName, object fieldValue, Type fieldType) + { + PushScope(fieldName); + + SerializeFields(fieldValue); + + PopScope(); + } + + private void SerializeClass(string fieldName, object fieldValue, Type fieldType) + { + if (typeof(Entity).IsAssignableFrom(fieldType)) + { + AddField(fieldName, SerializationType.EntityReference, ((Entity)fieldValue)?.UUID ?? UUID.Zero); + } + else if (typeof(Component).IsAssignableFrom(fieldType)) + { + AddField(fieldName, SerializationType.ComponentReference, ((Component)fieldValue)?.UUID ?? UUID.Zero); + } + else + { + if (fieldValue == null) + { + AddField(fieldName, SerializationType.ObjectReference, UUID.Zero); + } + else + { + var (context, newContext) = GetSerializedObject(fieldValue); + + if (newContext) + { + context.SerializeFields(fieldValue); + } + + AddField(fieldName, SerializationType.ObjectReference, context._id); + } + } + } + } + public class SerializationContext { StringBuilder _builder = new(); @@ -257,11 +496,17 @@ public static class EntitySerializer } } - public static void Serialize(Entity entity, SerializationContext context) - { - context ??= new SerializationContext(); + //public static void Serialize(Entity entity, SerializationContext context) + //{ + // context ??= new SerializationContext(); - context.Serialize($"Entity: {entity.UUID}", typeof(Entity), entity, false); + // context.Serialize($"Entity: {entity.UUID}", typeof(Entity), entity, false); + //} + + public static void Serialize(Entity entity, IntPtr internalContext) + { + SerializedObject obj = new SerializedObject(internalContext, UUID.Zero, new Dictionary()); + obj.Serialize(entity); } public static bool SerializeField(FieldInfo fieldInfo)