Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use boxed interface visitors instead of constrained generics #159

Merged
merged 3 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/artifacts/bin/bench/Debug/net7.0/bench.dll",
"program": "${workspaceFolder}/perf/bench/bin/Debug/net8.0/bench.dll",
"args": [],
"cwd": "${workspaceFolder}/bench",
"cwd": "${workspaceFolder}/perf/bench",
"console": "internalConsole",
"stopAtEntry": false,
"justMyCode": false,
Expand Down
147 changes: 89 additions & 58 deletions perf/bench/SampleTypes.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

#nullable disable

using System;
using Serde;

namespace Benchmarks
Expand Down Expand Up @@ -57,79 +58,115 @@ public partial record Location

public partial record LocationWrap : IDeserialize<Location>
{
[GenerateDeserialize]
private enum FieldNames : byte
static Benchmarks.Location Serde.IDeserialize<Benchmarks.Location>.Deserialize<D>(ref D deserializer)
{
Id = 1,
Address1 = 2,
Address2 = 3,
City = 4,
State = 5,
PostalCode = 6,
Name = 7,
PhoneNumber = 8,
Country = 9,
var fieldNames = new[]
{
"Id",
"Address1",
"Address2",
"City",
"State",
"PostalCode",
"Name",
"PhoneNumber",
"Country"
};
return deserializer.DeserializeType("Location", fieldNames, SerdeVisitor.Instance);
}

public static Location Deserialize<D>(ref D deserializer) where D : IDeserializer
{
return Deserialize<Location, D>(ref deserializer);
}
private sealed class LocationVisitor : IDeserializeVisitor<Location>
private sealed class SerdeVisitor : Serde.IDeserializeVisitor<Benchmarks.Location>
{
public string ExpectedTypeName => nameof(Location);
public static readonly SerdeVisitor Instance = new SerdeVisitor();
public string ExpectedTypeName => "Benchmarks.Location";

Location IDeserializeVisitor<Location>.VisitDictionary<D>(ref D d)
private sealed class FieldNameVisitor : Serde.IDeserialize<byte>, Serde.IDeserializeVisitor<byte>
{
int _l_id = default!;
string _l_address1 = default!;
string _l_address2 = default!;
string _l_city = default!;
string _l_state = default!;
string _l_postalCode = default!;
string _l_name = default!;
string _l_phoneNumber = default!;
string _l_country = default!;
short _r_assignedValid = 0b0;
while (d.TryGetNextKey<FieldNames, FieldNamesWrap>(out var key))
public static readonly FieldNameVisitor Instance = new FieldNameVisitor();
public static byte Deserialize<D>(ref D deserializer)
where D : IDeserializer => deserializer.DeserializeString(Instance);
public string ExpectedTypeName => "string";

byte Serde.IDeserializeVisitor<byte>.VisitString(string s) => VisitUtf8Span(System.Text.Encoding.UTF8.GetBytes(s));
public byte VisitUtf8Span(System.ReadOnlySpan<byte> s)
{
switch (s[0])
{
case (byte)'i'when s.SequenceEqual("id"u8):
return 1;
case (byte)'a'when s.SequenceEqual("address1"u8):
return 2;
case (byte)'a'when s.SequenceEqual("address2"u8):
return 3;
case (byte)'c'when s.SequenceEqual("city"u8):
return 4;
case (byte)'s'when s.SequenceEqual("state"u8):
return 5;
case (byte)'p'when s.SequenceEqual("postalCode"u8):
return 6;
case (byte)'n'when s.SequenceEqual("name"u8):
return 7;
case (byte)'p'when s.SequenceEqual("phoneNumber"u8):
return 8;
case (byte)'c'when s.SequenceEqual("country"u8):
return 9;
default:
return 0;
}
}
}

Benchmarks.Location Serde.IDeserializeVisitor<Benchmarks.Location>.VisitDictionary<D>(ref D d)
{
int _l_id = default !;
string _l_address1 = default !;
string _l_address2 = default !;
string _l_city = default !;
string _l_state = default !;
string _l_postalcode = default !;
string _l_name = default !;
string _l_phonenumber = default !;
string _l_country = default !;
ushort _r_assignedValid = 0b0;
while (d.TryGetNextKey<byte, FieldNameVisitor>(out byte key))
{
switch (key)
{
case FieldNames.Id:
case 1:
_l_id = d.GetNextValue<int, Int32Wrap>();
_r_assignedValid |= 1 << 0;
_r_assignedValid |= ((ushort)1) << 0;
break;
case FieldNames.Address1:
case 2:
_l_address1 = d.GetNextValue<string, StringWrap>();
_r_assignedValid |= 1 << 1;
_r_assignedValid |= ((ushort)1) << 1;
break;
case FieldNames.Address2:
case 3:
_l_address2 = d.GetNextValue<string, StringWrap>();
_r_assignedValid |= 1 << 2;
_r_assignedValid |= ((ushort)1) << 2;
break;
case FieldNames.City:
case 4:
_l_city = d.GetNextValue<string, StringWrap>();
_r_assignedValid |= 1 << 3;
_r_assignedValid |= ((ushort)1) << 3;
break;
case FieldNames.State:
case 5:
_l_state = d.GetNextValue<string, StringWrap>();
_r_assignedValid |= 1 << 4;
_r_assignedValid |= ((ushort)1) << 4;
break;
case FieldNames.PostalCode:
_l_postalCode = d.GetNextValue<string, StringWrap>();
_r_assignedValid |= 1 << 5;
case 6:
_l_postalcode = d.GetNextValue<string, StringWrap>();
_r_assignedValid |= ((ushort)1) << 5;
break;
case FieldNames.Name:
case 7:
_l_name = d.GetNextValue<string, StringWrap>();
_r_assignedValid |= 1 << 6;
_r_assignedValid |= ((ushort)1) << 6;
break;
case FieldNames.PhoneNumber:
_l_phoneNumber = d.GetNextValue<string, StringWrap>();
_r_assignedValid |= 1 << 7;
case 8:
_l_phonenumber = d.GetNextValue<string, StringWrap>();
_r_assignedValid |= ((ushort)1) << 7;
break;
case FieldNames.Country:
case 9:
_l_country = d.GetNextValue<string, StringWrap>();
_r_assignedValid |= 1 << 8;
_r_assignedValid |= ((ushort)1) << 8;
break;
}
}
Expand All @@ -139,27 +176,21 @@ Location IDeserializeVisitor<Location>.VisitDictionary<D>(ref D d)
throw new Serde.InvalidDeserializeValueException("Not all members were assigned");
}

var newType = new Location
var newType = new Benchmarks.Location()
{
Id = _l_id,
Address1 = _l_address1,
Address2 = _l_address2,
City = _l_city,
State = _l_state,
PostalCode = _l_postalCode,
PostalCode = _l_postalcode,
Name = _l_name,
PhoneNumber = _l_phoneNumber,
PhoneNumber = _l_phonenumber,
Country = _l_country,
};
return newType;
}
}

