diff --git a/GlitchyEngine/src/Scripting/EntitySerializerWrapper.bf b/GlitchyEngine/src/Scripting/EntitySerializerWrapper.bf new file mode 100644 index 0000000..e158858 --- /dev/null +++ b/GlitchyEngine/src/Scripting/EntitySerializerWrapper.bf @@ -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); + } +} diff --git a/GlitchyEngine/src/Scripting/ScriptClass.bf b/GlitchyEngine/src/Scripting/ScriptClass.bf index bacc290..cbbf039 100644 --- a/GlitchyEngine/src/Scripting/ScriptClass.bf +++ b/GlitchyEngine/src/Scripting/ScriptClass.bf @@ -231,23 +231,6 @@ class ScriptClass : SharpClass return *(T*)Mono.mono_object_unbox(object); } - public T GetFieldValue(MonoObject* instance, MonoClassField* field) - { - T value = default; - Mono.mono_field_get_value(instance, field, &value); - return value; - } - - public void SetFieldValue(MonoObject* instance, MonoClassField* field, in T value) - { - Mono.mono_field_set_value(instance, field, &value); - } - - public void SetFieldValue(MonoObject* instance, MonoClassField* field, in T value) where T : struct* - { - Mono.mono_field_set_value(instance, field, value); - } - public MonoObject* BoxValue(in T value) { return Mono.mono_value_box(ScriptEngine.[Friend]s_AppDomain, _monoClass, &value); diff --git a/GlitchyEngine/src/Scripting/ScriptEngine.bf b/GlitchyEngine/src/Scripting/ScriptEngine.bf index 89bc5e9..f6f8b3e 100644 --- a/GlitchyEngine/src/Scripting/ScriptEngine.bf +++ b/GlitchyEngine/src/Scripting/ScriptEngine.bf @@ -22,7 +22,7 @@ class EngineClasses private EntityEditorWrapper s_EntityEditor ~ _?.ReleaseRef(); - private ScriptClass s_EntitySerializer ~ _?.ReleaseRef(); + private EntitySerializerWrapper s_EntitySerializer ~ _?.ReleaseRef(); private ScriptClass s_SerializationContext ~ _?.ReleaseRef(); private ScriptClass s_Collision2D ~ _?.ReleaseRef(); @@ -35,7 +35,7 @@ class EngineClasses public EntityEditorWrapper EntityEditor => s_EntityEditor; - public ScriptClass EntitySerializer => s_EntitySerializer; + public EntitySerializerWrapper EntitySerializer => s_EntitySerializer; public ScriptClass Collision2D => s_Collision2D; @@ -68,7 +68,7 @@ class EngineClasses // Editor classes 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); @@ -116,7 +116,7 @@ static class ScriptEngine private static FileSystemWatcher _userAssemblyWatcher ~ delete _; // TODO: This should be a global setting somewhere - private static bool _debuggingEnabled = false; + private static bool _debuggingEnabled = true; private static String _appAssemblyPath = new .() ~ delete _; diff --git a/GlitchyEngine/src/Scripting/ScriptGlue.bf b/GlitchyEngine/src/Scripting/ScriptGlue.bf index 242e554..0bf2f00 100644 --- a/GlitchyEngine/src/Scripting/ScriptGlue.bf +++ b/GlitchyEngine/src/Scripting/ScriptGlue.bf @@ -1143,7 +1143,7 @@ static class ScriptGlue } [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; @@ -1151,7 +1151,7 @@ static class ScriptGlue 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); diff --git a/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf b/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf index 9988385..2aee668 100644 --- a/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf +++ b/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf @@ -31,19 +31,31 @@ public class ScriptInstanceSerializer { 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. 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); } + /// 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. public void DeserializeScriptInstances() { @@ -53,6 +65,11 @@ public class ScriptInstanceSerializer { 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. @@ -69,6 +86,25 @@ public class ScriptInstanceSerializer 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. public void FixupSerializedIds(Dictionary originalToCopyIds) { diff --git a/GlitchyEngine/src/Serialization/SerializedObject.bf b/GlitchyEngine/src/Serialization/SerializedObject.bf index 5cd48a0..5bd918f 100644 --- a/GlitchyEngine/src/Serialization/SerializedObject.bf +++ b/GlitchyEngine/src/Serialization/SerializedObject.bf @@ -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 public UUID Id; + public bool IsStatic; + public String TypeName ~ delete:append _; public ScriptInstanceSerializer Serializer; @@ -42,12 +44,14 @@ class SerializedObject public append Dictionary Fields = .(); [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); Serializer = serializer; + IsStatic = isStatic; + if (id == null) { // 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) { - 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); + ScriptEngine.Classes.EntitySerializer.Serialize(scriptInstance, this); } 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; - deserialize(scriptInstance.[Friend]_instance, thisPtr, &exception); - - if (exception != null) - ScriptEngine.[Friend]HandleMonoException(exception, scriptInstance); + public void DeserializeStaticFields(ScriptClass scriptClass) + { + ScriptEngine.Classes.EntitySerializer.DeserializeStatic(scriptClass, this); } static this @@ -374,7 +369,7 @@ class SerializedObject Try!(Deserialize.Value(reader, "ID", let objectId, environment)); - SerializedObject object = new SerializedObject(scriptSerializer, type, objectId); + SerializedObject object = new SerializedObject(scriptSerializer, false, type, objectId); while (reader.ObjectHasMore()) { diff --git a/GlitchyEngine/src/World/SceneSerializer.bf b/GlitchyEngine/src/World/SceneSerializer.bf index 7fa7923..84396e5 100644 --- a/GlitchyEngine/src/World/SceneSerializer.bf +++ b/GlitchyEngine/src/World/SceneSerializer.bf @@ -64,9 +64,6 @@ class SceneSerializer using (writer.ObjectBlock()) { - // TODO: Scene name goes here! - Serialize.Value(writer, "Name", "Scene name here pls!!!"); - Serialize.Value(writer, "Version", FileVersion); writer.Identifier("Entities"); @@ -93,7 +90,30 @@ class SceneSerializer { 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": Try!(DeserializeEntities(reader, loadAsPrefab)); case "ReferencedObjects": - Try!(DeserializeReferencedObjects(reader)); + Try!(DeserializeReferencedObjects(reader, false)); + case "StaticFields": + Try!(DeserializeReferencedObjects(reader, true)); default: Log.EngineLogger.Warning($"Encountered unexpected Identifier \"{identifier}\"."); } @@ -390,14 +412,16 @@ class SceneSerializer return .Ok; } - private Result DeserializeReferencedObjects(BonReader reader) + private Result DeserializeReferencedObjects(BonReader reader, bool isStatic) { Try!(reader.ArrayBlock()); while (reader.ArrayHasMore()) { - Try!(SerializedObject.BonDeserialize(reader, _scriptSerializer, _fileVersion, gBonEnv)); - + SerializedObject object = Try!(SerializedObject.BonDeserialize(reader, _scriptSerializer, _fileVersion, gBonEnv)); + + object.IsStatic = isStatic; + TryEndEntry(reader); } diff --git a/ScriptCore/Editor/EntityEditor.cs b/ScriptCore/Editor/EntityEditor.cs index 255cbd8..d277386 100644 --- a/ScriptCore/Editor/EntityEditor.cs +++ b/ScriptCore/Editor/EntityEditor.cs @@ -145,6 +145,22 @@ internal class EntityEditor return false; } + + public static bool ShowButtonInEditor(MethodInfo methodInfo) + { + var showButton = methodInfo.GetCustomAttribute(); + + 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; + } /// /// Entry point for the engine to show the editor for the given script instance. @@ -908,103 +924,119 @@ internal class EntityEditor return newValue; } - public static void ShowEditor(Type type, object? reference) + private static void ShowEditor(Type type, object? reference) { int i = 0; - + // 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++; ImGui.PushID(i); - if (ShowFieldInEditor(field)) + switch (member) { - object value = field.GetValue(reference); - - IEnumerable attributes = field.GetCustomAttributes(); - - LabelAttribute? label = GetAttribute(attributes); - - 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); - } + case FieldInfo field: + ShowField(field, reference); + break; + case MethodInfo method: + ShowButton(method, reference); + break; } ImGui.PopID(); } - ShowButtons(type, reference); - } - - /// - /// Shows all buttons in the UI. - /// - private static void ShowButtons(Type type, object? reference) - { - ImGui.PushID("Buttons"); - - int i = 0; - - // Iterate all methods - foreach (MethodInfo method in type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) - { - var showButton = method.GetCustomAttribute(); - - if (showButton == null) - continue; - - if (Application.IsInEditMode && !showButton.Visibility.HasFlag(ButtonVisibility.InEditMode)) - continue; + bool showedHeader = false; - if (Application.IsInPlayMode && !showButton.Visibility.HasFlag(ButtonVisibility.InPlayMode)) - continue; + 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); - 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); - } - } - + ShowField(field, null); + ImGui.PopID(); } - ImGui.PopID(); + if (showedHeader) + { + ImGui.TreePop(); + } + } + + private static void ShowField(FieldInfo field, object? reference) + { + if (!ShowFieldInEditor(field)) + return; + + object value = field.GetValue(reference); + + IEnumerable attributes = field.GetCustomAttributes(); + + LabelAttribute? label = GetAttribute(attributes); + + 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); + } + } + + private static void ShowButton(MethodInfo method, object? reference) + { + if (!ShowButtonInEditor(method)) + return; + + var showButton = method.GetCustomAttribute(); + + 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 diff --git a/ScriptCore/Extensions/FieldInfoExtension.cs b/ScriptCore/Extensions/FieldInfoExtension.cs deleted file mode 100644 index 6aab1d6..0000000 --- a/ScriptCore/Extensions/FieldInfoExtension.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text; - -namespace GlitchyEngine.Extensions; - -/// -/// Extends the -class with useful methods. -/// -public static class FieldInfoExtension -{ - /// - /// Returns if the field has the specified attribute. - /// - /// The type of the attribte. - public static bool HasCustomAttribute(this FieldInfo fiedInfo) where T : Attribute - { - return fiedInfo.GetCustomAttribute() != null; - } -} diff --git a/ScriptCore/Extensions/MemberInfoExtension.cs b/ScriptCore/Extensions/MemberInfoExtension.cs new file mode 100644 index 0000000..43fde12 --- /dev/null +++ b/ScriptCore/Extensions/MemberInfoExtension.cs @@ -0,0 +1,19 @@ +using System; +using System.Reflection; + +namespace GlitchyEngine.Extensions; + +/// +/// Extends with useful methods. +/// +public static class MemberInfoExtension +{ + /// + /// Returns if the member has the specified attribute. + /// + /// The type of the attribute. + public static bool HasCustomAttribute(this MemberInfo memberInfo) where T : Attribute + { + return memberInfo.GetCustomAttribute() != null; + } +} diff --git a/ScriptCore/Extensions/TypeExtension.cs b/ScriptCore/Extensions/TypeExtension.cs index 60b0b0a..f689acd 100644 --- a/ScriptCore/Extensions/TypeExtension.cs +++ b/ScriptCore/Extensions/TypeExtension.cs @@ -227,4 +227,17 @@ public static class TypeExtension return nameBuilder.ToString(); } + + /// + /// Returns if the type has at least one static field; otherwise. + /// + public static bool HasStaticFields(this Type type) + { + foreach (FieldInfo _ in type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + { + return true; + } + + return false; + } } diff --git a/ScriptCore/ScriptGlue.cs b/ScriptCore/ScriptGlue.cs index 638a9ad..c615ba7 100644 --- a/ScriptCore/ScriptGlue.cs +++ b/ScriptCore/ScriptGlue.cs @@ -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); [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)] public static extern unsafe void Serialization_DeserializeField(IntPtr internalContext, SerializationType expectedType, string fieldName, byte* value, out SerializationType actualType); diff --git a/ScriptCore/Serialization/DeserializationObject.cs b/ScriptCore/Serialization/DeserializationObject.cs index 96c3373..78c7a1a 100644 --- a/ScriptCore/Serialization/DeserializationObject.cs +++ b/ScriptCore/Serialization/DeserializationObject.cs @@ -28,6 +28,8 @@ public class DeserializationObject private IntPtr _internalContext; + private bool _isStatic; + private UUID _id; private Stack _structScope = new(); @@ -57,9 +59,12 @@ public class DeserializationObject } } - public DeserializationObject(IntPtr internalContext, UUID id, Dictionary deserializedClasses) + public bool IsStatic => _isStatic; + + public DeserializationObject(IntPtr internalContext, bool isStatic, UUID id, Dictionary deserializedClasses) { _internalContext = internalContext; + _isStatic = isStatic; _id = id; DeserializedClasses = deserializedClasses; } @@ -114,7 +119,7 @@ public class DeserializationObject return null; } - context = new DeserializationObject(contextPtr, id, DeserializedClasses); + context = new DeserializationObject(contextPtr, false, id, DeserializedClasses); DeserializedClasses.Add(id, context); @@ -250,6 +255,36 @@ public class DeserializationObject 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) { @@ -259,7 +294,7 @@ public class DeserializationObject foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) { - if (!EntitySerializer.SerializeField(field)) + if (!EntitySerializer.SerializeField(field, false)) continue; object currentValue = field.GetValue(obj); diff --git a/ScriptCore/Serialization/EntitySerializer.cs b/ScriptCore/Serialization/EntitySerializer.cs index 2570273..29ad427 100644 --- a/ScriptCore/Serialization/EntitySerializer.cs +++ b/ScriptCore/Serialization/EntitySerializer.cs @@ -7,102 +7,40 @@ using GlitchyEngine.Extensions; 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 void Serialize(Entity entity, IntPtr internalContext) { - SerializedObject obj = new SerializedObject(internalContext, UUID.Zero, new Dictionary()); + SerializedObject obj = new SerializedObject(internalContext, false, UUID.Zero, new Dictionary()); obj.Serialize(entity); } internal static void Deserialize(Entity entity, IntPtr internalContext) { - DeserializationObject obj = new DeserializationObject(internalContext, UUID.Zero, new Dictionary()); + DeserializationObject obj = new DeserializationObject(internalContext, false, UUID.Zero, new Dictionary()); obj.Deserialize(entity); } + + internal static void SerializeStaticFields(Type type, IntPtr internalContext) + { + SerializedObject obj = new SerializedObject(internalContext, true, UUID.Zero, new Dictionary()); + obj.Serialize(type); + } + + internal static void DeserializeStaticFields(Type type, IntPtr internalContext) + { + DeserializationObject obj = new DeserializationObject(internalContext, true, UUID.Zero, new Dictionary()); + obj.Deserialize(type); + } /// /// Returns whether or not the field described by the given should be serialized or not. /// - internal static bool SerializeField(FieldInfo fieldInfo) + /// The field info for which to check whether the field should be serialized or not. + /// If the only Static fields can be serialized; if only instance fields can be serialized. + internal static bool SerializeField(FieldInfo fieldInfo, bool staticSerialization) { - // TODO: We want to be able to serialize static fields in the future! - if (fieldInfo.IsStatic) + if (staticSerialization != fieldInfo.IsStatic) return false; var serializeField = fieldInfo.HasCustomAttribute(); diff --git a/ScriptCore/Serialization/SerializationType.cs b/ScriptCore/Serialization/SerializationType.cs new file mode 100644 index 0000000..b66fc4d --- /dev/null +++ b/ScriptCore/Serialization/SerializationType.cs @@ -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 + }; +} diff --git a/ScriptCore/Serialization/SerializedObject.cs b/ScriptCore/Serialization/SerializedObject.cs index a0e8a99..80458f8 100644 --- a/ScriptCore/Serialization/SerializedObject.cs +++ b/ScriptCore/Serialization/SerializedObject.cs @@ -19,16 +19,21 @@ public class SerializedObject private Stack _structScope = new(); private string _structScopeName = ""; + + private bool _isStatic; private delegate void SerializeMethod(SerializedObject container, string fieldName, object? fieldValue, Type fieldType); private static Dictionary _customSerializers = new(); public UUID Id => _id; + + public bool IsStatic => _isStatic; - public SerializedObject(IntPtr internalContext, UUID id, Dictionary serializedClasses) + public SerializedObject(IntPtr internalContext, bool isStatic, UUID id, Dictionary serializedClasses) { _internalContext = internalContext; + _isStatic = isStatic; _id = id; _serializedClasses = serializedClasses; } @@ -40,9 +45,9 @@ public class SerializedObject if (_serializedClasses.TryGetValue(o, out context)) 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); @@ -95,16 +100,21 @@ public class SerializedObject public void Serialize(Entity entity) { - SerializeFields(entity); + SerializeInstanceFields(entity); + } + + public void Serialize(Type type) + { + SerializeStaticTypeFields(type); } - public void SerializeFields(object obj) + public void SerializeInstanceFields(object obj) { Type type = obj.GetType(); foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) { - if (!EntitySerializer.SerializeField(field)) + if (!EntitySerializer.SerializeField(field, false)) continue; object? fieldValue = field.GetValue(obj); @@ -113,6 +123,20 @@ public class SerializedObject SerializeField(field.Name, fieldValue, fieldType); } } + + 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) { @@ -285,7 +309,7 @@ public class SerializedObject { PushScope(fieldName); - SerializeFields(fieldValue); + SerializeInstanceFields(fieldValue); PopScope(); } @@ -312,7 +336,7 @@ public class SerializedObject if (newContext) { - context.SerializeFields(fieldValue); + context.SerializeInstanceFields(fieldValue); } AddField(fieldName, SerializationType.ObjectReference, context._id);