Skip to content

Commit

Permalink
Can create serializers for all the events
Browse files Browse the repository at this point in the history
  • Loading branch information
halgari committed Jan 9, 2024
1 parent 5595144 commit c223169
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ public interface IVariableSizeSerializer<T> : ISerializer
/// </summary>
public interface IGenericSerializer : ISerializer
{
public bool TrySpecialze(Type baseType, Type[] argTypes, [NotNullWhen(true)] out ISerializer? serializer);
public bool TrySpecialze(Type baseType, Type[] argTypes, Func<Type, ISerializer> serializerFinder, [NotNullWhen(true)] out ISerializer? serializer);
}
1 change: 1 addition & 0 deletions src/NexusMods.EventSourcing/NexusMods.EventSourcing.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<ItemGroup>
<PackageReference Include="DynamicData" Version="8.3.27" />
<PackageReference Include="MemoryPack" Version="1.10.0" />
<PackageReference Include="Reloaded.Memory" Version="9.3.2" />
<PackageReference Include="System.Reactive" Version="6.0.0" />
<PackageReference Include="TransparentValueObjects" PrivateAssets="all" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>
Expand Down
147 changes: 147 additions & 0 deletions src/NexusMods.EventSourcing/Serialization/ArraySerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
using NexusMods.EventSourcing.Abstractions.Serialization;
using Reloaded.Memory.Extensions;

namespace NexusMods.EventSourcing.Serialization;

public class GenericArraySerializer : IGenericSerializer
{
public bool CanSerialize(Type valueType)
{
return false;
}

public bool TryGetFixedSize(Type valueType, out int size)
{
size = 0;
return false;
}

public bool TrySpecialze(Type baseType, Type[] argTypes, Func<Type, ISerializer> serializerFinder, [NotNullWhen(true)] out ISerializer? serializer)
{
if (!baseType.IsArray)
{
serializer = null;
return false;
}

var itemType = baseType.GetElementType()!;
var itemSerializer = serializerFinder(itemType);

if (itemSerializer.TryGetFixedSize(itemType, out var itemSize))
{
var type = typeof(FixedItemSizeArraySerializer<,>).MakeGenericType(itemType, itemSerializer.GetType());
serializer = (ISerializer) Activator.CreateInstance(type, itemSerializer, itemSize)!;
return true;
}
else
{
var type = typeof(VariableItemSizeSerializer<,>).MakeGenericType(itemType, itemSerializer.GetType());
serializer = (ISerializer) Activator.CreateInstance(type, itemSerializer, itemSize)!;
return true;
}
}
}


public class FixedItemSizeArraySerializer<TItem, TItemSerializer>(TItemSerializer itemSerializer, int itemSize) : IVariableSizeSerializer<TItem[]>
where TItemSerializer : IFixedSizeSerializer<TItem>
{
public bool CanSerialize(Type valueType)
{
if (!valueType.IsArray)
{
return false;
}

return itemSerializer.CanSerialize(valueType.GetElementType()!);
}

public bool TryGetFixedSize(Type valueType, out int size)
{
size = 0;
return false;
}

public void Serialize<TWriter>(TItem[] value, TWriter output) where TWriter : IBufferWriter<byte>
{
var totalSize = sizeof(ushort) + (itemSize * value.Length);
var span = output.GetSpan(totalSize);
BinaryPrimitives.WriteUInt32BigEndian(span, (ushort)value.Length);

foreach (var item in value)
{
itemSerializer.Serialize(item, span.SliceFast(itemSize, itemSize));
}
output.Advance(totalSize);
}

public int Deserialize(ReadOnlySpan<byte> from, out TItem[] value)
{
var size = BinaryPrimitives.ReadUInt16BigEndian(from);
var array = GC.AllocateUninitializedArray<TItem>(size);

from = from.SliceFast(sizeof(ushort));
for (var i = 0; i < size; i++)
{
array[i] = itemSerializer.Deserialize(from.SliceFast(i * itemSize, itemSize));
}

value = array;
return sizeof(ushort) + (itemSize * size);
}
}


