diff --git a/Engine.Serialization/Engine.Serialization.csproj b/Engine.Serialization/Engine.Serialization.csproj
new file mode 100644
index 0000000..9198433
--- /dev/null
+++ b/Engine.Serialization/Engine.Serialization.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+ enable
+ enable
+ Syntriax.Engine.Serialization
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Engine.Serialization/EntityFieldConverter.cs b/Engine.Serialization/EntityFieldConverter.cs
new file mode 100644
index 0000000..028edb5
--- /dev/null
+++ b/Engine.Serialization/EntityFieldConverter.cs
@@ -0,0 +1,172 @@
+using System.Collections;
+using System.Reflection;
+using Syntriax.Engine.Core;
+
+using YamlDotNet.Core;
+using YamlDotNet.Core.Events;
+using YamlDotNet.Serialization;
+using YamlDotNet.Serialization.NamingConventions;
+
+namespace Syntriax.Engine.Serialization;
+
+public class Vector2DConverter : IYamlTypeConverter
+{
+ public bool Accepts(Type type)
+ {
+ return type == typeof(Vector2D);
+ }
+
+ public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
+ {
+ parser.Consume();
+
+ float x = 0f;
+ float y = 0f;
+
+ while (!parser.TryConsume(out _))
+ {
+ string key = parser.Consume().Value;
+ string value = parser.Consume().Value;
+
+ if (key.CompareTo(nameof(Vector2D.X)) == 0) x = float.Parse(value);
+ if (key.CompareTo(nameof(Vector2D.Y)) == 0) y = float.Parse(value);
+ }
+
+ return new Vector2D(x, y);
+ }
+
+ public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
+ {
+ Vector2D vector2D = (Vector2D)value!;
+
+ emitter.Emit(new MappingStart());
+ emitter.Emit(new Scalar(nameof(Vector2D.X)));
+ emitter.Emit(new Scalar(vector2D.X.ToString()));
+ emitter.Emit(new Scalar(nameof(Vector2D.Y)));
+ emitter.Emit(new Scalar(vector2D.Y.ToString()));
+ emitter.Emit(new MappingEnd());
+ }
+}
+
+public class EntityConverter : IYamlTypeConverter
+{
+ private static readonly ISerializer serializer = new SerializerBuilder()
+ .WithNamingConvention(PascalCaseNamingConvention.Instance)
+ .DisableAliases()
+ .Build();
+
+ private static readonly IDeserializer deserializer = new DeserializerBuilder()
+ .WithNamingConvention(PascalCaseNamingConvention.Instance)
+ .Build();
+
+ public bool Accepts(Type type)
+ {
+ bool v = typeof(IEntity).IsAssignableFrom(type);
+ return v;
+ }
+
+ public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
+ {
+ string? id = null;
+
+ parser.Consume();
+
+ while (!parser.TryConsume(out _))
+ {
+ string key = parser.Consume().Value;
+ string value = parser.Consume().Value;
+
+ if (key.CompareTo(nameof(EntityReference.Id)) == 0)
+ id = value;
+ }
+
+ return new EntityReference() { Id = id };
+ }
+ public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
+ {
+ if (value is not IEntity entity)
+ return;
+
+ var typeData = GetTypeData(type);
+ emitter.Emit(new MappingStart());
+
+ foreach (var fieldInfo in typeData.Fields)
+ {
+ if (fieldInfo.GetCustomAttribute() != null)
+ continue;
+
+ emitter.Emit(new Scalar(fieldInfo.Name));
+ var fieldValue = fieldInfo.GetValue(entity);
+
+ EmitValue(fieldValue, fieldInfo.FieldType, emitter, serializer);
+ }
+
+ foreach (var propertyInfo in typeData.Properties)
+ {
+ emitter.Emit(new Scalar(propertyInfo.Name));
+ var propValue = propertyInfo.GetValue(entity);
+
+ EmitValue(propValue, propertyInfo.PropertyType, emitter, serializer);
+ }
+
+ emitter.Emit(new MappingEnd());
+ }
+ private void EmitValue(object? value, Type declaredType, IEmitter emitter, ObjectSerializer serializer)
+ {
+ if (value is null)
+ {
+ emitter.Emit(new Scalar("null"));
+ return;
+ }
+
+ if (value is IEntity singleEntity && !IsEnumerable(declaredType))
+ {
+ emitter.Emit(new Scalar(singleEntity.Id));
+ return;
+ }
+
+ if (IsEnumerable(declaredType) && value is System.Collections.IEnumerable list)
+ {
+ emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Block));
+ foreach (var item in list)
+ {
+ if (item is IEntity itemEntity)
+ emitter.Emit(new Scalar(itemEntity.Id));
+ else
+ serializer(item); // fallback for non-entity items
+ }
+ emitter.Emit(new SequenceEnd());
+ return;
+ }
+
+ // fallback
+ serializer(value);
+ }
+ private static TypeData GetTypeData(Type objectType)
+ {
+ IEnumerable eventInfos = objectType.GetEvents(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
+ .OrderBy(ei => ei.Name)
+ .AsEnumerable();
+ IEnumerable fieldInfos = objectType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
+ .Where(fi => !eventInfos.Any(ei => fi.Name.CompareTo(ei.Name) == 0))
+ .OrderBy(ei => ei.Name) //ei => ei.FieldType.IsPrimitive || ei.FieldType == typeof(string))
+ // .ThenByDescending(ei => ei.Name)
+ .AsEnumerable();
+ IEnumerable propertyInfos = objectType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | 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();
+
+ return new TypeData(eventInfos, fieldInfos, propertyInfos);
+ }
+ private static bool IsEnumerable(Type type)
+ {
+ return typeof(System.Collections.IEnumerable).IsAssignableFrom(type) && type != typeof(string);
+ }
+ private record struct TypeData(IEnumerable Events, 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);
+ }
+}
diff --git a/Engine.Serialization/EntityReference.cs b/Engine.Serialization/EntityReference.cs
new file mode 100644
index 0000000..218847d
--- /dev/null
+++ b/Engine.Serialization/EntityReference.cs
@@ -0,0 +1,6 @@
+namespace Syntriax.Engine.Serialization;
+
+public class EntityReference
+{
+ public string Id { get; set; }
+}
diff --git a/Engine.Serialization/IgnoreSerializationAttribute.cs b/Engine.Serialization/IgnoreSerializationAttribute.cs
new file mode 100644
index 0000000..6604941
--- /dev/null
+++ b/Engine.Serialization/IgnoreSerializationAttribute.cs
@@ -0,0 +1,5 @@
+namespace Syntriax.Engine.Serialization;
+
+public class IgnoreSerializationAttribute : Attribute
+{
+}
diff --git a/Engine.Serialization/Serializer.cs b/Engine.Serialization/Serializer.cs
new file mode 100644
index 0000000..c96647f
--- /dev/null
+++ b/Engine.Serialization/Serializer.cs
@@ -0,0 +1,30 @@
+using YamlDotNet.Serialization;
+using YamlDotNet.Serialization.NamingConventions;
+
+namespace Syntriax.Engine.Serialization;
+
+public static class Serializer
+{
+ private static readonly ISerializer serializer = new SerializerBuilder()
+ .WithNamingConvention(PascalCaseNamingConvention.Instance)
+ .DisableAliases()
+ .WithTypeConverter(new EntityConverter())
+ .WithTypeConverter(new Vector2DConverter())
+ .Build();
+
+ private static readonly IDeserializer deserializer = new DeserializerBuilder()
+ .WithNamingConvention(PascalCaseNamingConvention.Instance)
+ .WithTypeConverter(new EntityConverter())
+ .WithTypeConverter(new Vector2DConverter())
+ .Build();
+
+ public static string Serialize(object instance)
+ {
+ return serializer.Serialize(instance);
+ }
+
+ public static T Deserialize(string yaml)
+ {
+ return deserializer.Deserialize(yaml);
+ }
+}
diff --git a/Engine.sln b/Engine.sln
index 4bc06af..218717c 100644
--- a/Engine.sln
+++ b/Engine.sln
@@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Systems", "Engine.Sy
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine", "Engine\Engine.csproj", "{58AE79C1-9203-44AE-8022-AA180F0A71DC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Serialization", "Engine.Serialization\Engine.Serialization.csproj", "{9B46BE51-DD6B-4EDD-AAA8-41E993D7422C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -36,5 +38,9 @@ Global
{58AE79C1-9203-44AE-8022-AA180F0A71DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58AE79C1-9203-44AE-8022-AA180F0A71DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{58AE79C1-9203-44AE-8022-AA180F0A71DC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9B46BE51-DD6B-4EDD-AAA8-41E993D7422C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9B46BE51-DD6B-4EDD-AAA8-41E993D7422C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9B46BE51-DD6B-4EDD-AAA8-41E993D7422C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9B46BE51-DD6B-4EDD-AAA8-41E993D7422C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/Engine/Engine.csproj b/Engine/Engine.csproj
index c7b03c3..9d32971 100644
--- a/Engine/Engine.csproj
+++ b/Engine/Engine.csproj
@@ -8,6 +8,7 @@
+