refactor: moved serialization into core project

This commit is contained in:
2025-04-25 22:20:27 +03:00
parent 791349686b
commit fb363970fc
25 changed files with 65 additions and 63 deletions

View File

@@ -7,4 +7,8 @@
<RootNamespace>Syntriax.Engine.Core</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\YamlDotNet\YamlDotNet\YamlDotNet.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,5 @@
using YamlDotNet.Serialization;
namespace Syntriax.Engine.Core.Serialization;
public interface IEngineTypeYamlConverter : IYamlTypeConverter;

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Syntriax.Engine.Core.Serialization;
public class BehaviourControllerConverter : IEngineTypeYamlConverter
{
private const string SERIALIZED_SCALAR_NAME = "Properties";
private const string BEHAVIOURS_SCALAR_NAME = "Behaviours";
public bool Accepts(Type type) => typeof(IBehaviourController).IsAssignableFrom(type);
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
string id;
IBehaviourController behaviourController;
IStateEnable stateEnable;
List<IBehaviour> behaviours;
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(IBehaviourController.Id)) != 0)
throw new();
id = parser.Consume<Scalar>().Value;
if (parser.Consume<Scalar>().Value.CompareTo(SERIALIZED_SCALAR_NAME) != 0)
throw new();
SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!;
behaviourController = (IBehaviourController)instanceSerializedClass.CreateInstance();
string value = parser.Consume<Scalar>().Value;
if (value.CompareTo(nameof(IBehaviourController.StateEnable)) != 0)
throw new();
stateEnable = (IStateEnable)rootDeserializer(typeof(IStateEnable))!;
if (parser.Consume<Scalar>().Value.CompareTo(BEHAVIOURS_SCALAR_NAME) != 0)
throw new();
behaviours = (List<IBehaviour>)rootDeserializer(typeof(List<IBehaviour>))!;
parser.Consume<MappingEnd>();
behaviourController.Id = id;
stateEnable.Assign(behaviourController);
behaviourController.Assign(stateEnable);
foreach (IBehaviour behaviour in behaviours)
behaviourController.AddBehaviour(behaviour);
return behaviourController;
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
IBehaviourController behaviourController = (IBehaviourController)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(IBehaviourController.Id)));
emitter.Emit(new Scalar(behaviourController.Id));
emitter.Emit(new Scalar(SERIALIZED_SCALAR_NAME));
serializer(new SerializedClass(behaviourController));
emitter.Emit(new Scalar(nameof(IBehaviourController.StateEnable)));
serializer(behaviourController.StateEnable);
emitter.Emit(new Scalar(BEHAVIOURS_SCALAR_NAME));
serializer(behaviourController.GetBehaviours<IBehaviour>().Where(b => !b.GetType().HasAttribute<IgnoreSerializationAttribute>()));
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,73 @@
using System;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Syntriax.Engine.Core.Serialization;
public class BehaviourConverter : IEngineTypeYamlConverter
{
private const string SERIALIZED_SCALAR_NAME = "Properties";
public bool Accepts(Type type) => typeof(IBehaviour).IsAssignableFrom(type);
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
string id;
int priority;
IBehaviour behaviour;
IStateEnable stateEnable;
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(IBehaviour.Id)) != 0)
throw new();
id = parser.Consume<Scalar>().Value;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(IBehaviour.Priority)) != 0)
throw new();
priority = int.Parse(parser.Consume<Scalar>().Value);
if (parser.Consume<Scalar>().Value.CompareTo(SERIALIZED_SCALAR_NAME) != 0)
throw new();
SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!;
behaviour = (IBehaviour)instanceSerializedClass.CreateInstance();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(IBehaviour.StateEnable)) != 0)
throw new();
stateEnable = (IStateEnable)rootDeserializer(typeof(IStateEnable))!;
parser.Consume<MappingEnd>();
behaviour.Id = id;
behaviour.Priority = priority;
stateEnable.Assign(behaviour);
behaviour.Assign(stateEnable);
return behaviour;
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
IBehaviour behaviour = (IBehaviour)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(IBehaviour.Id)));
emitter.Emit(new Scalar(behaviour.Id));
emitter.Emit(new Scalar(nameof(IBehaviour.Priority)));
emitter.Emit(new Scalar(behaviour.Priority.ToString()));
emitter.Emit(new Scalar(SERIALIZED_SCALAR_NAME));
serializer(new SerializedClass(behaviour));
emitter.Emit(new Scalar(nameof(IBehaviour.StateEnable)));
serializer(behaviour.StateEnable);
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,41 @@
using System;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Syntriax.Engine.Core.Serialization;
public class AABBConverter : IEngineTypeYamlConverter
{
public bool Accepts(Type type) => type == typeof(AABB);
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(AABB.LowerBoundary)) != 0)
throw new ArgumentException($"{nameof(AABB)} mapping must start with {nameof(AABB.LowerBoundary)}");
Vector2D lowerBoundary = (Vector2D)rootDeserializer(typeof(Vector2D))!;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(AABB.UpperBoundary)) != 0)
throw new ArgumentException($"{nameof(AABB)} mapping must end with {nameof(AABB.UpperBoundary)}");
Vector2D upperBoundary = (Vector2D)rootDeserializer(typeof(Vector2D))!;
parser.Consume<MappingEnd>();
return new AABB(lowerBoundary, upperBoundary);
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
AABB aabb = (AABB)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(AABB.LowerBoundary)));
serializer(aabb.LowerBoundary, typeof(Vector2D));
emitter.Emit(new Scalar(nameof(AABB.UpperBoundary)));
serializer(aabb.UpperBoundary, typeof(Vector2D));
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,41 @@
using System;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Syntriax.Engine.Core.Serialization;
public class CircleConverter : IEngineTypeYamlConverter
{
public bool Accepts(Type type) => type == typeof(Circle);
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Circle.Center)) != 0)
throw new ArgumentException($"{nameof(Circle)} mapping must start with {nameof(Circle.Center)}");
Vector2D lowerBoundary = (Vector2D)rootDeserializer(typeof(Vector2D))!;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Circle.Radius)) != 0)
throw new ArgumentException($"{nameof(Circle)} mapping must end with {nameof(Circle.Radius)}");
float radius = (float)rootDeserializer(typeof(float))!;
parser.Consume<MappingEnd>();
return new Circle(lowerBoundary, radius);
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Circle circle = (Circle)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(Circle.Center)));
serializer(circle.Center, typeof(Vector2D));
emitter.Emit(new Scalar(nameof(Circle.Radius)));
serializer(circle.Radius, typeof(float));
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,41 @@
using System;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Syntriax.Engine.Core.Serialization;
public class Line2DConverter : IEngineTypeYamlConverter
{
public bool Accepts(Type type) => type == typeof(Line2D);
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Line2D.From)) != 0)
throw new ArgumentException($"{nameof(Line2D)} mapping must start with {nameof(Line2D.From)}");
Vector2D from = (Vector2D)rootDeserializer(typeof(Vector2D))!;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Line2D.To)) != 0)
throw new ArgumentException($"{nameof(Line2D)} mapping must end with {nameof(Line2D.To)}");
Vector2D to = (Vector2D)rootDeserializer(typeof(Vector2D))!;
parser.Consume<MappingEnd>();
return new Line2D(from, to);
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Line2D line2D = (Line2D)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(Line2D.From)));
serializer(line2D.From, typeof(Vector2D));
emitter.Emit(new Scalar(nameof(Line2D.To)));
serializer(line2D.To, typeof(Vector2D));
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,41 @@
using System;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Syntriax.Engine.Core.Serialization;
public class Line2DEquationConverter : IEngineTypeYamlConverter
{
public bool Accepts(Type type) => type == typeof(Line2DEquation);
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Line2DEquation.Slope)) != 0)
throw new ArgumentException($"{nameof(Line2DEquation)} mapping must start with {nameof(Line2DEquation.Slope)}");
float slope = (float)rootDeserializer(typeof(float))!;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Line2DEquation.OffsetY)) != 0)
throw new ArgumentException($"{nameof(Line2DEquation)} mapping must end with {nameof(Line2DEquation.OffsetY)}");
float offset = (float)rootDeserializer(typeof(float))!;
parser.Consume<MappingEnd>();
return new Line2DEquation(slope, offset);
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Line2DEquation line2DEquation = (Line2DEquation)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(Line2DEquation.Slope)));
serializer(line2DEquation.Slope, typeof(float));
emitter.Emit(new Scalar(nameof(Line2DEquation.OffsetY)));
serializer(line2DEquation.OffsetY, typeof(float));
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,41 @@
using System;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Syntriax.Engine.Core.Serialization;
public class Projection1DConverter : IEngineTypeYamlConverter
{
public bool Accepts(Type type) => type == typeof(Projection1D);
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Projection1D.Min)) != 0)
throw new ArgumentException($"{nameof(Projection1D)} mapping must start with {nameof(Projection1D.Min)}");
float min = (float)rootDeserializer(typeof(float))!;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Projection1D.Max)) != 0)
throw new ArgumentException($"{nameof(Projection1D)} mapping must end with {nameof(Projection1D.Max)}");
float max = (float)rootDeserializer(typeof(float))!;
parser.Consume<MappingEnd>();
return new Projection1D(min, max);
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Projection1D projection1D = (Projection1D)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(Projection1D.Min)));
serializer(projection1D.Min, typeof(float));
emitter.Emit(new Scalar(nameof(Projection1D.Max)));
serializer(projection1D.Max, typeof(float));
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,28 @@
using System;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Syntriax.Engine.Core.Serialization;
public class QuaternionConverter : IEngineTypeYamlConverter
{
private static readonly int SUBSTRING_START_LENGTH = nameof(Quaternion).Length + 1;
public bool Accepts(Type type) => type == typeof(Quaternion);
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
string value = parser.Consume<Scalar>().Value;
string insideParenthesis = value[SUBSTRING_START_LENGTH..^1];
string[] values = insideParenthesis.Split(", ");
return new Quaternion(float.Parse(values[0]), float.Parse(values[1]), float.Parse(values[2]), float.Parse(values[3]));
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Quaternion quaternion = (Quaternion)value!;
emitter.Emit(new Scalar($"{nameof(Quaternion)}({quaternion.X}, {quaternion.Y}, {quaternion.Z}, {quaternion.W})"));
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Syntriax.Engine.Core.Serialization;
public class Shape2DConverter : IEngineTypeYamlConverter
{
public bool Accepts(Type type) => type == typeof(Shape2D);
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Shape2D.Vertices)) != 0)
throw new ArgumentException($"{nameof(Shape2D)} mapping have a {nameof(Shape2D.Vertices)}");
parser.Consume<SequenceStart>();
List<Vector2D> vertices = [];
while (!parser.TryConsume<SequenceEnd>(out _))
{
Vector2D vertex = (Vector2D)rootDeserializer(typeof(Vector2D))!;
vertices.Add(vertex);
}
parser.Consume<MappingEnd>();
return new Shape2D(vertices);
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Shape2D shape2D = (Shape2D)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(Shape2D.Vertices)));
emitter.Emit(new SequenceStart(anchor: null, tag: null, isImplicit: false, style: SequenceStyle.Block));
foreach (Vector2D vertex in shape2D.Vertices)
serializer(vertex, typeof(Vector2D));
emitter.Emit(new SequenceEnd());
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,47 @@
using System;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Syntriax.Engine.Core.Serialization;
public class TriangleConverter : IEngineTypeYamlConverter
{
public bool Accepts(Type type) => type == typeof(Triangle);
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Triangle.A)) != 0)
throw new ArgumentException($"{nameof(Triangle)} mapping must start with {nameof(Triangle.A)}");
Vector2D a = (Vector2D)rootDeserializer(typeof(Vector2D))!;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Triangle.B)) != 0)
throw new ArgumentException($"{nameof(Triangle)} mapping must have {nameof(Triangle.B)} after {nameof(Triangle.A)}");
Vector2D b = (Vector2D)rootDeserializer(typeof(Vector2D))!;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Triangle.C)) != 0)
throw new ArgumentException($"{nameof(Triangle)} mapping must end with {nameof(Triangle.C)}");
Vector2D c = (Vector2D)rootDeserializer(typeof(Vector2D))!;
parser.Consume<MappingEnd>();
return new Triangle(a, b, c);
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Triangle aabb = (Triangle)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(Triangle.A)));
serializer(aabb.A, typeof(Vector2D));
emitter.Emit(new Scalar(nameof(Triangle.B)));
serializer(aabb.B, typeof(Vector2D));
emitter.Emit(new Scalar(nameof(Triangle.C)));
serializer(aabb.C, typeof(Vector2D));
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,28 @@
using System;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Syntriax.Engine.Core.Serialization;
public class Vector2DConverter : IEngineTypeYamlConverter
{
private static readonly int SUBSTRING_START_LENGTH = nameof(Vector2D).Length + 1;
public bool Accepts(Type type) => type == typeof(Vector2D);
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
string value = parser.Consume<Scalar>().Value;
string insideParenthesis = value[SUBSTRING_START_LENGTH..^1];
string[] values = insideParenthesis.Split(", ");
return new Vector2D(float.Parse(values[0]), float.Parse(values[1]));
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Vector2D vector2D = (Vector2D)value!;
emitter.Emit(new Scalar($"{nameof(Vector2D)}({vector2D.X}, {vector2D.Y})"));
}
}