public class VariableItemSizeSerializer<TItem, TItemSerializer>(TItemSerializer itemSerializer, int itemSize) : IVariableSizeSerializer<TItem[]>
where TItemSerializer : IVariableSizeSerializer<TItem>
{
public bool CanSerialize(Type valueType)
{
if (!valueType.IsArray)
{
return false;
}

return itemSerializer.CanSerialize(valueType.GetElementType()!);
}

public bool TryGetFixedSize(Type valueType, out int size)
{
size = 0;
return false;
}

public void Serialize<TWriter>(TItem[] value, TWriter output) where TWriter : IBufferWriter<byte>
{
var totalSize = sizeof(ushort) + (itemSize * value.Length);
var span = output.GetSpan(totalSize);
BinaryPrimitives.WriteUInt32BigEndian(span, (ushort)value.Length);

foreach (var item in value)
{
itemSerializer.Serialize(item, output);
}
output.Advance(totalSize);
}

public int Deserialize(ReadOnlySpan<byte> from, out TItem[] value)
{
var size = BinaryPrimitives.ReadUInt16BigEndian(from);
var array = GC.AllocateUninitializedArray<TItem>(size);

from = from.SliceFast(sizeof(ushort));
var offset = sizeof(ushort);
for (var i = 0; i < size; i++)
{
offset += itemSerializer.Deserialize(from.SliceFast(offset), out var item);
array[i] = item;
}

value = array;
return offset;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public bool TryGetFixedSize(Type valueType, out int size)
return false;
}

public bool TrySpecialze(Type baseType, Type[] argTypes, [NotNullWhen(true)] out ISerializer? serializer)
public bool TrySpecialze(Type baseType, Type[] argTypes, Func<Type, ISerializer> serializerFinder, [NotNullWhen(true)] out ISerializer? serializer)
{
if (baseType != typeof(EntityId<>) || argTypes.Length != 1)
{
Expand Down
61 changes: 51 additions & 10 deletions src/NexusMods.EventSourcing/Serialization/EventSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,19 @@ namespace NexusMods.EventSourcing.Serialization;

using MemberDefinition = (ParameterInfo Ref, ParameterInfo Base, ParameterExpression Variable, ISerializer Serializer);

public sealed class BinaryEventSerializer : IEventSerializer
public sealed class BinaryEventSerializer : IEventSerializer, IVariableSizeSerializer<IEvent>
{
private readonly PooledMemoryBufferWriter _writer;

private readonly Dictionary<Type, EventSerializerDelegate> _serializerDelegates = new();
private readonly Dictionary<UInt128, EventDeserializerDelegate> _deserializerDelegates = new();

private static readonly GuidSerializer _guidSerializer = new();

/// <summary>
/// Write an event to the given writer, and return the
/// </summary>
internal delegate void EventSerializerDelegate(IEvent @event);

private delegate IEvent EventDeserializerDelegate(ReadOnlySpan<byte> data);
private delegate int EventDeserializerDelegate(ReadOnlySpan<byte> data, out IEvent @event);

public BinaryEventSerializer(IEnumerable<ISerializer> diInjectedSerializers, IEnumerable<EventDefinition> eventDefinitions)
{
Expand Down Expand Up @@ -58,7 +56,8 @@ public ReadOnlySpan<byte> Serialize(IEvent @event)
public IEvent Deserialize(ReadOnlySpan<byte> data)
{
var id = BinaryPrimitives.ReadUInt128BigEndian(data);
return _deserializerDelegates[id](SliceFastStart(data, 16));
var used = _deserializerDelegates[id](SliceFastStart(data, 16), out var @event);
return @event;
}


Expand Down Expand Up @@ -165,6 +164,9 @@ private EventSerializerDelegate BuildVariableSizeSerializer(EventDefinition even
private EventDeserializerDelegate BuildVariableSizeDeserializer(EventDefinition definition, MemberDefinition[] allParams,
List<MemberDefinition> fixedParams, int fixedSize, List<MemberDefinition> unfixedParams)
{

var outParam = Expression.Parameter(typeof(IEvent).MakeByRefType(), "output");

var spanParam = Expression.Parameter(typeof(ReadOnlySpan<byte>));

var ctorExpressions = new List<Expression>();
Expand Down Expand Up @@ -204,16 +206,22 @@ private EventDeserializerDelegate BuildVariableSizeDeserializer(EventDefinition
var ctorCall = Expression.New(definition.Type.GetConstructors().First(c => c.GetParameters().Length == ctorParams.Length),
ctorParams);

var casted = Expression.Convert(ctorCall, typeof(IEvent));
var casted = Expression.Assign(outParam, Expression.Convert(ctorCall, typeof(IEvent)));
blockExprs.Add(casted);
blockExprs.Add(offsetVariable);

var outerBlock = Expression.Block(ctorParams.Append(offsetVariable), blockExprs);
var lambda = Expression.Lambda<EventDeserializerDelegate>(outerBlock, spanParam);
var lambda = Expression.Lambda<EventDeserializerDelegate>(outerBlock, [spanParam, outParam]);
return lambda.Compile();
}

private ISerializer GetSerializer(ISerializer[] serializers, Type type)
{
if (type == typeof(IEvent))
{
return this;
}

var result = serializers.FirstOrDefault(s => s.CanSerialize(type));
if (result != null)
{
Expand All @@ -225,18 +233,28 @@ private ISerializer GetSerializer(ISerializer[] serializers, Type type)
var genericMakers = serializers.OfType<IGenericSerializer>();
foreach (var maker in genericMakers)
{
if (maker.TrySpecialze(type.GetGenericTypeDefinition(), type.GetGenericArguments(), out var serializer))
if (maker.TrySpecialze(type.GetGenericTypeDefinition(),
type.GetGenericArguments(), t => GetSerializer(serializers, t), out var serializer))
{
return serializer;
}
}
}

if (type.IsArray)
{
var arrayMaker = serializers.OfType<GenericArraySerializer>().First();
arrayMaker.TrySpecialze(type, [type.GetElementType()!], t => GetSerializer(serializers, t), out var serializer);
return serializer!;
}

throw new Exception($"No serializer found for {type}");
}

private EventDeserializerDelegate BuildFixedSizeDeserializer(EventDefinition definitions, MemberDefinition[] allDefinitions, List<MemberDefinition> fixedParams, int fixedSize)
{
var outParam = Expression.Parameter(typeof(IEvent).MakeByRefType());

var spanParam = Expression.Parameter(typeof(ReadOnlySpan<byte>));

var blockExprs = new List<Expression>();
Expand All @@ -256,11 +274,13 @@ private EventDeserializerDelegate BuildFixedSizeDeserializer(EventDefinition def

var ctorCall = Expression.New(definitions.Type.GetConstructors().First(c => c.GetParameters().Length == allDefinitions.Length),
allDefinitions.Select(d => d.Variable));
var casted = Expression.Convert(ctorCall, typeof(IEvent));
var casted = Expression.Assign(outParam, Expression.Convert(ctorCall, typeof(IEvent)));
blockExprs.Add(casted);

blockExprs.Add(Expression.Constant(fixedSize));

var outerBlock = Expression.Block(allDefinitions.Select(d => d.Variable), blockExprs);
var lambda = Expression.Lambda<EventDeserializerDelegate>(outerBlock, spanParam);
var lambda = Expression.Lambda<EventDeserializerDelegate>(outerBlock, [spanParam, outParam]);
return lambda.Compile();

}
Expand Down Expand Up @@ -406,4 +426,25 @@ private EventSerializerDelegate BuildFixedSizeSerializer(EventDefinition eventDe
}


public bool CanSerialize(Type valueType)
{
return valueType == typeof(IEvent);
}

public bool TryGetFixedSize(Type valueType, out int size)
{
size = 0;
return false;
}

public void Serialize<TWriter>(IEvent value, TWriter output) where TWriter : IBufferWriter<byte>
{
_serializerDelegates[value.GetType()](value);
}

public int Deserialize(ReadOnlySpan<byte> from, out IEvent value)
{
var used = _deserializerDelegates[BinaryPrimitives.ReadUInt128BigEndian(from)](SliceFastStart(from, 16), out value);
return 16 + used;
}
}
1 change: 1 addition & 0 deletions src/NexusMods.EventSourcing/Services.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public static class Services
public static IServiceCollection AddEventSourcing(this IServiceCollection services)
{
return services
.AddSingleton<ISerializer, GenericArraySerializer>()
.AddSingleton<ISerializer, GenericEntityIdSerializer>()
.AddSingleton<ISerializer, StringSerializer>()
.AddSingleton<ISerializer, BoolSerializer>()
Expand Down

0 comments on commit c223169

Please sign in to comment.