From f51d5f342efab5b71b266e6f776ddfa9b14fe59a Mon Sep 17 00:00:00 2001 From: Syntriax Date: Sun, 20 Apr 2025 20:07:04 +0300 Subject: [PATCH] chore: added a generic converter --- .../Converters/EntityConverter.cs | 28 ----- .../Converters/InstanceConverter.cs | 111 ++++++++++++++++++ .../Converters/StateEnableConverter.cs | 46 -------- .../IgnoreSerializationAttribute.cs | 5 +- Engine.Serialization/SerializeAttribute.cs | 4 + Engine.Serialization/Utils.cs | 36 ++++-- 6 files changed, 140 insertions(+), 90 deletions(-) delete mode 100644 Engine.Serialization/Converters/EntityConverter.cs create mode 100644 Engine.Serialization/Converters/InstanceConverter.cs delete mode 100644 Engine.Serialization/Converters/StateEnableConverter.cs create mode 100644 Engine.Serialization/SerializeAttribute.cs diff --git a/Engine.Serialization/Converters/EntityConverter.cs b/Engine.Serialization/Converters/EntityConverter.cs deleted file mode 100644 index 4e54caf..0000000 --- a/Engine.Serialization/Converters/EntityConverter.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Syntriax.Engine.Core; -using Syntriax.Engine.Core.Factory; - -using YamlDotNet.Core; -using YamlDotNet.Serialization; - -namespace Syntriax.Engine.Serialization; - -public class EntityConverter : IEngineTypeYamlConverter -{ - public bool Accepts(Type type) => typeof(IEntity).IsAssignableFrom(type); - - public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) - { - EntityReference entityReference = (EntityReference)rootDeserializer(typeof(EntityReference))!; - - IEntity entity = (IEntity)TypeFactory.Get(entityReference.TypeContainer.Type); - - return entity; - } - - public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) - { - IEntity? entity = (IEntity)value!; - - serializer(new EntityReference(entity), typeof(EntityReference)); - } -} diff --git a/Engine.Serialization/Converters/InstanceConverter.cs b/Engine.Serialization/Converters/InstanceConverter.cs new file mode 100644 index 0000000..faa34e2 --- /dev/null +++ b/Engine.Serialization/Converters/InstanceConverter.cs @@ -0,0 +1,111 @@ +using System.Collections; +using System.Reflection; + +using Syntriax.Engine.Core.Factory; + +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace Syntriax.Engine.Serialization; + +public class InstanceConverter : IEngineTypeYamlConverter +{ + public bool Accepts(Type type) => !type.IsPrimitive && type != typeof(string); + + public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) + { + parser.Consume(); + + TypeContainer typeContainer = (TypeContainer)rootDeserializer(typeof(TypeContainer))!; + + object instance = TypeFactory.Get(typeContainer.Type); + + while (!parser.TryConsume(out _)) + { + string key = parser.Consume().Value; + + if (type.GetField(key, BindingFlags.Instance | BindingFlags.NonPublic) is FieldInfo fieldInfo) + { + object? fieldValue = rootDeserializer(fieldInfo.FieldType); + fieldInfo.SetValue(instance, fieldValue); + } + else if (type.GetProperty(key) is PropertyInfo propertyInfo) + { + object? propertyValue = rootDeserializer(propertyInfo.PropertyType); + propertyInfo.SetValue(instance, propertyValue); + } + else + parser.SkipThisAndNestedEvents(); + } + + parser.TryConsume(out _); + + return instance; + } + + public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) + { + emitter.Emit(new MappingStart()); + serializer(new TypeContainer(value), typeof(TypeContainer)); + + TypeData typeData = Utils.GetTypeData(type); + + foreach (PropertyInfo propertyInfo in typeData.Properties) + { + if (propertyInfo.GetCustomAttribute() is not null) + continue; + + if ((propertyInfo.PropertyType.IsClass && propertyInfo.PropertyType != typeof(string)) || propertyInfo.PropertyType.IsAbstract) + continue; + + emitter.Emit(new Scalar(propertyInfo.Name)); + object? propertyValue = propertyInfo.GetValue(value); + + EmitValue(propertyValue, propertyInfo.PropertyType, emitter, serializer); + } + + foreach (FieldInfo fieldInfo in typeData.Fields) + { + if (fieldInfo.GetCustomAttribute() is not null) + continue; + + if (fieldInfo.GetCustomAttribute() is null) + continue; + + if ((fieldInfo.FieldType.IsClass && fieldInfo.FieldType != typeof(string)) || fieldInfo.FieldType.IsAbstract) + continue; + + emitter.Emit(new Scalar(fieldInfo.Name)); + object? fieldValue = fieldInfo.GetValue(value); + + EmitValue(fieldValue, fieldInfo.FieldType, emitter, serializer); + } + + emitter.Emit(new MappingEnd()); + } + + private static void EmitValue(object? value, Type declaredType, IEmitter emitter, ObjectSerializer serializer) + { + if (value is null) + { + emitter.Emit(new Scalar("")); + return; + } + + bool isSequence = Utils.IsEnumerable(declaredType); + + if (!isSequence) + { + serializer(value); + return; + } + + IEnumerable sequence = (IEnumerable)value; + + emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Block)); + foreach (object? item in sequence) + serializer(item); + emitter.Emit(new SequenceEnd()); + } +} diff --git a/Engine.Serialization/Converters/StateEnableConverter.cs b/Engine.Serialization/Converters/StateEnableConverter.cs deleted file mode 100644 index efc9b66..0000000 --- a/Engine.Serialization/Converters/StateEnableConverter.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Syntriax.Engine.Core; -using Syntriax.Engine.Core.Factory; - -using YamlDotNet.Core; -using YamlDotNet.Core.Events; -using YamlDotNet.Serialization; - -namespace Syntriax.Engine.Serialization; - -public class StateEnableConverter : IEngineTypeYamlConverter -{ - public bool Accepts(Type type) => typeof(IStateEnable).IsAssignableFrom(type); - - public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) - { - parser.Consume(); - - TypeContainer typeContainer = (TypeContainer)rootDeserializer(typeof(TypeContainer))!; - EntityReference entityReference = (EntityReference)rootDeserializer(typeof(EntityReference))!; - - if (parser.Consume().Value.CompareTo(nameof(IStateEnable.Enabled)) != 0) - throw new ArgumentException($"{nameof(IStateEnable)} mapping must have a {nameof(IStateEnable.Enabled)}"); - bool enabled = bool.Parse(parser.Consume().Value); - - parser.Consume(); - - IStateEnable stateEnable = (IStateEnable)TypeFactory.Get(typeContainer.Type); - stateEnable.Enabled = enabled; - stateEnable.Assign((IEntity)TypeFactory.Get(entityReference.TypeContainer.Type)); - - return stateEnable; - } - - public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) - { - IStateEnable stateEnable = (IStateEnable)value!; - - emitter.Emit(new MappingStart()); - serializer(new TypeContainer(stateEnable), typeof(TypeContainer)); - emitter.Emit(new Scalar(nameof(StateEnable.Entity))); - serializer(new EntityReference(stateEnable.Entity), typeof(EntityReference)); - emitter.Emit(new Scalar(nameof(IStateEnable.Enabled))); - emitter.Emit(new Scalar(stateEnable.Enabled.ToString())); - emitter.Emit(new MappingEnd()); - } -} diff --git a/Engine.Serialization/IgnoreSerializationAttribute.cs b/Engine.Serialization/IgnoreSerializationAttribute.cs index 6604941..1b8499a 100644 --- a/Engine.Serialization/IgnoreSerializationAttribute.cs +++ b/Engine.Serialization/IgnoreSerializationAttribute.cs @@ -1,5 +1,4 @@ namespace Syntriax.Engine.Serialization; -public class IgnoreSerializationAttribute : Attribute -{ -} +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)] +public class IgnoreSerializationAttribute : Attribute; diff --git a/Engine.Serialization/SerializeAttribute.cs b/Engine.Serialization/SerializeAttribute.cs new file mode 100644 index 0000000..eed67f3 --- /dev/null +++ b/Engine.Serialization/SerializeAttribute.cs @@ -0,0 +1,4 @@ +namespace Syntriax.Engine.Serialization; + +[AttributeUsage(AttributeTargets.Field)] +public class SerializeAttribute : Attribute; diff --git a/Engine.Serialization/Utils.cs b/Engine.Serialization/Utils.cs index fa14510..c021ed2 100644 --- a/Engine.Serialization/Utils.cs +++ b/Engine.Serialization/Utils.cs @@ -4,28 +4,38 @@ namespace Syntriax.Engine.Serialization; internal static class Utils { - internal static TypeData GetTypeData(this Type type) + internal static bool IsEnumerable(this Type type) => typeof(System.Collections.IEnumerable).IsAssignableFrom(type) && type != typeof(string); + + internal static TypeData GetTypeData(this Type objectType) { - IEnumerable eventInfos = type.GetEvents(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) - .OrderBy(ei => ei.Name) - .AsEnumerable(); - IEnumerable fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) - .Where(fi => !eventInfos.Any(ei => fi.Name.CompareTo(ei.Name) == 0)) + // IEnumerable eventInfos = objectType.GetEvents(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) + // .OrderBy(ei => ei.Name) + // .ToList(); + List fieldInfos = objectType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) .OrderBy(ei => ei.Name) //ei => ei.FieldType.IsPrimitive || ei.FieldType == typeof(string)) // .ThenByDescending(ei => ei.Name) - .AsEnumerable(); - IEnumerable propertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) + .ToList(); + + List propertyInfos = objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public) .Where(pi => pi.SetMethod is not null) .OrderBy(ei => ei.Name)// ei => ei.PropertyType.IsPrimitive || ei.PropertyType == typeof(string)) // .ThenByDescending(ei => ei.Name) - .AsEnumerable(); + .ToList(); - return new TypeData(eventInfos, fieldInfos, propertyInfos); + propertyInfos.AddRange( + objectType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic) + .Where(pi => pi.SetMethod is not null && pi.GetCustomAttribute() is not null) + .OrderBy(ei => ei.Name)// ei => ei.PropertyType.IsPrimitive || ei.PropertyType == typeof(string)) + // .ThenByDescending(ei => ei.Name) + .ToList() + ); + + return new TypeData(fieldInfos, propertyInfos); } } -internal record struct TypeData(IEnumerable Events, IEnumerable Fields, IEnumerable Properties) +internal record struct TypeData(IEnumerable Fields, IEnumerable Properties) { - public static implicit operator (IEnumerable events, IEnumerable fields, IEnumerable properties)(TypeData value) => (value.Events, value.Fields, value.Properties); - public static implicit operator TypeData((IEnumerable events, IEnumerable fields, IEnumerable properties) value) => new TypeData(value.events, value.fields, value.properties); + public static implicit operator (IEnumerable fields, IEnumerable properties)(TypeData value) => (value.Fields, value.Properties); + public static implicit operator TypeData((IEnumerable fields, IEnumerable properties) value) => new(value.fields, value.properties); }