Serialize Enums (and maybe Entitiy/Component refs)

This commit is contained in:
Simon Lübeß
2023-12-25 21:46:19 +01:00
parent 215e1f6500
commit 2f86f751a4
6 changed files with 135 additions and 93 deletions
@@ -568,32 +568,6 @@ namespace GlitchyEditor.EditWindows
ScriptEngine.ShowScriptEditor(entity, scriptComponent); 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<uint64>();
StringView valueName = "<Invalid Value>";
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() private static Entity? ShowEntitySelector()
{ {
+2 -2
View File
@@ -536,7 +536,7 @@ static class ScriptGlue
#region Serialization #region Serialization
[RegisterCall("ScriptGlue::Serialization_SerializeField")] [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; SerializedObject context = Internal.UnsafeCastToObject(serializationContext) as SerializedObject;
@@ -544,7 +544,7 @@ static class ScriptGlue
char8* name = Mono.mono_string_to_utf8(nameObject); 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); Mono.mono_free(name);
} }
@@ -70,6 +70,20 @@ public enum SerializationType : int32
class SerializedObject 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 /// 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. /// 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 /// 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<String> _ownedString = new List<String>() ~ DeleteContainerAndItems!(_); private List<String> _ownedString = new List<String>() ~ DeleteContainerAndItems!(_);
public append Dictionary<StringView, (SerializationType PrimitiveType, uint8[16] Data)> Fields = .(); public append Dictionary<StringView, (SerializationType PrimitiveType, FieldData Data)> Fields = .();
[AllowAppend] [AllowAppend]
public this(Dictionary<UUID, SerializedObject> allObjects, StringView? typeName, UUID? id = null) public this(Dictionary<UUID, SerializedObject> allObjects, StringView? typeName, UUID? id = null)
@@ -109,12 +123,13 @@ class SerializedObject
TypeName = typeNameCopy; 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 // If the string is null, we store a nullptr and 0-length
StringView valueView = StringView(null, 0); StringView valueView = StringView(null, 0);
@@ -132,29 +147,25 @@ class SerializedObject
valueView = stringValue; valueView = stringValue;
} }
Internal.MemCpy(&data, &valueView, sizeof(StringView)); data.StringView = valueView;
} case .EntityReference | .ComponentReference:
else if (primitiveType == .Enum) String typeName = null;
{
MonoString* string = (.)value;
char8* rawEnumValue = Mono.mono_string_to_utf8(string);
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); Mono.mono_free(rawTypeName);
}
StringView valueView = enumValue;
Internal.MemCpy(&data, &valueView, sizeof(StringView)); data.EngineObject = (Type: typeName, ID: *(UUID*)Mono.mono_object_unbox(value));
} default:
else
{
void* rawValue = Mono.mono_object_unbox(value); 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); String nameCopy = new String(name);
_ownedString.Add(nameCopy); _ownedString.Add(nameCopy);
@@ -177,19 +188,16 @@ class SerializedObject
switch (field.PrimitiveType) switch (field.PrimitiveType)
{ {
case .String, .Enum: case .String, .Enum:
#unwarn *(StringView*)target = field.Data.StringView;
StringView view = *(StringView*)&field.Data; case .EntityReference | .ComponentReference:
char8* typeNamePtr = field.Data.EngineObject.Type.Ptr;
char8* stringPtr = view.Ptr; *(UUID*)target = field.Data.EngineObject.ID;
int stringLen = view.Length; *(char8**)(target + sizeof(UUID)) = typeNamePtr;
// We just pass the raw utf8-Pointer and length to C#
Internal.MemCpy(target, &stringPtr, sizeof(void*));
Internal.MemCpy(target + 8, &stringLen, sizeof(int));
default: 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 #unwarn
Internal.MemCpy(target, &field.Data, 16); Internal.MemCpy(target, &field.Data.RawData, sizeof(FieldData));
} }
} }
+31 -1
View File
@@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using GlitchyEngine.Core; using GlitchyEngine.Core;
using GlitchyEngine.Extensions; using GlitchyEngine.Extensions;
@@ -101,7 +102,7 @@ public class Entity : EngineObject
if (HasComponent(componentType)) if (HasComponent(componentType))
{ {
return Activator.CreateInstance(componentType) as Component; return Activator.CreateInstance(componentType, true, _uuid) as Component;
} }
return null; return null;
@@ -291,6 +292,35 @@ public class Entity : EngineObject
return scriptInstance as T; return scriptInstance as T;
} }
/// <summary>
/// Returns the script of the given type, or null, if the entity has no script of the given type.
/// </summary>
/// <param name="type">The type of the script.</param>
/// <returns>The script instance or null.</returns>
public object As(Type type)
{
Debug.Assert(type.IsSubclassOf(typeof(Entity)));
ScriptGlue.Entity_GetScriptInstance(_uuid, out object scriptInstance);
return scriptInstance;
}
/// <summary>
/// Returns the script of the given type, or null, if the entity has no script of the given type.
/// </summary>
/// <param name="id">The id of the entity whose script instance shall be returned.</param>
/// <param name="type">The type of the script.</param>
/// <returns>The script instance or null.</returns>
internal static object GetScriptReference(UUID id, Type type)
{
Debug.Assert(type.IsSubclassOf(typeof(Entity)));
ScriptGlue.Entity_GetScriptInstance(id, out object scriptInstance);
return scriptInstance;
}
/// <summary> /// <summary>
/// Destroys the entity and all it's children. /// Destroys the entity and all it's children.
/// </summary> /// </summary>
+1 -1
View File
@@ -140,7 +140,7 @@ internal static class ScriptGlue
#region Serialization #region Serialization
[MethodImpl(MethodImplOptions.InternalCall)] [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)] [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, string fullTypeName, out IntPtr context, out UUID id);
+62 -32
View File
@@ -93,11 +93,11 @@ public static class EntitySerializer
_structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1); _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}"; string completeFieldName = $"{_structScopeName}{fieldName}";
ScriptGlue.Serialization_SerializeField(_internalContext, serializationType, completeFieldName, value); ScriptGlue.Serialization_SerializeField(_internalContext, serializationType, completeFieldName, value, fullTypeName);
} }
public void Serialize(Entity entity) public void Serialize(Entity entity)
@@ -228,11 +228,11 @@ public static class EntitySerializer
{ {
if (typeof(Entity).IsAssignableFrom(fieldType)) 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)) 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 else
{ {
@@ -363,6 +363,20 @@ public static class EntitySerializer
_structScopeName.Remove(_structScopeName.Length - scopeToRemove.Length - 1); _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) private unsafe object GetFieldValue(string fieldName, SerializationType serializationType)
{ {
string completeFieldName = $"{_structScopeName}{fieldName}"; string completeFieldName = $"{_structScopeName}{fieldName}";
@@ -372,6 +386,8 @@ public static class EntitySerializer
byte* rawData = stackalloc byte[16]; byte* rawData = stackalloc byte[16];
//byte* rawData = (byte*)&backingFieldOnStack; //byte* rawData = (byte*)&backingFieldOnStack;
ref DataHelper dataHelper = ref Unsafe.AsRef<DataHelper>(rawData);
ScriptGlue.Serialization_DeserializeField(_internalContext, serializationType, completeFieldName, rawData); ScriptGlue.Serialization_DeserializeField(_internalContext, serializationType, completeFieldName, rawData);
string GetString() string GetString()
@@ -423,12 +439,10 @@ public static class EntitySerializer
return *(decimal*)rawData; return *(decimal*)rawData;
case SerializationType.Enum: case SerializationType.Enum:
string value = GetString(); return GetString();
// TODO!
return null;
case SerializationType.EntityReference: case SerializationType.EntityReference:
case SerializationType.ComponentReference: case SerializationType.ComponentReference:
return dataHelper.EngineObjectReference;
case SerializationType.ObjectReference: case SerializationType.ObjectReference:
return *(UUID*)rawData; return *(UUID*)rawData;
default: default:
@@ -476,7 +490,7 @@ public static class EntitySerializer
} }
else if (fieldType.IsEnum) else if (fieldType.IsEnum)
{ {
// SerializeEnum(fieldName, fieldValue, fieldType); newFieldValue = DeserializeEnum(field.Name, fieldType);
} }
else if (fieldType.IsArray) else if (fieldType.IsArray)
{ {
@@ -563,9 +577,21 @@ public static class EntitySerializer
return GetFieldValue(fieldName, expectedType); 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) private object DeserializeStruct(string fieldName, object targetInstance)
@@ -581,36 +607,40 @@ public static class EntitySerializer
private object DeserializeClass(string fieldName, Type fieldType) 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) if (id == UUID.Zero)
return null; return null;
Entity reference = new Entity(id); string fullTypeName = Marshal.PtrToStringUni(data.FullTypeName);
if (fieldType.IsSubclassOf(typeof(Entity))) Type type = GetTypeFromName(fullTypeName);
return reference.As<Entity>();
if (type == null)
return reference;
}
else if (typeof(Component).IsAssignableFrom(fieldType))
{
UUID id = (UUID)GetFieldValue(fieldName, SerializationType.ComponentReference);
if (id == UUID.Zero)
return 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 entity.GetComponent(type);
// return reference.As<Entity>(); }
//return reference;
return null;
} }
else else
{ {