Serialize Data

This commit is contained in:
Simon Lübeß
2023-12-23 00:44:04 +01:00
parent a6f68a59d6
commit c9116812b4
6 changed files with 504 additions and 12 deletions
+9 -2
View File
@@ -866,7 +866,9 @@ namespace GlitchyEditor
/// Starts the play mode for the current scene
private void OnScenePlay()
{
// TODO: Dictionary<UUID, ScriptData> scriptData = _editorScene.SerializeScripts();
Dictionary<UUID, SerializedObject> serializedData = scope .();
ScriptEngine.SerializeScriptInstances(serializedData);
_editor.SceneViewportWindow.EditorMode = false;
_sceneState = .Play;
@@ -879,13 +881,18 @@ namespace GlitchyEditor
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.
+31 -4
View File
@@ -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<UUID, SerializedObject> 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<UUID, SerializedObject> allObjects)
{
for (let (id, script) in _entityScriptInstances)
{
if (script.ScriptClass.ClassName != "SerializationTest")
continue;
if (allObjects.TryGetValue(id, let object))
{
object.Deserialize(script);
}
}
}
}
+31
View File
@@ -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<T>(String name, T method) where T : var
@@ -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<UUID, SerializedObject> AllObjects;
private List<String> _ownedString = new List<String>() ~ DeleteContainerAndItems!(_);
public append Dictionary<StringView, (SerializationType PrimitiveType, uint8[16] Data)> Fields = .();
public this(Dictionary<UUID, SerializedObject> 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);
}
}
+11
View File
@@ -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
}
+250 -5
View File
@@ -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<string> _structScope = new();
private string _structScopeName;
public Dictionary<object, SerializedObject> SerializedClasses;
public SerializedObject(IntPtr internalContext, UUID id, Dictionary<object, SerializedObject> 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<object, SerializedObject>());
obj.Serialize(entity);
}
public static bool SerializeField(FieldInfo fieldInfo)