From 2f86f751a42dc52810197d681548c81aefb662fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Mon, 25 Dec 2023 21:46:19 +0100 Subject: [PATCH] Serialize Enums (and maybe Entitiy/Component refs) --- .../src/EditWindows/ComponentEditWindow.bf | 26 ----- GlitchyEngine/src/Scripting/ScriptGlue.bf | 4 +- .../src/Scripting/ScriptInstanceSerializer.bf | 70 ++++++++------ ScriptCore/Entity.cs | 32 ++++++- ScriptCore/ScriptGlue.cs | 2 +- ScriptCore/Serialization/EntitySerializer.cs | 94 ++++++++++++------- 6 files changed, 135 insertions(+), 93 deletions(-) diff --git a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf index 68a8c39..b231021 100644 --- a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf +++ b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf @@ -568,32 +568,6 @@ namespace GlitchyEditor.EditWindows ScriptEngine.ShowScriptEditor(entity, scriptComponent); } - private static void ShowEnumSelector(ScriptFieldInstance* field, StringView fieldName, ScriptClass scriptClass) - { - ScriptField scriptField = scriptClass.Fields[fieldName]; - SharpEnum enumType = scriptField.SharpType as SharpEnum; - - Log.EngineLogger.Assert(enumType != null, "Enum must have a SharpEnum!"); - - // Simply get Enum as a uint64 - var fieldValue = field.GetData(); - - StringView valueName = ""; - - if (enumType.Values.TryGetValue(fieldValue, let enumValue)) - valueName = enumValue.Name; - - if (ImGui.BeginCombo(fieldName.Ptr, valueName.Ptr)) - { - for (let (entryValue, enumEntry) in enumType.Values) - { - if (ImGui.Selectable(enumEntry.Name.Ptr, fieldValue == entryValue)) - field.SetData(entryValue); - } - - ImGui.EndCombo(); - } - } private static Entity? ShowEntitySelector() { diff --git a/GlitchyEngine/src/Scripting/ScriptGlue.bf b/GlitchyEngine/src/Scripting/ScriptGlue.bf index 27a1642..3530d74 100644 --- a/GlitchyEngine/src/Scripting/ScriptGlue.bf +++ b/GlitchyEngine/src/Scripting/ScriptGlue.bf @@ -536,7 +536,7 @@ static class ScriptGlue #region Serialization [RegisterCall("ScriptGlue::Serialization_SerializeField")] - static void Serialization_SerializeField(void* serializationContext, SerializationType type, MonoString* nameObject, MonoObject* valueObject) + static void Serialization_SerializeField(void* serializationContext, SerializationType type, MonoString* nameObject, MonoObject* valueObject, MonoString* fullTypeName) { SerializedObject context = Internal.UnsafeCastToObject(serializationContext) as SerializedObject; @@ -544,7 +544,7 @@ static class ScriptGlue char8* name = Mono.mono_string_to_utf8(nameObject); - context.AddField(StringView(name), type, valueObject); + context.AddField(StringView(name), type, valueObject, fullTypeName); Mono.mono_free(name); } diff --git a/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf b/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf index da244a6..bb9fc2c 100644 --- a/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf +++ b/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf @@ -70,6 +70,20 @@ public enum SerializationType : int32 class SerializedObject { + [Union] + public struct FieldData + { + public uint8[16] RawData; + public StringView StringView; + public (String Type, UUID ID) EngineObject; + + static this() + { + // This is important, because we expect 16 Bytes on the C# side + Compiler.Assert(sizeof(Self) == 16); + } + } + /// ID used to identify the object represented by this SerializedObject. In case that the represented object is a /// Script Instance, the ID is the UUID of the Entity, random otherwise. /// Todo: This sucks because we don't know all UUIDs beforehand and might accidentally assign the ID of an Entity to some class @@ -81,7 +95,7 @@ class SerializedObject private List _ownedString = new List() ~ DeleteContainerAndItems!(_); - public append Dictionary Fields = .(); + public append Dictionary Fields = .(); [AllowAppend] public this(Dictionary allObjects, StringView? typeName, UUID? id = null) @@ -109,12 +123,13 @@ class SerializedObject TypeName = typeNameCopy; } - public void AddField(StringView name, SerializationType primitiveType, MonoObject* value) + public void AddField(StringView name, SerializationType primitiveType, MonoObject* value, MonoString* fullTypeName) { - uint8[16] data = .(); + FieldData data = .(); - if (primitiveType == .String) + switch (primitiveType) { + case .String, .Enum: // If the string is null, we store a nullptr and 0-length StringView valueView = StringView(null, 0); @@ -132,29 +147,25 @@ class SerializedObject valueView = stringValue; } - Internal.MemCpy(&data, &valueView, sizeof(StringView)); - } - else if (primitiveType == .Enum) - { - MonoString* string = (.)value; - - char8* rawEnumValue = Mono.mono_string_to_utf8(string); + data.StringView = valueView; + case .EntityReference | .ComponentReference: + String typeName = null; - String enumValue = new String(rawEnumValue); + if (fullTypeName != null) + { + char8* rawTypeName = Mono.mono_string_to_utf8(fullTypeName); + typeName = new String(rawTypeName); - _ownedString.Add(enumValue); + _ownedString.Add(typeName); - Mono.mono_free(rawEnumValue); - - StringView valueView = enumValue; + Mono.mono_free(rawTypeName); + } - Internal.MemCpy(&data, &valueView, sizeof(StringView)); - } - else - { + data.EngineObject = (Type: typeName, ID: *(UUID*)Mono.mono_object_unbox(value)); + default: void* rawValue = Mono.mono_object_unbox(value); - Internal.MemCpy(&data, rawValue, primitiveType.GetSize()); + Internal.MemCpy(&data.RawData, rawValue, primitiveType.GetSize()); String nameCopy = new String(name); _ownedString.Add(nameCopy); @@ -177,19 +188,16 @@ class SerializedObject switch (field.PrimitiveType) { case .String, .Enum: -#unwarn - StringView view = *(StringView*)&field.Data; + *(StringView*)target = field.Data.StringView; + case .EntityReference | .ComponentReference: + char8* typeNamePtr = field.Data.EngineObject.Type.Ptr; - char8* stringPtr = view.Ptr; - int stringLen = view.Length; - - // We just pass the raw utf8-Pointer and length to C# - Internal.MemCpy(target, &stringPtr, sizeof(void*)); - Internal.MemCpy(target + 8, &stringLen, sizeof(int)); + *(UUID*)target = field.Data.EngineObject.ID; + *(char8**)(target + sizeof(UUID)) = typeNamePtr; default: - // Most values can simply be copied, the conversion will be done in C# + // Most values can simply be copied, the conversion will be done in C# #unwarn - Internal.MemCpy(target, &field.Data, 16); + Internal.MemCpy(target, &field.Data.RawData, sizeof(FieldData)); } } diff --git a/ScriptCore/Entity.cs b/ScriptCore/Entity.cs index 8ad00e8..af900d0 100644 --- a/ScriptCore/Entity.cs +++ b/ScriptCore/Entity.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Runtime.CompilerServices; using GlitchyEngine.Core; using GlitchyEngine.Extensions; @@ -101,7 +102,7 @@ public class Entity : EngineObject if (HasComponent(componentType)) { - return Activator.CreateInstance(componentType) as Component; + return Activator.CreateInstance(componentType, true, _uuid) as Component; } return null; @@ -291,6 +292,35 @@ public class Entity : EngineObject return scriptInstance as T; } + /// + /// Returns the script of the given type, or null, if the entity has no script of the given type. + /// + /// The type of the script. + /// The script instance or null. + public object As(Type type) + { + Debug.Assert(type.IsSubclassOf(typeof(Entity))); + + ScriptGlue.Entity_GetScriptInstance(_uuid, out object scriptInstance); + + return scriptInstance; + } + + /// + /// Returns the script of the given type, or null, if the entity has no script of the given type. + /// + /// The id of the entity whose script instance shall be returned. + /// The type of the script. + /// The script instance or null. + internal static object GetScriptReference(UUID id, Type type) + { + Debug.Assert(type.IsSubclassOf(typeof(Entity))); + + ScriptGlue.Entity_GetScriptInstance(id, out object scriptInstance); + + return scriptInstance; + } + /// /// Destroys the entity and all it's children. /// diff --git a/ScriptCore/ScriptGlue.cs b/ScriptCore/ScriptGlue.cs index 6c58a1e..e49a338 100644 --- a/ScriptCore/ScriptGlue.cs +++ b/ScriptCore/ScriptGlue.cs @@ -140,7 +140,7 @@ internal static class ScriptGlue #region Serialization [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Serialization_SerializeField(IntPtr serializationContext, SerializationType type, string name, object value); + internal static extern void Serialization_SerializeField(IntPtr serializationContext, SerializationType type, string name, object value, string fullTypeName = null); [MethodImpl(MethodImplOptions.InternalCall)] internal static extern void Serialization_CreateObject(IntPtr currentContext, string fullTypeName, out IntPtr context, out UUID id); diff --git a/ScriptCore/Serialization/EntitySerializer.cs b/ScriptCore/Serialization/EntitySerializer.cs index f6a6454..becc292 100644 --- a/ScriptCore/Serialization/EntitySerializer.cs +++ b/ScriptCore/Serialization/EntitySerializer.cs @@ -93,11 +93,11 @@ public static class EntitySerializer _structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1); } - private void AddField(string fieldName, SerializationType serializationType, object value) + private void AddField(string fieldName, SerializationType serializationType, object value, string fullTypeName = null) { string completeFieldName = $"{_structScopeName}{fieldName}"; - ScriptGlue.Serialization_SerializeField(_internalContext, serializationType, completeFieldName, value); + ScriptGlue.Serialization_SerializeField(_internalContext, serializationType, completeFieldName, value, fullTypeName); } public void Serialize(Entity entity) @@ -228,11 +228,11 @@ public static class EntitySerializer { if (typeof(Entity).IsAssignableFrom(fieldType)) { - AddField(fieldName, SerializationType.EntityReference, ((Entity)fieldValue)?.UUID ?? UUID.Zero); + AddField(fieldName, SerializationType.EntityReference, ((Entity)fieldValue)?.UUID ?? UUID.Zero, fieldValue?.GetType().FullName); } else if (typeof(Component).IsAssignableFrom(fieldType)) { - AddField(fieldName, SerializationType.ComponentReference, ((Component)fieldValue)?.UUID ?? UUID.Zero); + AddField(fieldName, SerializationType.ComponentReference, ((Component)fieldValue)?.UUID ?? UUID.Zero, fieldValue?.GetType().FullName); } else { @@ -363,6 +363,20 @@ public static class EntitySerializer _structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1); } + [StructLayout(LayoutKind.Explicit)] + private struct DataHelper + { + [StructLayout(LayoutKind.Sequential)] + public struct EngineObjectReferenceHelper + { + public IntPtr FullTypeName; + public UUID Id; + } + + [FieldOffset(0)] + public EngineObjectReferenceHelper EngineObjectReference; + } + private unsafe object GetFieldValue(string fieldName, SerializationType serializationType) { string completeFieldName = $"{_structScopeName}{fieldName}"; @@ -372,6 +386,8 @@ public static class EntitySerializer byte* rawData = stackalloc byte[16]; //byte* rawData = (byte*)&backingFieldOnStack; + ref DataHelper dataHelper = ref Unsafe.AsRef(rawData); + ScriptGlue.Serialization_DeserializeField(_internalContext, serializationType, completeFieldName, rawData); string GetString() @@ -423,12 +439,10 @@ public static class EntitySerializer return *(decimal*)rawData; case SerializationType.Enum: - string value = GetString(); - // TODO! - return null; - + return GetString(); case SerializationType.EntityReference: case SerializationType.ComponentReference: + return dataHelper.EngineObjectReference; case SerializationType.ObjectReference: return *(UUID*)rawData; default: @@ -476,7 +490,7 @@ public static class EntitySerializer } else if (fieldType.IsEnum) { - // SerializeEnum(fieldName, fieldValue, fieldType); + newFieldValue = DeserializeEnum(field.Name, fieldType); } else if (fieldType.IsArray) { @@ -563,9 +577,21 @@ public static class EntitySerializer return GetFieldValue(fieldName, expectedType); } - private void DeserializeEnum(string fieldName, object fieldValue, Type fieldType) + private object DeserializeEnum(string fieldName, Type enumType) { - //AddField(fieldName, SerializationType.Enum, fieldValue.ToString()); + if (GetFieldValue(fieldName, SerializationType.Enum) is not string valueName) + return null; + + try + { + return Enum.Parse(enumType, valueName); + } + catch + { + Log.Error($"Failed to parse \"{valueName}\" as enum-type \"{enumType}\""); + } + + return null; } private object DeserializeStruct(string fieldName, object targetInstance) @@ -581,36 +607,40 @@ public static class EntitySerializer private object DeserializeClass(string fieldName, Type fieldType) { - if (typeof(Entity).IsAssignableFrom(fieldType)) + bool isEntity = typeof(Entity).IsAssignableFrom(fieldType); + bool isComponent = fieldType.IsSubclassOf(typeof(Component)); + + if (isEntity || isComponent) { - UUID id = (UUID)GetFieldValue(fieldName, SerializationType.EntityReference); - + var data = (DataHelper.EngineObjectReferenceHelper)GetFieldValue(fieldName, SerializationType.EntityReference); + + UUID id = data.Id; + if (id == UUID.Zero) return null; - Entity reference = new Entity(id); + string fullTypeName = Marshal.PtrToStringUni(data.FullTypeName); - if (fieldType.IsSubclassOf(typeof(Entity))) - return reference.As(); - - return reference; - } - else if (typeof(Component).IsAssignableFrom(fieldType)) - { - UUID id = (UUID)GetFieldValue(fieldName, SerializationType.ComponentReference); - - if (id == UUID.Zero) + Type type = GetTypeFromName(fullTypeName); + + if (type == null) return null; - // TODO: Get class + if (isEntity) + { + // We have to differentiate between simple Entity references and script instances + // (because we decided to use the same type for both, so we could have a field Entity which contains a script instance instead of an Entity reference) + if (type == typeof(Entity)) + return new Entity(id); - //Entity reference = new Entity(id); + return Entity.GetScriptReference(id, type); + } + else + { + Entity entity = new Entity(id); - //if (fieldType.IsSubclassOf(typeof(Entity))) - // return reference.As(); - - //return reference; - return null; + return entity.GetComponent(type); + } } else {