View File

@@ -0,0 +1,28 @@
using System;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Syntriax.Engine.Core.Serialization;
public class Vector3DConverter : IEngineTypeYamlConverter
{
private static readonly int SUBSTRING_START_LENGTH = nameof(Vector3D).Length + 1;
public bool Accepts(Type type) => type == typeof(Vector3D);
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
string value = parser.Consume<Scalar>().Value;
string insideParenthesis = value[SUBSTRING_START_LENGTH..^1];
string[] values = insideParenthesis.Split(", ");
return new Vector3D(float.Parse(values[0]), float.Parse(values[1]), float.Parse(values[2]));
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Vector3D vector3D = (Vector3D)value!;
emitter.Emit(new Scalar($"{nameof(Vector3D)}({vector3D.X}, {vector3D.Y}, {vector3D.Z})"));
}
}

View File

@@ -0,0 +1,53 @@
using System;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Syntriax.Engine.Core.Serialization;
public class StateEnableConverter : IEngineTypeYamlConverter
{
private const string SERIALIZED_SCALAR_NAME = "Properties";
public bool Accepts(Type type) => typeof(IStateEnable).IsAssignableFrom(type);
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
bool enabled;
IStateEnable stateEnable;
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(IStateEnable.Enabled)) != 0)
throw new();
enabled = bool.Parse(parser.Consume<Scalar>().Value);
if (parser.Consume<Scalar>().Value.CompareTo(SERIALIZED_SCALAR_NAME) != 0)
throw new();
SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!;
stateEnable = (IStateEnable)instanceSerializedClass.CreateInstance();
parser.Consume<MappingEnd>();
stateEnable.Enabled = enabled;
return stateEnable;
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
IStateEnable stateEnable = (IStateEnable)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(IStateEnable.Enabled)));
emitter.Emit(new Scalar(stateEnable.Enabled.ToString()));
emitter.Emit(new Scalar(SERIALIZED_SCALAR_NAME));
serializer(new SerializedClass(stateEnable));
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Linq;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Syntriax.Engine.Core.Serialization;
public class UniverseObjectConverter : IEngineTypeYamlConverter
{
private const string SERIALIZED_SCALAR_NAME = "Properties";
public bool Accepts(Type type) => typeof(IUniverseObject).IsAssignableFrom(type);
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
string id;
string name;
IUniverseObject universeObject;
IStateEnable stateEnable;
IBehaviourController behaviourController;
List<IUniverseObject> children;
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(IUniverseObject.Id)) != 0)
throw new();
id = parser.Consume<Scalar>().Value;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(IUniverseObject.Name)) != 0)
throw new();
name = parser.Consume<Scalar>().Value;
if (parser.Consume<Scalar>().Value.CompareTo(SERIALIZED_SCALAR_NAME) != 0)
throw new();
SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!;
universeObject = (IUniverseObject)instanceSerializedClass.CreateInstance();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(IUniverseObject.StateEnable)) != 0)
throw new();
stateEnable = (IStateEnable)rootDeserializer(typeof(IStateEnable))!;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(IUniverseObject.BehaviourController)) != 0)
throw new();
behaviourController = (IBehaviourController)rootDeserializer(typeof(IBehaviourController))!;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(IUniverseObject.Children)) != 0)
throw new();
children = (List<IUniverseObject>)rootDeserializer(typeof(List<IUniverseObject>))!;
parser.Consume<MappingEnd>();
universeObject.Id = id;
universeObject.Name = name;
stateEnable.Assign(universeObject);
universeObject.Assign(stateEnable);
behaviourController.Assign(universeObject);
universeObject.Assign(behaviourController);
foreach (IUniverseObject child in children)
universeObject.AddChild(child);
return universeObject;
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
IUniverseObject universeObject = (IUniverseObject)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(IUniverseObject.Id)));
emitter.Emit(new Scalar(universeObject.Id));
emitter.Emit(new Scalar(nameof(IUniverseObject.Name)));
emitter.Emit(new Scalar(universeObject.Name));
emitter.Emit(new Scalar(SERIALIZED_SCALAR_NAME));
serializer(new SerializedClass(universeObject));
emitter.Emit(new Scalar(nameof(IUniverseObject.StateEnable)));
serializer(universeObject.StateEnable);
emitter.Emit(new Scalar(nameof(IUniverseObject.BehaviourController)));
serializer(universeObject.BehaviourController);
emitter.Emit(new Scalar(nameof(IUniverseObject.Children)));
serializer(universeObject.Children.Where(c => !c.GetType().HasAttribute<IgnoreSerializationAttribute>()));
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,6 @@
using System;
namespace Syntriax.Engine.Core.Serialization;
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Class)]
public class IgnoreSerializationAttribute : Attribute;