private static T Deserialize<T, D>(ref D deserialize)
where T : IDeserialize<T>
where D : IDeserializer
{
return T.Deserialize(ref deserialize);
}
}
}
1 change: 1 addition & 0 deletions perf/bench/bench.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<Deterministic>true</Deterministic>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>

<ItemGroup>
Expand Down
33 changes: 8 additions & 25 deletions src/generator/Generator.Deserialize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ private static MethodDeclarationSyntax GenerateDeserializeMethod(
// 3. Custom type
//
// var fieldNames = new[] { 'field1', 'field2', 'field3' ... };
// return deserializer.DeserializeType<'TypeName', 'GeneratedVisitorName'>('TypeName', fieldNames, visitor);
// return deserializer.DeserializeType('TypeName', fieldNames, visitor);

var serdeName = SerdeBuiltInName(typeSymbol.SpecialType);
var typeSyntax = ParseTypeName(typeSymbol.ToString());
Expand All @@ -80,13 +80,7 @@ private static MethodDeclarationSyntax GenerateDeserializeMethod(
stmts.Add(ReturnStatement(InvocationExpression(
QualifiedName(
IdentifierName("deserializer"),
GenericName(
Identifier("Deserialize" + serdeName),
TypeArgumentList(SeparatedList(new TypeSyntax[] {
typeSyntax,
IdentifierName(GeneratedVisitorName)
}))
)
IdentifierName("Deserialize" + serdeName)
),
ArgumentList(SeparatedList(new[] {
Argument(IdentifierName("visitor"))
Expand All @@ -99,13 +93,7 @@ private static MethodDeclarationSyntax GenerateDeserializeMethod(
stmts.Add(ReturnStatement(InvocationExpression(
QualifiedName(
IdentifierName("deserializer"),
GenericName(
Identifier("DeserializeString"),
TypeArgumentList(SeparatedList(new TypeSyntax[] {
typeSyntax,
IdentifierName(GeneratedVisitorName)
}))
)
IdentifierName("DeserializeString")
),
ArgumentList(SeparatedList(new[] {
Argument(IdentifierName("visitor"))
Expand All @@ -131,17 +119,11 @@ private static MethodDeclarationSyntax GenerateDeserializeMethod(
)
));

// return deserializer.DeserializeType<'TypeName', 'GeneratedVisitorName'>('TypeName', fieldNames, visitor);
// return deserializer.DeserializeType('TypeName', fieldNames, visitor);
stmts.Add(ReturnStatement(InvocationExpression(
QualifiedName(
IdentifierName("deserializer"),
GenericName(
Identifier("DeserializeType"),
TypeArgumentList(SeparatedList(new TypeSyntax[] {
typeSyntax,
IdentifierName(GeneratedVisitorName)
})))
),
IdentifierName("DeserializeType")),
ArgumentList(SeparatedList(new[] {
Argument(StringLiteral(typeSymbol.Name)),
Argument(IdentifierName("fieldNames")),
Expand Down Expand Up @@ -262,10 +244,11 @@ _ when System.MemoryExtensions.SequenceEqual(s, "{{formatted}}"u8) => {{typeName
private static MemberDeclarationSyntax GenerateFieldNameVisitor(ITypeSymbol type, string typeName, List<DataMemberSymbol> members)
{
var text = $$"""
private struct FieldNameVisitor : Serde.IDeserialize<byte>, Serde.IDeserializeVisitor<byte>
private sealed class FieldNameVisitor : Serde.IDeserialize<byte>, Serde.IDeserializeVisitor<byte>
{
public static readonly FieldNameVisitor Instance = new FieldNameVisitor();
public static byte Deserialize<D>(ref D deserializer) where D : IDeserializer
=> deserializer.DeserializeString<byte, FieldNameVisitor>(new FieldNameVisitor());
=> deserializer.DeserializeString(Instance);

public string ExpectedTypeName => "string";

Expand Down
40 changes: 20 additions & 20 deletions src/serde/IDeserialize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,25 +73,25 @@ bool TryGetNextEntry<K, DK, V, DV>([MaybeNullWhen(false)] out (K, V) next)

public interface IDeserializer
{
T DeserializeAny<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeBool<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeChar<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeByte<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeU16<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeU32<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeU64<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeSByte<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeI16<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeI32<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeI64<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeFloat<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeDouble<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeDecimal<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeString<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeIdentifier<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeType<T, V>(string typeName, ReadOnlySpan<string> fieldNames, V v) where V : IDeserializeVisitor<T>;
T DeserializeEnumerable<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeDictionary<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeNullableRef<T, V>(V v) where V : IDeserializeVisitor<T>;
T DeserializeAny<T>(IDeserializeVisitor<T> v);
T DeserializeBool<T>(IDeserializeVisitor<T> v);
T DeserializeChar<T>(IDeserializeVisitor<T> v);
T DeserializeByte<T>(IDeserializeVisitor<T> v);
T DeserializeU16<T>(IDeserializeVisitor<T> v);
T DeserializeU32<T>(IDeserializeVisitor<T> v);
T DeserializeU64<T>(IDeserializeVisitor<T> v);
T DeserializeSByte<T>(IDeserializeVisitor<T> v);
T DeserializeI16<T>(IDeserializeVisitor<T> v);
T DeserializeI32<T>(IDeserializeVisitor<T> v);
T DeserializeI64<T>(IDeserializeVisitor<T> v);
T DeserializeFloat<T>(IDeserializeVisitor<T> v);
T DeserializeDouble<T>(IDeserializeVisitor<T> v);
T DeserializeDecimal<T>(IDeserializeVisitor<T> v);
T DeserializeString<T>(IDeserializeVisitor<T> v);
T DeserializeIdentifier<T>(IDeserializeVisitor<T> v);
T DeserializeType<T>(string typeName, ReadOnlySpan<string> fieldNames, IDeserializeVisitor<T> v);
T DeserializeEnumerable<T>(IDeserializeVisitor<T> v);
T DeserializeDictionary<T>(IDeserializeVisitor<T> v);
T DeserializeNullableRef<T>(IDeserializeVisitor<T> v);
}
}
2 changes: 1 addition & 1 deletion src/serde/Wrappers.Dictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ void ISerialize<Dictionary<TKey, TValue>>.Serialize(Dictionary<TKey, TValue> val
{
static Dictionary<TKey, TValue> IDeserialize<Dictionary<TKey, TValue>>.Deserialize<D>(ref D deserializer)
{
return deserializer.DeserializeDictionary<Dictionary<TKey, TValue>, Visitor>(new Visitor());
return deserializer.DeserializeDictionary(new Visitor());
}
private struct Visitor : IDeserializeVisitor<Dictionary<TKey, TValue>>
{
Expand Down
8 changes: 3 additions & 5 deletions src/serde/Wrappers.List.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ void ISerialize.Serialize(ISerializer serializer)
{
static T[] IDeserialize<T[]>.Deserialize<D>(ref D deserializer)
{
return deserializer.DeserializeEnumerable<T[], SerdeVisitor>(new SerdeVisitor());
return deserializer.DeserializeEnumerable(new SerdeVisitor());
}
private struct SerdeVisitor : IDeserializeVisitor<T[]>
{
Expand Down Expand Up @@ -132,7 +132,7 @@ void ISerialize<List<T>>.Serialize(List<T> value, ISerializer serializer)
{
static List<T> IDeserialize<List<T>>.Deserialize<D>(ref D deserializer)
{
return deserializer.DeserializeEnumerable<List<T>, SerdeVisitor>(new SerdeVisitor());
return deserializer.DeserializeEnumerable(new SerdeVisitor());
}
private struct SerdeVisitor : IDeserializeVisitor<List<T>>
{
Expand Down Expand Up @@ -184,9 +184,7 @@ void ISerialize<ImmutableArray<T>>.Serialize(ImmutableArray<T> value, ISerialize
{
static ImmutableArray<T> IDeserialize<ImmutableArray<T>>.Deserialize<D>(ref D deserializer)
{
return deserializer.DeserializeEnumerable<
ImmutableArray<T>,
Visitor>(new Visitor());
return deserializer.DeserializeEnumerable(new Visitor());
}

private struct Visitor : IDeserializeVisitor<ImmutableArray<T>>
Expand Down
Loading
Loading