diff --git a/.gitignore b/.gitignore index bc78471..87e72e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# This is only for local build +PackageRedirects.props + ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2eb53eb --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/artifacts/bin/bench/debug/bench.dll", + "args": [], + "cwd": "${workspaceFolder}/bench", + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..49dab52 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/serde.msgpack.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/serde.msgpack.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/serde.msgpack.sln" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..e8704e1 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,18 @@ + + + + + <_ExcludePackage Include="@(PackageRedirect)" Exclude="@(PackageReference)"> + PackageName + %(PackageName) + + <_ExcludeProjects Include ="@(_ExcludePackage->Metadata('ProjectPath'))" /> + + %(ReferenceOutputAssembly) + %(OutputItemType) + + + + + \ No newline at end of file diff --git a/bench/DataGenerator.cs b/bench/DataGenerator.cs index 5338905..691c509 100644 --- a/bench/DataGenerator.cs +++ b/bench/DataGenerator.cs @@ -24,19 +24,6 @@ public static T GenerateSerialize() where T : Serde.ISerializeProvider } - public static Location CreateLocation() => new Location - { - Id = 1234, - Address1 = "The Street Name", - Address2 = "20/11", - City = "The City", - State = "The State", - PostalCode = "abc-12", - Name = "Nonexisting", - PhoneNumber = "+0 11 222 333 44", - Country = "The Greatest" - }; - public static byte[] GenerateDeserialize() { if (typeof(T) == typeof(LoginViewModel)) diff --git a/bench/DeserializeFromString.cs b/bench/DeserializeFromString.cs index bd7ce38..7610140 100644 --- a/bench/DeserializeFromString.cs +++ b/bench/DeserializeFromString.cs @@ -8,15 +8,13 @@ namespace Benchmarks { - [GenericTypeArguments(typeof(Location), typeof(LocationWrap))] - public class DeserializeFromString + [GenericTypeArguments(typeof(Location))] + public class DeserializeFromString where T : Serde.IDeserializeProvider - where U : Serde.IDeserializeProvider { private byte[] value = null!; - private readonly IDeserialize _proxy = T.DeserializeInstance; - private readonly IDeserialize _manualProxy = U.DeserializeInstance; + private readonly IDeserialize _proxy = T.Instance; [GlobalSetup] public void Setup() @@ -32,11 +30,5 @@ public void Setup() [Benchmark] public T SerdeMsgPack() => Serde.MsgPack.MsgPackSerializer.Deserialize>(value, _proxy); - - [Benchmark] - public T SerdeMsgPackManual() => Serde.MsgPack.MsgPackSerializer.Deserialize>(value, _manualProxy); - - // DataContractJsonSerializer does not provide an API to serialize to string - // so it's not included here (apples vs apples thing) } } \ No newline at end of file diff --git a/bench/Program.cs b/bench/Program.cs index 705b599..d627cd0 100644 --- a/bench/Program.cs +++ b/bench/Program.cs @@ -15,7 +15,7 @@ } var loc1 = MessagePackSerializer.Deserialize(msg1); -var loc2 = Serde.MsgPack.MsgPackSerializer.Deserialize(msg1, LocationWrap.Instance); +var loc2 = Serde.MsgPack.MsgPackSerializer.Deserialize(msg1); Console.WriteLine("Checking correctness of serialization: " + (loc1 == loc2)); if (loc1 != loc2) @@ -31,5 +31,5 @@ Serialization is not correct } var config = DefaultConfig.Instance.AddDiagnoser(MemoryDiagnoser.Default); -var summary = BenchmarkSwitcher.FromAssembly(typeof(DeserializeFromString<,>).Assembly) +var summary = BenchmarkSwitcher.FromAssembly(typeof(DeserializeFromString<>).Assembly) .Run(args, config); \ No newline at end of file diff --git a/bench/SampleTypes.cs b/bench/SampleTypes.cs index dd3f887..7248993 100644 --- a/bench/SampleTypes.cs +++ b/bench/SampleTypes.cs @@ -17,26 +17,18 @@ public partial class LoginViewModel } [GenerateSerialize, GenerateDeserialize] - [MessagePackObject] + [MessagePackObject(keyAsPropertyName: true)] + [SerdeTypeOptions(MemberFormat = MemberFormat.None)] public partial record Location { - [Key(0)] public int Id { get; set; } - [Key(1)] public string Address1 { get; set; } - [Key(2)] public string Address2 { get; set; } - [Key(3)] public string City { get; set; } - [Key(4)] public string State { get; set; } - [Key(5)] public string PostalCode { get; set; } - [Key(6)] public string Name { get; set; } - [Key(7)] public string PhoneNumber { get; set; } - [Key(8)] public string Country { get; set; } public static Location Sample => new Location @@ -52,110 +44,4 @@ public partial record Location Country = "The Greatest" }; } - - public sealed partial class LocationWrap : IDeserialize, IDeserializeProvider - { - public static LocationWrap Instance { get; } = new(); - static IDeserialize IDeserializeProvider.DeserializeInstance => Instance; - private LocationWrap() { } - - public static ISerdeInfo SerdeInfo { get; } = Serde.SerdeInfo.MakeCustom( - "Location", - typeof(Location).GetCustomAttributesData(), - [ - ("id", Int32Proxy.SerdeInfo, typeof(Location).GetProperty("Id")!), - ("address1", StringProxy.SerdeInfo, typeof(Location).GetProperty("Address1")!), - ("address2", StringProxy.SerdeInfo, typeof(Location).GetProperty("Address2")!), - ("city", StringProxy.SerdeInfo, typeof(Location).GetProperty("City")!), - ("state", StringProxy.SerdeInfo, typeof(Location).GetProperty("State")!), - ("postalCode", StringProxy.SerdeInfo, typeof(Location).GetProperty("PostalCode")!), - ("name", StringProxy.SerdeInfo, typeof(Location).GetProperty("Name")!), - ("phoneNumber", StringProxy.SerdeInfo, typeof(Location).GetProperty("PhoneNumber")!), - ("country", StringProxy.SerdeInfo, typeof(Location).GetProperty("Country")!) - ]); - - Benchmarks.Location Serde.IDeserialize.Deserialize(IDeserializer deserializer) - { - 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; - - var _l_serdeInfo = SerdeInfo; - var typeDeserialize = deserializer.ReadType(_l_serdeInfo); - int index; - while ((index = typeDeserialize.TryReadIndex(_l_serdeInfo, out _)) != IDeserializeType.EndOfType) - { - switch (index) - { - case 0: - _l_id = typeDeserialize.ReadI32(index); - _r_assignedValid |= ((ushort)1) << 0; - break; - case 1: - _l_address1 = typeDeserialize.ReadString(index); - _r_assignedValid |= ((ushort)1) << 1; - break; - case 2: - _l_address2 = typeDeserialize.ReadString(index); - _r_assignedValid |= ((ushort)1) << 2; - break; - case 3: - _l_city = typeDeserialize.ReadString(index); - _r_assignedValid |= ((ushort)1) << 3; - break; - case 4: - _l_state = typeDeserialize.ReadString(index); - _r_assignedValid |= ((ushort)1) << 4; - break; - case 5: - _l_postalcode = typeDeserialize.ReadString(index); - _r_assignedValid |= ((ushort)1) << 5; - break; - case 6: - _l_name = typeDeserialize.ReadString(index); - _r_assignedValid |= ((ushort)1) << 6; - break; - case 7: - _l_phonenumber = typeDeserialize.ReadString(index); - _r_assignedValid |= ((ushort)1) << 7; - break; - case 8: - _l_country = typeDeserialize.ReadString(index); - _r_assignedValid |= ((ushort)1) << 8; - break; - case Serde.IDeserializeType.IndexNotFound: - typeDeserialize.SkipValue(); - break; - default: - throw new InvalidOperationException("Unexpected index: " + index); - } - } - - if (_r_assignedValid != 0b111111111) - { - throw Serde.DeserializeException.UnassignedMember(); - } - - var newType = new Benchmarks.Location() - { - Id = _l_id, - Address1 = _l_address1, - Address2 = _l_address2, - City = _l_city, - State = _l_state, - PostalCode = _l_postalcode, - Name = _l_name, - PhoneNumber = _l_phonenumber, - Country = _l_country, - }; - return newType; - } - } } \ No newline at end of file diff --git a/bench/SerializeToBytes.cs b/bench/SerializeToBytes.cs new file mode 100644 index 0000000..046b405 --- /dev/null +++ b/bench/SerializeToBytes.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using BenchmarkDotNet.Attributes; +using MessagePack; +using Serde; + +namespace Benchmarks +{ + [GenericTypeArguments(typeof(Location))] + public class SerializeToBytes + where T : Serde.ISerializeProvider + { + private T value = default!; + + private readonly ISerialize _proxy = T.Instance; + + [GlobalSetup] + public void Setup() + { + value = DataGenerator.GenerateSerialize(); + } + + [Benchmark] + public byte[] MessagePack() + { + return MessagePackSerializer.Serialize(value); + } + + [Benchmark] + public byte[] SerdeMsgPack() => Serde.MsgPack.MsgPackSerializer.Serialize(value, _proxy); + } +} \ No newline at end of file diff --git a/src/MsgPackSerializer.cs b/src/MsgPackSerializer.cs index ddd944a..903e09b 100644 --- a/src/MsgPackSerializer.cs +++ b/src/MsgPackSerializer.cs @@ -7,18 +7,11 @@ public static class MsgPackSerializer { public static byte[] Serialize(T value) where T : ISerializeProvider - { - using var buffer = new ScratchBuffer(); - var writer = new MsgPackWriter(buffer); - var serializeObject = T.SerializeInstance; - serializeObject.Serialize(value, writer); - return buffer.Span.ToArray(); - } + => Serialize(value, T.Instance); - public static byte[] Serialize(T value, U proxy) - where U : ISerialize + public static byte[] Serialize(T value, ISerialize proxy) { - using var buffer = new ScratchBuffer(); + using var buffer = new ScratchBuffer(1024); var writer = new MsgPackWriter(buffer); proxy.Serialize(value, writer); return buffer.Span.ToArray(); @@ -31,4 +24,11 @@ public static T Deserialize(byte[] bytes, U proxy) using var reader = new MsgPackReader(byteBuffer); return proxy.Deserialize(reader); } + public static T Deserialize(byte[] bytes) + where T : IDeserializeProvider + { + var byteBuffer = new ArrayBufReader(bytes); + using var reader = new MsgPackReader(byteBuffer); + return T.Instance.Deserialize(reader); + } } diff --git a/src/ScratchBuffer.cs b/src/ScratchBuffer.cs index 7a48772..1a9731f 100644 --- a/src/ScratchBuffer.cs +++ b/src/ScratchBuffer.cs @@ -6,6 +6,16 @@ internal sealed class ScratchBuffer : IDisposable { + public ScratchBuffer() { } + public ScratchBuffer(int capacity) + { + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + _rented = ArrayPool.Shared.Rent(capacity); + } + private byte[]? _rented; private int _count; public int Count @@ -26,8 +36,14 @@ public int Count public int Capacity => _rented?.Length ?? 0; + /// + /// The underlying buffer, including unused space at the end. + /// public Span BufferSpan => _rented ?? default; + /// + /// A span of the used portion of the buffer. + /// public Span Span => _count == 0 ? default : BufferSpan.Slice(0, _count); public void Add(byte value) @@ -54,19 +70,21 @@ private void AddSlow(byte value) public void AddRange(ReadOnlySpan span) { - if (!span.IsEmpty) - { - var buffer = BufferSpan; - var count = Count; - if (buffer.Length - count < span.Length) - { - EnsureCapacity(checked(count + span.Length)); - buffer = BufferSpan; - } + var buffer = GetAppendSpan(span.Length); + span.CopyTo(buffer); + Count += span.Length; + } - span.CopyTo(buffer[count..]); - Count = count + span.Length; - } + /// + /// Get a span into the buffer that starts at the end of the current + /// buffer and is large enough to hold the specified number of bytes. + /// The is not updated. + /// + public Span GetAppendSpan(int size) + { + var count = Count; + EnsureCapacity(count + size); + return BufferSpan.Slice(count, size); } public void EnsureCapacity(int capacity) diff --git a/src/Serde.MsgPack.csproj b/src/Serde.MsgPack.csproj index 5436206..7c0a8b9 100644 --- a/src/Serde.MsgPack.csproj +++ b/src/Serde.MsgPack.csproj @@ -4,11 +4,11 @@ net8.0 enable enable - 0.1.0-preview1 + 0.1.0-preview2 - + diff --git a/src/reader/MsgPackReader.Collections.cs b/src/reader/MsgPackReader.Collections.cs new file mode 100644 index 0000000..7036829 --- /dev/null +++ b/src/reader/MsgPackReader.Collections.cs @@ -0,0 +1,134 @@ + +namespace Serde.MsgPack; + +partial class MsgPackReader +{ + private struct DeserializeCollection(MsgPackReader deserializer, bool isDict, int length) : ITypeDeserializer + { + private int _index; + int? ITypeDeserializer.SizeOpt => isDict switch { + true => length / 2, + false => length, + }; + + bool ITypeDeserializer.ReadBool(ISerdeInfo info, int index) + { + var v = deserializer.ReadBool(); + _index++; + return v; + } + + char ITypeDeserializer.ReadChar(ISerdeInfo info, int index) + { + var v = deserializer.ReadChar(); + _index++; + return v; + } + + decimal ITypeDeserializer.ReadDecimal(ISerdeInfo info, int index) + { + var v = deserializer.ReadDecimal(); + _index++; + return v; + } + + float ITypeDeserializer.ReadF32(ISerdeInfo info, int index) + { + var v = deserializer.ReadF32(); + _index++; + return v; + } + + double ITypeDeserializer.ReadF64(ISerdeInfo info, int index) + { + var v = deserializer.ReadF64(); + _index++; + return v; + } + + short ITypeDeserializer.ReadI16(ISerdeInfo info, int index) + { + var v = deserializer.ReadI16(); + _index++; + return v; + } + + int ITypeDeserializer.ReadI32(ISerdeInfo info, int index) + { + var v = deserializer.ReadI32(); + _index++; + return v; + } + + long ITypeDeserializer.ReadI64(ISerdeInfo info, int index) + { + var v = deserializer.ReadI64(); + _index++; + return v; + } + + sbyte ITypeDeserializer.ReadI8(ISerdeInfo info, int index) + { + var v = deserializer.ReadI8(); + _index++; + return v; + } + + string ITypeDeserializer.ReadString(ISerdeInfo info, int index) + { + var v = deserializer.ReadString(); + _index++; + return v; + } + + ushort ITypeDeserializer.ReadU16(ISerdeInfo info, int index) + { + var v = deserializer.ReadU16(); + _index++; + return v; + } + + uint ITypeDeserializer.ReadU32(ISerdeInfo info, int index) + { + var v = deserializer.ReadU32(); + _index++; + return v; + } + + ulong ITypeDeserializer.ReadU64(ISerdeInfo info, int index) + { + var v = deserializer.ReadU64(); + _index++; + return v; + } + + byte ITypeDeserializer.ReadU8(ISerdeInfo info, int index) + { + var v = deserializer.ReadU8(); + _index++; + return v; + } + + int ITypeDeserializer.TryReadIndex(ISerdeInfo info, out string? errorName) + { + errorName = null; + if (_index >= length) + { + return ITypeDeserializer.EndOfType; + } + return _index; + } + + T ITypeDeserializer.ReadValue(ISerdeInfo info, int index, IDeserialize d) + { + var next = d.Deserialize(deserializer); + _index++; + return next; + } + + void ITypeDeserializer.SkipValue(ISerdeInfo info, int index) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/reader/MsgPackReader.IDeserializeCollection.cs b/src/reader/MsgPackReader.IDeserializeCollection.cs deleted file mode 100644 index 1b77af9..0000000 --- a/src/reader/MsgPackReader.IDeserializeCollection.cs +++ /dev/null @@ -1,26 +0,0 @@ - -namespace Serde.MsgPack; - -partial class MsgPackReader -{ - private struct DeserializeCollection(MsgPackReader deserializer, bool isDict, int length) : IDeserializeCollection - { - private int _index; - int? IDeserializeCollection.SizeOpt => isDict switch { - true => length / 2, - false => length, - }; - - bool IDeserializeCollection.TryReadValue(ISerdeInfo typeInfo, D d, out T next) - { - if (_index >= length) - { - next = default!; - return false; - } - next = d.Deserialize(deserializer); - _index++; - return true; - } - } -} \ No newline at end of file diff --git a/src/reader/MsgPackReader.IDeserializeType.cs b/src/reader/MsgPackReader.IDeserializeType.cs deleted file mode 100644 index 9242f35..0000000 --- a/src/reader/MsgPackReader.IDeserializeType.cs +++ /dev/null @@ -1,70 +0,0 @@ - -namespace Serde.MsgPack; - -partial class MsgPackReader -{ - private struct DeserializeType(MsgPackReader deserializer) : IDeserializeType - { - private int _index; - - bool IDeserializeType.ReadBool(int index) => deserializer.ReadBool(); - - byte IDeserializeType.ReadByte(int index) => deserializer.ReadByte(); - - char IDeserializeType.ReadChar(int index) => (char)deserializer.ReadU16(); - - decimal IDeserializeType.ReadDecimal(int index) => deserializer.ReadDecimal(); - double IDeserializeType.ReadDouble(int index) => deserializer.ReadDouble(); - - float IDeserializeType.ReadFloat(int index) => deserializer.ReadFloat(); - - short IDeserializeType.ReadI16(int index) => deserializer.ReadI16(); - - int IDeserializeType.ReadI32(int index) => deserializer.ReadI32(); - - long IDeserializeType.ReadI64(int index) => deserializer.ReadI64(); - - sbyte IDeserializeType.ReadSByte(int index) => deserializer.ReadSByte(); - - string IDeserializeType.ReadString(int index) => deserializer.ReadString(); - - ushort IDeserializeType.ReadU16(int index) => deserializer.ReadU16(); - - uint IDeserializeType.ReadU32(int index) => deserializer.ReadU32(); - - ulong IDeserializeType.ReadU64(int index) => deserializer.ReadU64(); - - T IDeserializeType.ReadValue(int index, Serde.IDeserialize deserialize) - => deserialize.Deserialize(deserializer); - - void IDeserializeType.SkipValue() - => throw new NotImplementedException(); - - int IDeserializeType.TryReadIndex(ISerdeInfo map, out string? errorName) - { - // Two options: we have a struct/class, or an enum - if (map.Kind == InfoKind.CustomType) - { - // custom types always serialize in order, so just increment the index counter - if (_index >= map.FieldCount) - { - errorName = null; - return IDeserializeType.EndOfType; - } - errorName = null; - return _index++; - } - else if (map.Kind == InfoKind.Enum) - { - // Enums are serialized as the index of the enum member - errorName = null; - return deserializer.ReadI32(); - } - else - { - errorName = "Expected a custom type or enum, found: " + map.Kind; - return IDeserializeType.IndexNotFound; - } - } - } -} \ No newline at end of file diff --git a/src/reader/MsgPackReader.ITypeDeserializer.cs b/src/reader/MsgPackReader.ITypeDeserializer.cs new file mode 100644 index 0000000..6b6bb02 --- /dev/null +++ b/src/reader/MsgPackReader.ITypeDeserializer.cs @@ -0,0 +1,75 @@ + +namespace Serde.MsgPack; + +partial class MsgPackReader +{ + private struct DeserializeType(MsgPackReader deserializer) : ITypeDeserializer + { + int? ITypeDeserializer.SizeOpt => null; + + private int _count; + + bool ITypeDeserializer.ReadBool(ISerdeInfo info, int index) => deserializer.ReadBool(); + + byte ITypeDeserializer.ReadU8(ISerdeInfo info, int index) => deserializer.ReadU8(); + + char ITypeDeserializer.ReadChar(ISerdeInfo info, int index) => (char)deserializer.ReadU16(); + + decimal ITypeDeserializer.ReadDecimal(ISerdeInfo info, int index) => deserializer.ReadDecimal(); + double ITypeDeserializer.ReadF64(ISerdeInfo info, int index) => deserializer.ReadF64(); + + float ITypeDeserializer.ReadF32(ISerdeInfo info, int index) => deserializer.ReadF32(); + + short ITypeDeserializer.ReadI16(ISerdeInfo info, int index) => deserializer.ReadI16(); + + int ITypeDeserializer.ReadI32(ISerdeInfo info, int index) => deserializer.ReadI32(); + + long ITypeDeserializer.ReadI64(ISerdeInfo info, int index) => deserializer.ReadI64(); + + sbyte ITypeDeserializer.ReadI8(ISerdeInfo info, int index) => deserializer.ReadI8(); + + string ITypeDeserializer.ReadString(ISerdeInfo info, int index) => deserializer.ReadString(); + + ushort ITypeDeserializer.ReadU16(ISerdeInfo info, int index) => deserializer.ReadU16(); + + uint ITypeDeserializer.ReadU32(ISerdeInfo info, int index) => deserializer.ReadU32(); + + ulong ITypeDeserializer.ReadU64(ISerdeInfo info, int index) => deserializer.ReadU64(); + + T ITypeDeserializer.ReadValue(ISerdeInfo info, int index, IDeserialize deserialize) + => deserialize.Deserialize(deserializer); + + void ITypeDeserializer.SkipValue(ISerdeInfo info, int index) + => throw new NotImplementedException(); + + int ITypeDeserializer.TryReadIndex(ISerdeInfo map, out string? errorName) + { + // Two options: we have a struct/class, or an enum + if (map.Kind == InfoKind.CustomType) + { + if (_count >= map.FieldCount) + { + errorName = null; + return ITypeDeserializer.EndOfType; + } + // custom types are serialized like maps with field names as keys + var span = deserializer.ReadUtf8Span(); + int index = map.TryGetIndex(span); + errorName = index == ITypeDeserializer.IndexNotFound ? span.ToString() : null; + _count++; + return index; + } + else if (map.Kind == InfoKind.Enum) + { + // Enums are serialized as the index of the enum member + errorName = null; + return deserializer.ReadI32(); + } + else + { + errorName = "Expected a custom type or enum, found: " + map.Kind; + return ITypeDeserializer.IndexNotFound; + } + } + } +} \ No newline at end of file diff --git a/src/reader/MsgPackReader.cs b/src/reader/MsgPackReader.cs index d14bda5..8946973 100644 --- a/src/reader/MsgPackReader.cs +++ b/src/reader/MsgPackReader.cs @@ -30,11 +30,6 @@ private static void ThrowEof() throw new Exception("Unexpected end of stream"); } - T IDeserializer.ReadAny(IDeserializeVisitor v) - { - throw new NotImplementedException(); - } - bool IDeserializer.ReadBool() => ReadBool(); private bool ReadBool() @@ -81,7 +76,7 @@ private byte EatByteOrThrow() /// /// Eats at least one byte from the buffer. /// - bool TryReadByte(out byte result) + bool TryReadU8(out byte result) { return TryReadByte(EatByteOrThrow(), out result); } @@ -102,27 +97,27 @@ bool TryReadByte(byte b, out byte result) return false; } - byte IDeserializer.ReadByte() => ReadByte(); + byte IDeserializer.ReadU8() => ReadU8(); - private byte ReadByte() + private byte ReadU8() { - if (!TryReadByte(out var b)) + if (!TryReadU8(out var b)) { throw new Exception($"Expected byte 0xcc, got 0x{b:x}"); } return b; } - char IDeserializer.ReadChar() + public char ReadChar() { // char is encoded as either a 1-2 byte integer return (char)ReadU16(); } - IDeserializeCollection IDeserializer.ReadCollection(ISerdeInfo typeInfo) + private ITypeDeserializer ReadCollection(ISerdeInfo typeInfo) { var b = EatByteOrThrow(); - if (typeInfo.Kind == InfoKind.Enumerable) + if (typeInfo.Kind == InfoKind.List) { int length; if (b <= 0x9f) @@ -166,7 +161,7 @@ IDeserializeCollection IDeserializer.ReadCollection(ISerdeInfo typeInfo) } else { - throw new Exception("Expected collection"); + throw new Exception("Expected either List or Dictionary, found " + typeInfo.Kind); } } @@ -175,7 +170,7 @@ public decimal ReadDecimal() throw new NotImplementedException(); } - private double ReadDouble() + private double ReadF64() { var span = _reader.Span; if (span.Length < 9) @@ -192,9 +187,9 @@ private double ReadDouble() return result; } - double IDeserializer.ReadDouble() => ReadDouble(); + double IDeserializer.ReadF64() => ReadF64(); - private float ReadFloat() + private float ReadF32() { var span = _reader.Span; if (span.Length < 5) @@ -210,9 +205,9 @@ private float ReadFloat() _reader.Advance(5); return result; } - float IDeserializer.ReadFloat() => ReadFloat(); + float IDeserializer.ReadF32() => ReadF32(); - private bool TryReadSbyte(out sbyte s) + private bool TryReadI8(out sbyte s) { var first = EatByteOrThrow(); if (first <= 0x7f || first >= 0xe0) @@ -231,7 +226,7 @@ private bool TryReadSbyte(out sbyte s) private bool TryReadI16(out short i16) { - if (TryReadSbyte(out var sb)) + if (TryReadI8(out var sb)) { i16 = sb; return true; @@ -284,7 +279,7 @@ private int ReadI32() { if (!TryReadI32(out var i32)) { - throw new Exception("Expected 32-bit integer"); + throw new Exception($"Expected 32-bit integer, found 0x{i32:x}"); } return i32; } @@ -323,7 +318,7 @@ private long ReadI64() long IDeserializer.ReadI64() => ReadI64(); - T? IDeserializer.ReadNullableRef(TProxy proxy) + T? IDeserializer.ReadNullableRef(IDeserialize proxy) where T : class { var b = PeekByteOrThrow(); @@ -335,9 +330,9 @@ private long ReadI64() return proxy.Deserialize(this); } - public sbyte ReadSByte() + public sbyte ReadI8() { - if (!TryReadSbyte(out var sb)) + if (!TryReadI8(out var sb)) { throw new Exception("Expected signed byte"); } @@ -349,6 +344,13 @@ public sbyte ReadSByte() private string ReadString() { // strings are encoded in UTF8 as byte arrays + var span = ReadUtf8Span(); + var str = Encoding.UTF8.GetString(span); + return str; + } + + private ReadOnlySpan ReadUtf8Span() + { var b = EatByteOrThrow(); int length; if (b <= 0xbf) @@ -379,15 +381,17 @@ private string ReadString() { span = RefillNoEof(length); } - var str = Encoding.UTF8.GetString(span[..length]); _reader.Advance(length); - return str; + return span[..length]; } - IDeserializeType IDeserializer.ReadType(ISerdeInfo typeInfo) + ITypeDeserializer IDeserializer.ReadType(ISerdeInfo typeInfo) { - // Types are just an array of fields - if (typeInfo.Kind == InfoKind.CustomType) + if (typeInfo.Kind == InfoKind.List || typeInfo.Kind == InfoKind.Dictionary) + { + return ReadCollection(typeInfo); + } + else if (typeInfo.Kind == InfoKind.CustomType) { var fieldCount = typeInfo.FieldCount; var b = EatByteOrThrow(); @@ -442,7 +446,7 @@ private ushort ReadU16() private bool TryReadU16(out ushort u16) { - if (TryReadByte(out var b)) + if (TryReadU8(out var b)) { u16 = b; return true; diff --git a/src/writer/MsgPackWriter.EnumSerializer.cs b/src/writer/MsgPackWriter.EnumSerializer.cs new file mode 100644 index 0000000..0be0867 --- /dev/null +++ b/src/writer/MsgPackWriter.EnumSerializer.cs @@ -0,0 +1,39 @@ + +using System.Runtime.CompilerServices; + +namespace Serde.MsgPack; + +partial class MsgPackWriter +{ + private sealed class EnumSerializer(MsgPackWriter writer) : ITypeSerializer + { + public void End(ISerdeInfo info) + { + // Enums aren't nested, so no action needed + } + + private void ThrowInvalidEnum([CallerMemberName] string memberName = "") + { + throw new InvalidOperationException($"EnumSerializer cannot be used for non-enum types. Called from {memberName}."); + } + + public void WriteBool(ISerdeInfo typeInfo, int index, bool b) => ThrowInvalidEnum(); + public void WriteChar(ISerdeInfo typeInfo, int index, char c) => ThrowInvalidEnum(); + public void WriteDecimal(ISerdeInfo typeInfo, int index, decimal d) => ThrowInvalidEnum(); + public void WriteF64(ISerdeInfo typeInfo, int index, double d) => ThrowInvalidEnum(); + public void WriteValue(ISerdeInfo typeInfo, int index, T value, ISerialize serialize) + where T : class? + => ThrowInvalidEnum(); + public void WriteF32(ISerdeInfo typeInfo, int index, float f) => ThrowInvalidEnum(); + public void WriteI8(ISerdeInfo typeInfo, int index, sbyte b) => writer.WriteI8(b); + public void WriteI16(ISerdeInfo typeInfo, int index, short i16) => writer.WriteI16(i16); + public void WriteI32(ISerdeInfo typeInfo, int index, int i32) => writer.WriteI32(i32); + public void WriteI64(ISerdeInfo typeInfo, int index, long i64) => writer.WriteI64(i64); + public void WriteNull(ISerdeInfo typeInfo, int index) => ThrowInvalidEnum(); + public void WriteString(ISerdeInfo typeInfo, int index, string s) => ThrowInvalidEnum(); + public void WriteU8(ISerdeInfo typeInfo, int index, byte b) => writer.WriteU8(b); + public void WriteU16(ISerdeInfo typeInfo, int index, ushort u16) => writer.WriteU16(u16); + public void WriteU32(ISerdeInfo typeInfo, int index, uint u32) => writer.WriteU32(u32); + public void WriteU64(ISerdeInfo typeInfo, int index, ulong u64) => writer.WriteU64(u64); + } +} \ No newline at end of file diff --git a/src/writer/MsgPackWriter.ISerializeCollection.cs b/src/writer/MsgPackWriter.ISerializeCollection.cs index b24b77b..3f0b815 100644 --- a/src/writer/MsgPackWriter.ISerializeCollection.cs +++ b/src/writer/MsgPackWriter.ISerializeCollection.cs @@ -1,15 +1,62 @@ namespace Serde.MsgPack; -partial class MsgPackWriter : ISerializeCollection +partial class MsgPackWriter { - void ISerializeCollection.End(ISerdeInfo typeInfo) + private sealed class SerCollection(MsgPackWriter writer) : ITypeSerializer { - // No action needed, all collections are length-prefixed - } + public void End(ISerdeInfo typeInfo) + { + // No action needed, all collections are length-prefixed + } - void ISerializeCollection.SerializeElement(T value, U serialize) - { - serialize.Serialize(value, this); + public void WriteBool(ISerdeInfo typeInfo, int index, bool b) + => writer.WriteBool(b); + + public void WriteChar(ISerdeInfo typeInfo, int index, char c) + => writer.WriteChar(c); + + public void WriteDecimal(ISerdeInfo typeInfo, int index, decimal d) + => writer.WriteDecimal(d); + + public void WriteF32(ISerdeInfo typeInfo, int index, float f) + => writer.WriteF32(f); + + public void WriteF64(ISerdeInfo typeInfo, int index, double d) + => writer.WriteF64(d); + + public void WriteI16(ISerdeInfo typeInfo, int index, short i16) + => writer.WriteI16(i16); + + public void WriteI32(ISerdeInfo typeInfo, int index, int i32) + => writer.WriteI32(i32); + + public void WriteI64(ISerdeInfo typeInfo, int index, long i64) + => writer.WriteI64(i64); + + public void WriteI8(ISerdeInfo typeInfo, int index, sbyte b) + => writer.WriteI8(b); + + public void WriteNull(ISerdeInfo typeInfo, int index) + => writer.WriteNull(); + + public void WriteString(ISerdeInfo typeInfo, int index, string s) + => writer.WriteString(s); + + public void WriteU16(ISerdeInfo typeInfo, int index, ushort u16) + => writer.WriteU16(u16); + + public void WriteU32(ISerdeInfo typeInfo, int index, uint u32) + => writer.WriteU32(u32); + + public void WriteU64(ISerdeInfo typeInfo, int index, ulong u64) + => writer.WriteU64(u64); + + public void WriteU8(ISerdeInfo typeInfo, int index, byte b) + => writer.WriteU8(b); + + public void WriteValue(ISerdeInfo typeInfo, int index, T value, ISerialize serialize) + where T : class? + => serialize.Serialize(value, writer); } } \ No newline at end of file diff --git a/src/writer/MsgPackWriter.ISerializeType.cs b/src/writer/MsgPackWriter.ISerializeType.cs index aec0bd6..baccd46 100644 --- a/src/writer/MsgPackWriter.ISerializeType.cs +++ b/src/writer/MsgPackWriter.ISerializeType.cs @@ -3,15 +3,112 @@ namespace Serde.MsgPack; -partial class MsgPackWriter : ISerializeType +partial class MsgPackWriter : ITypeSerializer { - void ISerializeType.End() + private void WritePropertyName(ISerdeInfo typeInfo, int fieldIndex) { - // No action needed, all collections are length-prefixed + var fieldName = typeInfo.GetFieldName(fieldIndex); + WriteUtf8(fieldName); } - void ISerializeType.SerializeField(ISerdeInfo typeInfo, int index, T value, U serialize) + void ITypeSerializer.End(ISerdeInfo typeInfo) { + // No end, all types are length-prefixed + } + + void ITypeSerializer.WriteValue(ISerdeInfo typeInfo, int fieldIndex, T value, ISerialize serialize) + { + WritePropertyName(typeInfo, fieldIndex); serialize.Serialize(value, this); } + + void ITypeSerializer.WriteBool(ISerdeInfo typeInfo, int index, bool b) + { + WritePropertyName(typeInfo, index); + WriteBool(b); + } + + void ITypeSerializer.WriteChar(ISerdeInfo typeInfo, int index, char c) + { + WritePropertyName(typeInfo, index); + WriteChar(c); + } + + void ITypeSerializer.WriteU8(ISerdeInfo typeInfo, int index, byte b) + { + WritePropertyName(typeInfo, index); + WriteU8(b); + } + + void ITypeSerializer.WriteU16(ISerdeInfo typeInfo, int index, ushort u16) + { + WritePropertyName(typeInfo, index); + WriteU16(u16); + } + + void ITypeSerializer.WriteU32(ISerdeInfo typeInfo, int index, uint u32) + { + WritePropertyName(typeInfo, index); + WriteU32(u32); + } + + void ITypeSerializer.WriteU64(ISerdeInfo typeInfo, int index, ulong u64) + { + WritePropertyName(typeInfo, index); + WriteU64(u64); + } + + void ITypeSerializer.WriteI8(ISerdeInfo typeInfo, int index, sbyte b) + { + WritePropertyName(typeInfo, index); + WriteI8(b); + } + + void ITypeSerializer.WriteI16(ISerdeInfo typeInfo, int index, short i16) + { + WritePropertyName(typeInfo, index); + WriteI16(i16); + } + + void ITypeSerializer.WriteI32(ISerdeInfo typeInfo, int index, int i32) + { + WritePropertyName(typeInfo, index); + WriteI32(i32); + } + + void ITypeSerializer.WriteI64(ISerdeInfo typeInfo, int index, long i64) + { + WritePropertyName(typeInfo, index); + WriteI64(i64); + } + + void ITypeSerializer.WriteF32(ISerdeInfo typeInfo, int index, float f) + { + WritePropertyName(typeInfo, index); + WriteF32(f); + } + + void ITypeSerializer.WriteF64(ISerdeInfo typeInfo, int index, double d) + { + WritePropertyName(typeInfo, index); + WriteF64(d); + } + + void ITypeSerializer.WriteDecimal(ISerdeInfo typeInfo, int index, decimal d) + { + WritePropertyName(typeInfo, index); + WriteDecimal(d); + } + + void ITypeSerializer.WriteString(ISerdeInfo typeInfo, int index, string s) + { + WritePropertyName(typeInfo, index); + WriteString(s); + } + + void ITypeSerializer.WriteNull(ISerdeInfo typeInfo, int index) + { + WritePropertyName(typeInfo, index); + WriteNull(); + } } \ No newline at end of file diff --git a/src/writer/MsgPackWriter.cs b/src/writer/MsgPackWriter.cs index 85175f5..33db05d 100644 --- a/src/writer/MsgPackWriter.cs +++ b/src/writer/MsgPackWriter.cs @@ -1,30 +1,38 @@ using System.Buffers.Binary; using System.ComponentModel; +using System.Runtime.CompilerServices; using System.Text; namespace Serde.MsgPack; -internal sealed partial class MsgPackWriter(ScratchBuffer outBuffer) : ISerializer +internal sealed partial class MsgPackWriter : ISerializer { - private readonly ScratchBuffer _out = outBuffer; + private readonly ScratchBuffer _out; + private readonly EnumSerializer _enumSerializer; - void ISerializer.SerializeBool(bool b) + public MsgPackWriter(ScratchBuffer scratch) + { + _out = scratch; + _enumSerializer = new EnumSerializer(this); + } + + public void WriteBool(bool b) { _out.Add(b ? (byte)0xc3 : (byte)0xc2); } - void ISerializer.SerializeByte(byte b) => SerializeU64(b); + public void WriteU8(byte b) => WriteU64(b); - void ISerializer.SerializeChar(char c) => SerializeU64(c); + public void WriteChar(char c) => WriteU64(c); - ISerializeCollection ISerializer.SerializeCollection(ISerdeInfo typeInfo, int? length) + ITypeSerializer ISerializer.WriteCollection(ISerdeInfo typeInfo, int? length) { if (length is null) { throw new InvalidOperationException("Cannot serialize a collection with an unknown length."); } - if (typeInfo.Kind == InfoKind.Enumerable) + if (typeInfo.Kind == InfoKind.List) { if (length <= 15) { @@ -43,62 +51,61 @@ ISerializeCollection ISerializer.SerializeCollection(ISerdeInfo typeInfo, int? l } else if (typeInfo.Kind == InfoKind.Dictionary) { - if (length <= 15) - { - _out.Add((byte)(0x80 | length)); - } - else if (length <= 0xffff) - { - _out.Add(0xde); - WriteBigEndian((ushort)length); - } - else - { - _out.Add(0xdf); - WriteBigEndian((uint)length); - } + WriteMapLength((int)length); } else { throw new InvalidOperationException("Expected a collection, found: " + typeInfo.Kind); } - return this; + return new SerCollection(this); } - void ISerializer.SerializeDecimal(decimal d) + private void WriteMapLength(int length) { - throw new NotImplementedException(); + if (length <= 15) + { + _out.Add((byte)(0x80 | length)); + } + else if (length <= 0xffff) + { + _out.Add(0xde); + WriteBigEndian((ushort)length); + } + else + { + _out.Add(0xdf); + WriteBigEndian((uint)length); + } } - void ISerializer.SerializeDouble(double d) + public void WriteDecimal(decimal d) { - _out.Add(0xcb); - WriteBigEndian(d); + throw new NotImplementedException(); } - void ISerializer.SerializeEnumValue(ISerdeInfo typeInfo, int index, T value, U serialize) + public void WriteF64(double d) { - // Serialize the index of the enum member - SerializeI64(index); + _out.Add(0xcb); + WriteBigEndian(d); } - void ISerializer.SerializeFloat(float f) + public void WriteF32(float f) { _out.Add(0xca); WriteBigEndian(f); } - void ISerializer.SerializeI16(short i16) => SerializeI64(i16); + public void WriteI16(short i16) => WriteI64(i16); - void ISerializer.SerializeI32(int i32) => SerializeI64(i32); + public void WriteI32(int i32) => WriteI64(i32); - void ISerializer.SerializeI64(long i64) => SerializeI64(i64); + void ISerializer.WriteI64(long i64) => WriteI64(i64); - private void SerializeI64(long i64) + private void WriteI64(long i64) { if (i64 >= 0) { - SerializeU64((ulong)i64); + WriteU64((ulong)i64); } else if (i64 >= -32) { @@ -125,84 +132,104 @@ private void SerializeI64(long i64) WriteBigEndian(i64); } } - - void ISerializer.SerializeNull() + public void WriteNull() { _out.Add(0xc0); } - void ISerializer.SerializeSByte(sbyte b) => SerializeI64(b); + public void WriteI8(sbyte b) => WriteI64(b); - void ISerializer.SerializeString(string s) + private static readonly Encoding _utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + + public void WriteString(string s) { - var bytes = Encoding.UTF8.GetBytes(s); - if (bytes.Length <= 31) + // We can write the string directly to the output buffer, but the string + // is length-prefixed and we don't know precisely how long it will be until + // we encode it. So we need to write space for the length prefix first, then + // write the string, and finally go back, fill in the length prefix, and move + // the string if necessary. + var sLen = s.Length; + var maxByteCount = _utf8.GetMaxByteCount(sLen); + var appendSpan = _out.GetAppendSpan(checked(maxByteCount + 5 /* max length prefix */)); + int estimatedOffset = sLen switch { + <= 31 => 1, + <= 255 => 2, + <= 65535 => 3, + _ => 5 + }; + var u8Dest = appendSpan.Slice(estimatedOffset, maxByteCount); + int actualStrSize = _utf8.GetBytes(s, u8Dest); + // write prefix and move body if necessary + int actualOffset = WriteUtf8Header(actualStrSize, appendSpan); + if (actualOffset < estimatedOffset) { - _out.Add((byte)(0xa0 | bytes.Length)); + u8Dest.CopyTo(appendSpan.Slice(actualOffset, actualStrSize)); } - else if (bytes.Length <= 0xff) + _out.Count += actualOffset + actualStrSize; + } + + private void WriteUtf8(ReadOnlySpan str) + { + var span = _out.GetAppendSpan(str.Length + 5); + int offset = WriteUtf8Header(str.Length, span); + str.CopyTo(span.Slice(offset, str.Length)); + _out.Count += offset + str.Length; + } + + /// + /// Assumes that span is large enough to hold the header. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int WriteUtf8Header(int length, Span span) + { + int offset; + if (length <= 31) { - _out.Add(0xd9); - _out.Add((byte)bytes.Length); + offset = 1; + span[0] = (byte)(0xa0 | length); } - else if (bytes.Length <= 0xffff) + else if (length <= 0xff) { - _out.Add(0xda); - WriteBigEndian((ushort)bytes.Length); + offset = 2; + span[0] = 0xd9; + span[1] = unchecked((byte)length); } - else + else if (length <= 0xffff) { - _out.Add(0xdb); - WriteBigEndian((uint)bytes.Length); + offset = 3; + span[0] = 0xda; + BinaryPrimitives.WriteUInt16BigEndian(span.Slice(1), (ushort)length); } - foreach (var b in bytes) + else { - _out.Add(b); + offset = 5; + span[0] = 0xdb; + BinaryPrimitives.WriteUInt32BigEndian(span.Slice(1), (uint)length); } + return offset; } - ISerializeType ISerializer.SerializeType(ISerdeInfo typeInfo) + ITypeSerializer ISerializer.WriteType(ISerdeInfo typeInfo) { - // Check that, if the members are marked with [Key], they are in order. - // We do not support out-of-order keys. - for (int i = 0; i < typeInfo.FieldCount; i++) + switch (typeInfo.Kind) { - var attrs = typeInfo.GetFieldAttributes(i); - foreach (var attr in attrs) - { - if (attr.AttributeType.FullName == "MessagePack.KeyAttribute") - { - if (attr.ConstructorArguments is [ { Value: int index } ] && index != i) - { - throw new InvalidOperationException($"Found member {typeInfo.GetFieldStringName(i)} declared at index {i} but marked with [Key({index})]. Key indices must match declaration order."); - } - } - } - } - - // Write as an array, with the keys left implicit in the order - if (typeInfo.FieldCount <= 15) - { - _out.Add((byte)(0x90 | typeInfo.FieldCount)); - } - else if (typeInfo.FieldCount <= 0xffff) - { - _out.Add(0xdc); - WriteBigEndian((ushort)typeInfo.FieldCount); - } - else - { - _out.Add(0xdd); - WriteBigEndian((uint)typeInfo.FieldCount); + case InfoKind.CustomType: + // Custom types are serialized as a map + WriteMapLength(typeInfo.FieldCount); + return this; + case InfoKind.Enum: + return _enumSerializer; } - return this; + throw new InvalidOperationException("Unexpected info kind: " + typeInfo.Kind); } - void ISerializer.SerializeU16(ushort u16) => SerializeU64(u16); + public void WriteU16(ushort u16) => WriteU64(u16); + + public void WriteU32(uint u32) => WriteU64(u32); - void ISerializer.SerializeU32(uint u32) => SerializeU64(u32); + void ISerializer.WriteU64(ulong u64) => WriteU64(u64); - private void SerializeU64(ulong u64) + private void WriteU64(ulong u64) { if (u64 <= 0x7f) { @@ -230,47 +257,48 @@ private void SerializeU64(ulong u64) } } - void ISerializer.SerializeU64(ulong u64) => SerializeU64(u64); - private void WriteBigEndian(ushort value) { - _out.Add((byte)(value >> 8)); - _out.Add((byte)value); + var span = _out.GetAppendSpan(2); + BinaryPrimitives.WriteUInt16BigEndian( + span, + value); + _out.Count += 2; } private void WriteBigEndian(uint value) { - _out.Add((byte)(value >> 24)); - _out.Add((byte)(value >> 16)); - _out.Add((byte)(value >> 8)); - _out.Add((byte)value); + var span = _out.GetAppendSpan(4); + BinaryPrimitives.WriteUInt32BigEndian( + span, + value + ); + _out.Count += 4; } private void WriteBigEndian(ulong value) { - _out.Add((byte)(value >> 56)); - _out.Add((byte)(value >> 48)); - _out.Add((byte)(value >> 40)); - _out.Add((byte)(value >> 32)); - _out.Add((byte)(value >> 24)); - _out.Add((byte)(value >> 16)); - _out.Add((byte)(value >> 8)); - _out.Add((byte)value); + var span = _out.GetAppendSpan(8); + BinaryPrimitives.WriteUInt64BigEndian( + span, + value + ); + _out.Count += 8; } private void WriteBigEndian(short value) => WriteBigEndian((ushort)value); private void WriteBigEndian(int value) => WriteBigEndian((uint)value); private void WriteBigEndian(long value) => WriteBigEndian((ulong)value); - private void WriteBigEndian(float f) + private void WriteBigEndian(float value) { - Span bytes = stackalloc byte[4]; - BinaryPrimitives.WriteSingleBigEndian(bytes, f); - _out.AddRange(bytes); + var span = _out.GetAppendSpan(4); + BinaryPrimitives.WriteSingleBigEndian(span, value); + _out.Count += 4; } - private void WriteBigEndian(double d) + private void WriteBigEndian(double value) { - Span bytes = stackalloc byte[8]; - BinaryPrimitives.WriteDoubleBigEndian(bytes, d); - _out.AddRange(bytes); + var span = _out.GetAppendSpan(8); + BinaryPrimitives.WriteDoubleBigEndian(span, value); + _out.Count += 8; } } \ No newline at end of file diff --git a/tests/RoundTripTests.cs b/tests/RoundTripTests.cs index 9992ee9..d46a839 100644 --- a/tests/RoundTripTests.cs +++ b/tests/RoundTripTests.cs @@ -14,38 +14,38 @@ public void TestChar() [Fact] public void TestByte() { - AssertRoundTrip((byte)42, ByteProxy.Instance); - AssertRoundTrip((byte)0xf0, ByteProxy.Instance); + AssertRoundTrip((byte)42, U8Proxy.Instance); + AssertRoundTrip((byte)0xf0, U8Proxy.Instance); } [Fact] public void TestByteSizedUInt() { - AssertRoundTrip(42u, UInt32Proxy.Instance); + AssertRoundTrip(42u, U32Proxy.Instance); } [Fact] public void TestPositiveByteSizedInt() { - AssertRoundTrip(42, Int32Proxy.Instance); + AssertRoundTrip(42, I32Proxy.Instance); } [Fact] public void TestNegativeByteSizedInt() { - AssertRoundTrip(-42, Int32Proxy.Instance); + AssertRoundTrip(-42, I32Proxy.Instance); } [Fact] public void TestPositiveUInt16() { - AssertRoundTrip((ushort)0x1000, UInt16Proxy.Instance); + AssertRoundTrip((ushort)0x1000, U16Proxy.Instance); } [Fact] public void TestNegativeInt16() { - AssertRoundTrip((short)-0x1000, Int16Proxy.Instance); + AssertRoundTrip((short)-0x1000, I16Proxy.Instance); } [Fact] @@ -59,8 +59,8 @@ public void TestNullableString() { AssertRoundTrip< string?, - NullableRefProxy.Serialize, - NullableRefProxy.Deserialize>(null); + NullableRefProxy.Ser, + NullableRefProxy.De>(null); } [GenerateSerde] @@ -92,12 +92,11 @@ public void TestIntEnum() } [GenerateSerde] - [MessagePackObject] + [SerdeTypeOptions(MemberFormat = MemberFormat.None)] + [MessagePackObject(keyAsPropertyName: true)] public partial record Point { - [Key(0)] public int X { get; init; } - [Key(1)] public int Y { get; init; } } @@ -117,7 +116,7 @@ public partial record OutOfOrderKeys public int X { get; init; } } - [Fact] + [Fact(Skip = "Key ordering is not supported for Serde")] public void TestOutOfOrderKeys() { // Out of order keys are not supported for Serde @@ -131,9 +130,9 @@ public void TestOutOfOrderKeys() [Fact] public void TestDouble() { - AssertRoundTrip(3.14, DoubleProxy.Instance); - AssertRoundTrip(double.NaN, DoubleProxy.Instance); - AssertRoundTrip(double.PositiveInfinity, DoubleProxy.Instance); + AssertRoundTrip(3.14, F64Proxy.Instance); + AssertRoundTrip(double.NaN, F64Proxy.Instance); + AssertRoundTrip(double.PositiveInfinity, F64Proxy.Instance); } [Fact] @@ -141,16 +140,16 @@ public void TestArray() { AssertRoundTrip< int[], - ArrayProxy.Serialize, - ArrayProxy.Deserialize>(new[] { 1, 2, 3 }); + ArrayProxy.Ser, + ArrayProxy.De>(new[] { 1, 2, 3 }); AssertRoundTrip< string[], - ArrayProxy.Serialize, - ArrayProxy.Deserialize>(new[] { "a", "b", "c" }); + ArrayProxy.Ser, + ArrayProxy.De>(new[] { "a", "b", "c" }); AssertRoundTrip< Point[], - ArrayProxy.Serialize, - ArrayProxy.Deserialize>( + ArrayProxy.Ser, + ArrayProxy.De>( new[] { new Point { X = 1, Y = 2 }, new Point { X = 3, Y = 4 } }); } @@ -159,47 +158,47 @@ public void TestDictionary() { AssertRoundTrip< Dictionary, - DictProxy.Serialize, - DictProxy.Deserialize>( + DictProxy.Ser, + DictProxy.De>( new Dictionary { { "a", 1 }, { "b", 2 } }); AssertRoundTrip< Dictionary, - DictProxy.Serialize, - DictProxy.Deserialize>( + DictProxy.Ser, + DictProxy.De>( new Dictionary { { 1, "a" }, { 2, "b" } }); AssertRoundTrip< Dictionary, - DictProxy.Serialize, - DictProxy.Deserialize>( + DictProxy.Ser, + DictProxy.De>( new Dictionary { { new Point { X = 1, Y = 2 }, "a" }, { new Point { X = 3, Y = 4 }, "b" } }); } [Fact] public void TestFloat() { - AssertRoundTrip(3.14f, SingleProxy.Instance); - AssertRoundTrip(float.NaN, SingleProxy.Instance); - AssertRoundTrip(float.PositiveInfinity, SingleProxy.Instance); + AssertRoundTrip(3.14f, F32Proxy.Instance); + AssertRoundTrip(float.NaN, F32Proxy.Instance); + AssertRoundTrip(float.PositiveInfinity, F32Proxy.Instance); } private static void AssertRoundTrip(T expected) where T : ISerializeProvider, IDeserializeProvider, IEquatable { - AssertRoundTrip(expected, T.SerializeInstance, T.DeserializeInstance); + AssertRoundTrip(expected, SerializeProvider.GetSerialize(), DeserializeProvider.GetDeserialize()); } private static void AssertRoundTrip(T expected, TSerialize serializeObject) where TSerialize : ISerialize, IDeserializeProvider { - AssertRoundTrip(expected, serializeObject, TSerialize.DeserializeInstance); + AssertRoundTrip(expected, serializeObject, TSerialize.Instance); } private static void AssertRoundTrip(T expected) where TSerialize : ISerializeProvider where TDeserialize : IDeserializeProvider { - var serialized = MsgPackSerializer.Serialize(expected, TSerialize.SerializeInstance); - var actual = MsgPackSerializer.Deserialize>(serialized, TDeserialize.DeserializeInstance); + var serialized = MsgPackSerializer.Serialize(expected, TSerialize.Instance); + var actual = MsgPackSerializer.Deserialize>(serialized, TDeserialize.Instance); Assert.Equal(expected, actual); } diff --git a/tests/Serde.MsgPack.Tests.csproj b/tests/Serde.MsgPack.Tests.csproj index 250e0ed..1952564 100644 --- a/tests/Serde.MsgPack.Tests.csproj +++ b/tests/Serde.MsgPack.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/tests/SerializeOracleTests.cs b/tests/SerializeOracleTests.cs index e028641..e4b500b 100644 --- a/tests/SerializeOracleTests.cs +++ b/tests/SerializeOracleTests.cs @@ -10,8 +10,8 @@ public partial class SerializeOracleTests [Fact] public void TestByte() { - AssertMsgPackEqual((byte)42, ByteProxy.Instance); - AssertMsgPackEqual((byte)0xf0, ByteProxy.Instance); + AssertMsgPackEqual((byte)42, U8Proxy.Instance); + AssertMsgPackEqual((byte)0xf0, U8Proxy.Instance); } [Fact] @@ -23,31 +23,31 @@ public void TestChar() [Fact] public void TestByteSizedUInt() { - AssertMsgPackEqual(42u, UInt32Proxy.Instance); + AssertMsgPackEqual(42u, U32Proxy.Instance); } [Fact] public void TestPositiveByteSizedInt() { - AssertMsgPackEqual(42, Int32Proxy.Instance); + AssertMsgPackEqual(42, I32Proxy.Instance); } [Fact] public void TestNegativeByteSizedInt() { - AssertMsgPackEqual(-42, Int32Proxy.Instance); + AssertMsgPackEqual(-42, I32Proxy.Instance); } [Fact] public void TestPositiveUInt16() { - AssertMsgPackEqual((ushort)0x1000, UInt16Proxy.Instance); + AssertMsgPackEqual((ushort)0x1000, U16Proxy.Instance); } [Fact] public void TestNegativeInt16() { - AssertMsgPackEqual((short)-0x1000, Int16Proxy.Instance); + AssertMsgPackEqual((short)-0x1000, I16Proxy.Instance); } [Fact] @@ -59,7 +59,7 @@ public void TestString() [Fact] public void TestNullableString() { - AssertMsgPackEqual((string?)null, NullableRefProxy.Serialize.Instance); + AssertMsgPackEqual((string?)null, NullableRefProxy.Ser.Instance); } [GenerateSerialize] @@ -91,12 +91,11 @@ public void TestIntEnum() } [GenerateSerialize] - [MessagePackObject] + [SerdeTypeOptions(MemberFormat = MemberFormat.None)] + [MessagePackObject(keyAsPropertyName: true)] public partial record Point { - [Key(0)] public int X { get; init; } - [Key(1)] public int Y { get; init; } } @@ -116,7 +115,7 @@ public partial record OutOfOrderKeys public int X { get; init; } } - [Fact] + [Fact(Skip = "Keys are not supported for Serde")] public void TestOutOfOrderKeys() { // Out of order keys are not supported for Serde @@ -130,37 +129,37 @@ public void TestOutOfOrderKeys() [Fact] public void TestDouble() { - AssertMsgPackEqual(3.14, DoubleProxy.Instance); - AssertMsgPackEqual(double.NaN, DoubleProxy.Instance); - AssertMsgPackEqual(double.PositiveInfinity, DoubleProxy.Instance); + AssertMsgPackEqual(3.14, F64Proxy.Instance); + AssertMsgPackEqual(double.NaN, F64Proxy.Instance); + AssertMsgPackEqual(double.PositiveInfinity, F64Proxy.Instance); } [Fact] public void TestArray() { - AssertMsgPackEqual(new[] { 1, 2, 3 }, ArrayProxy.Serialize.Instance); - AssertMsgPackEqual(new[] { "a", "b", "c" }, ArrayProxy.Serialize.Instance); + AssertMsgPackEqual(new[] { 1, 2, 3 }, ArrayProxy.Ser.Instance); + AssertMsgPackEqual(new[] { "a", "b", "c" }, ArrayProxy.Ser.Instance); AssertMsgPackEqual(new[] { new Point { X = 1, Y = 2 }, new Point { X = 3, Y = 4 } }, - ArrayProxy.Serialize.Instance); + ArrayProxy.Ser.Instance); } [Fact] public void TestDictionary() { AssertMsgPackEqual(new Dictionary { { "a", 1 }, { "b", 2 } }, - DictProxy.Serialize.Instance); + DictProxy.Ser.Instance); AssertMsgPackEqual(new Dictionary { { 1, "a" }, { 2, "b" } }, - DictProxy.Serialize.Instance); + DictProxy.Ser.Instance); AssertMsgPackEqual(new Dictionary { { new Point { X = 1, Y = 2 }, "a" }, { new Point { X = 3, Y = 4 }, "b" } }, - DictProxy.Serialize.Instance); + DictProxy.Ser.Instance); } [Fact] public void TestFloat() { - AssertMsgPackEqual(3.14f, SingleProxy.Instance); - AssertMsgPackEqual(float.NaN, SingleProxy.Instance); - AssertMsgPackEqual(float.PositiveInfinity, SingleProxy.Instance); + AssertMsgPackEqual(3.14f, F32Proxy.Instance); + AssertMsgPackEqual(float.NaN, F32Proxy.Instance); + AssertMsgPackEqual(float.PositiveInfinity, F32Proxy.Instance); } private void AssertMsgPackEqual(T value, U proxy) @@ -172,6 +171,6 @@ private void AssertMsgPackEqual(T value, U proxy) } private void AssertMsgPackEqual(T value) where T : ISerializeProvider - => AssertMsgPackEqual(value, T.SerializeInstance); + => AssertMsgPackEqual(value, T.Instance); }