View File

@@ -0,0 +1,6 @@
using System;
namespace Syntriax.Engine.Core.Serialization;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class SerializeAllAttribute : Attribute;

View File

@@ -0,0 +1,6 @@
using System;
namespace Syntriax.Engine.Core.Serialization;
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class SerializeAttribute : Attribute;

View File

@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Syntriax.Engine.Core.Factory;
namespace Syntriax.Engine.Core.Serialization;
public class SerializedClass
{
private const BindingFlags PRIVATE_BINDING_FLAGS = BindingFlags.Instance | BindingFlags.NonPublic;
private const BindingFlags PUBLIC_BINDING_FLAGS = BindingFlags.Instance | BindingFlags.Public;
public string Type { get; set; } = string.Empty;
public Dictionary<string, object?> Public { get; set; } = [];
public Dictionary<string, object?> Private { get; set; } = [];
public SerializedClass() { }
public SerializedClass(object @class)
{
UpdateClass(@class);
}
private void UpdateClass(object @class)
{
Type type = @class.GetType();
Type = type.FullName ?? type.Name;
bool isFullySerializable = type.HasAttribute<SerializeAllAttribute>();
Public.Clear();
Private.Clear();
foreach (PropertyInfo privatePropertyInfo in Utils.GetPropertyInfosIncludingBaseClasses(type, PRIVATE_BINDING_FLAGS))
{
if (privatePropertyInfo.HasAttribute<IgnoreSerializationAttribute>())
continue;
if (privatePropertyInfo.SetMethod is null)
continue;
if (!isFullySerializable && !privatePropertyInfo.HasAttribute<SerializeAttribute>())
continue;
object? value = privatePropertyInfo.GetValue(@class);
if (value is IEntity entity)
Private.Add(privatePropertyInfo.Name, entity.Id);
else
Private.Add(privatePropertyInfo.Name, value);
}
foreach (PropertyInfo publicPropertyInfo in Utils.GetPropertyInfosIncludingBaseClasses(type, PUBLIC_BINDING_FLAGS))
{
if (publicPropertyInfo.HasAttribute<IgnoreSerializationAttribute>())
continue;
if (publicPropertyInfo.SetMethod is null)
continue;
if (!isFullySerializable && !publicPropertyInfo.HasAttribute<SerializeAttribute>())
continue;
object? value = publicPropertyInfo.GetValue(@class);
if (value is IEntity entity)
Public.Add(publicPropertyInfo.Name, entity.Id);
else
Public.Add(publicPropertyInfo.Name, value);
}
foreach (FieldInfo privateFieldInfo in Utils.GetFieldInfosIncludingBaseClasses(type, PRIVATE_BINDING_FLAGS))
{
if (privateFieldInfo.HasAttribute<System.Runtime.CompilerServices.CompilerGeneratedAttribute>())
continue;
if (!isFullySerializable && !privateFieldInfo.HasAttribute<SerializeAttribute>())
continue;
object? value = privateFieldInfo.GetValue(@class);
if (value is IEntity entity)
Private.Add(privateFieldInfo.Name, entity.Id);
else
Private.Add(privateFieldInfo.Name, value);
}
foreach (FieldInfo publicFieldInfo in Utils.GetFieldInfosIncludingBaseClasses(type, PUBLIC_BINDING_FLAGS))
{
if (publicFieldInfo.HasAttribute<System.Runtime.CompilerServices.CompilerGeneratedAttribute>())
continue;
if (!isFullySerializable && !publicFieldInfo.HasAttribute<SerializeAttribute>())
continue;
object? value = publicFieldInfo.GetValue(@class);
if (value is IEntity entity)
Public.Add(publicFieldInfo.Name, entity.Id);
else
Public.Add(publicFieldInfo.Name, value);
}
}
public object CreateInstance()
{
Type type = TypeFactory.GetType(Type);
object instance = TypeFactory.Get(type);
foreach ((string key, object? value) in Private)
if (type.GetField(key, PRIVATE_BINDING_FLAGS) is FieldInfo fieldInfo)
fieldInfo.SetValue(instance, value);
else if (type.GetProperty(key, PRIVATE_BINDING_FLAGS) is PropertyInfo propertyInfo)
propertyInfo.SetValue(instance, value);
foreach ((string key, object? value) in Public)
if (type.GetField(key, PUBLIC_BINDING_FLAGS) is FieldInfo fieldInfo)
fieldInfo.SetValue(instance, value);
else if (type.GetProperty(key, PUBLIC_BINDING_FLAGS) is PropertyInfo propertyInfo)
propertyInfo.SetValue(instance, value);
return instance;
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace Syntriax.Engine.Core.Serialization;
public static class Serializer
{
private static readonly ISerializer serializer = GetSerializer();
private static ISerializer GetSerializer()
{
SerializerBuilder serializerBuilder = new SerializerBuilder()
.WithNamingConvention(PascalCaseNamingConvention.Instance)
.DisableAliases();
foreach (IEngineTypeYamlConverter typeConverter in GetEngineYamlTypeConverters())
serializerBuilder = serializerBuilder.WithTypeConverter(typeConverter);
return serializerBuilder.Build();
}
private static readonly IDeserializer deserializer = GetDeserializer();
private static IDeserializer GetDeserializer()
{
DeserializerBuilder serializerBuilder = new DeserializerBuilder()
.WithNamingConvention(PascalCaseNamingConvention.Instance);
foreach (IEngineTypeYamlConverter typeConverter in GetEngineYamlTypeConverters())
serializerBuilder = serializerBuilder.WithTypeConverter(typeConverter);
return serializerBuilder.Build();
}
private static IEnumerable<IEngineTypeYamlConverter> GetEngineYamlTypeConverters()
{
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes().Where(t => typeof(IEngineTypeYamlConverter).IsAssignableFrom(t) && t.IsClass && !t.IsAbstract))
yield return (Activator.CreateInstance(type) as IEngineTypeYamlConverter)!;
}
public static string Serialize(object instance)
{
return serializer.Serialize(instance);
}
public static object Deserialize(string yaml)
{
return deserializer.Deserialize(yaml)!;
}
public static T Deserialize<T>(string yaml)
{
return deserializer.Deserialize<T>(yaml);
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Syntriax.Engine.Core.Serialization;
internal static class Utils
{
internal static bool HasAttribute<T>(this MemberInfo memberInfo) where T : Attribute => memberInfo.GetCustomAttribute<T>() is not null;
internal static bool IsEnumerable(this Type type) => typeof(System.Collections.IEnumerable).IsAssignableFrom(type) && type != typeof(string);
internal static TypeData GetTypeData(this Type objectType)
{
List<EventInfo> eventInfos = objectType.GetEvents(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
.OrderBy(ei => ei.Name)
.ToList();
List<FieldInfo> fieldInfos = GetFieldInfosIncludingBaseClasses(objectType, BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Public)
.Where(pi => !eventInfos.Any(ei => pi.Name.CompareTo(ei.Name) == 0)).ToList();
List<PropertyInfo> propertyInfos = GetPropertyInfosIncludingBaseClasses(objectType, BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.Public)
.Where(pi => pi.SetMethod is not null && !eventInfos.Any(ei => pi.Name.CompareTo(ei.Name) == 0))
.ToList();
return new TypeData(fieldInfos, propertyInfos);
}
internal static List<FieldInfo> GetFieldInfosIncludingBaseClasses(Type type, BindingFlags bindingFlags)
{
if (type.BaseType is null)
return [.. type.GetFields(bindingFlags)];
Type currentType = type;
FieldInfoComparer fieldComparer = new();
HashSet<FieldInfo> fieldInfoList = new(type.GetFields(bindingFlags), fieldComparer);
while (currentType.BaseType is Type baseType)
{
currentType = baseType;
fieldInfoList.UnionWith(currentType!.GetFields(bindingFlags));
}
return [.. fieldInfoList.OrderBy(fi => fi.Name)];
}
internal static List<PropertyInfo> GetPropertyInfosIncludingBaseClasses(Type type, BindingFlags bindingFlags)
{
if (type.BaseType is null)
return [.. type.GetProperties(bindingFlags)];
Type currentType = type;
PropertyInfoComparer propertyComparer = new();
HashSet<PropertyInfo> propertyInfoList = new(type.GetProperties(bindingFlags), propertyComparer);
while (currentType.BaseType is Type baseType)
{
currentType = baseType;
propertyInfoList.UnionWith(currentType.GetProperties(bindingFlags));
}
return [.. propertyInfoList.OrderBy(pi => pi.Name)];
}
private class FieldInfoComparer : IEqualityComparer<FieldInfo>
{
public bool Equals(FieldInfo? x, FieldInfo? y) => x?.DeclaringType == y?.DeclaringType && x?.Name == y?.Name;
public int GetHashCode(FieldInfo obj) => obj.Name.GetHashCode() ^ obj.DeclaringType!.GetHashCode();
}
private class PropertyInfoComparer : IEqualityComparer<PropertyInfo>
{
public bool Equals(PropertyInfo? x, PropertyInfo? y) => x?.DeclaringType == y?.DeclaringType && x?.Name == y?.Name;
public int GetHashCode(PropertyInfo obj) => obj.Name.GetHashCode() ^ obj.DeclaringType!.GetHashCode();
}
}
internal record struct TypeData(IEnumerable<FieldInfo> Fields, IEnumerable<PropertyInfo> Properties)
{
public static implicit operator (IEnumerable<FieldInfo> fields, IEnumerable<PropertyInfo> properties)(TypeData value) => (value.Fields, value.Properties);
public static implicit operator TypeData((IEnumerable<FieldInfo> fields, IEnumerable<PropertyInfo> properties) value) => new(value.fields, value.properties);
}