diff --git a/NetSerializer.UnitTests/HalfTest.cs b/NetSerializer.UnitTests/HalfTest.cs new file mode 100644 index 0000000..572557d --- /dev/null +++ b/NetSerializer.UnitTests/HalfTest.cs @@ -0,0 +1,49 @@ +#if NET5_0 +using System; +using System.IO; +using NUnit.Framework; + +namespace NetSerializer.UnitTests +{ + [TestFixture] + [TestOf(typeof(Primitives))] + [Parallelizable(ParallelScope.All)] + public class HalfTest + { + [Test] + public void Test() + { + var serializer = new Serializer(new[] {typeof(SerializationType)}); + + var stream = new MemoryStream(); + var obj = new SerializationType + { + R = (Half) 0, + G = (Half) 12.34, + B = (Half) 0.1, + A = Half.PositiveInfinity, + }; + + serializer.Serialize(stream, obj); + + stream.Position = 0; + + var read = (SerializationType) serializer.Deserialize(stream); + + Assert.That(read.R, Is.EqualTo(obj.R)); + Assert.That(read.G, Is.EqualTo(obj.G)); + Assert.That(read.B, Is.EqualTo(obj.B)); + Assert.That(read.A, Is.EqualTo(obj.A)); + } + + [Serializable] + private class SerializationType + { + public Half R; + public Half G; + public Half B; + public Half A; + } + } +} +#endif diff --git a/NetSerializer.UnitTests/NetSerializer.UnitTests.csproj b/NetSerializer.UnitTests/NetSerializer.UnitTests.csproj new file mode 100644 index 0000000..03ef9b8 --- /dev/null +++ b/NetSerializer.UnitTests/NetSerializer.UnitTests.csproj @@ -0,0 +1,26 @@ + + + + net45;netcoreapp3.1;net5.0 + + false + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + diff --git a/NetSerializer.UnitTests/PrimitivesTest.cs b/NetSerializer.UnitTests/PrimitivesTest.cs new file mode 100644 index 0000000..5bfdd2d --- /dev/null +++ b/NetSerializer.UnitTests/PrimitivesTest.cs @@ -0,0 +1,130 @@ +using System; +using System.IO; +using NUnit.Framework; + +namespace NetSerializer.UnitTests +{ + [TestFixture] + [TestOf(typeof(Primitives))] + [Parallelizable(ParallelScope.All)] + public class PrimitivesTest + { +#if NET5_0 +#if NO_UNSAFE + [Ignore("Float and half tests are inacurrate due to rounding when NO_UNSAFE is enabled.")] +#endif + [Test] + [TestCase(0)] + [TestCase(1)] + [TestCase(123.4)] + [TestCase(0.01)] + [TestCase(double.PositiveInfinity)] + [TestCase(double.NegativeInfinity)] + [TestCase(double.NaN)] + public void TestHalf(double val) + { + // Can't stick Half values in attributes so have to do this. + var half = (Half) val; + + var stream = new MemoryStream(); + Primitives.WritePrimitive(stream, half); + + stream.Position = 0; + + Primitives.ReadPrimitive(new ByteStream(stream), out Half read); + Assert.That(read, Is.EqualTo(half)); + } +#endif + +#if NO_UNSAFE + [Ignore("Float tests are inacurrate due to rounding when NO_UNSAFE is enabled.")] +#endif + [Test] + [TestCase(0)] + [TestCase(1)] + [TestCase(123.4f)] + [TestCase(0.01f)] + [TestCase(float.PositiveInfinity)] + [TestCase(float.NegativeInfinity)] + [TestCase(float.NaN)] + public void TestSingle(float val) + { + var stream = new MemoryStream(); + Primitives.WritePrimitive(stream, val); + + stream.Position = 0; + + Primitives.ReadPrimitive(new ByteStream(stream), out float read); + Assert.That(read, Is.EqualTo(val)); + } + + [Test] + [TestCase(0)] + [TestCase(1)] + [TestCase(123.4)] + [TestCase(0.01)] + [TestCase(float.PositiveInfinity)] + [TestCase(float.NegativeInfinity)] + [TestCase(float.NaN)] + public void TestDouble(double val) + { + var stream = new MemoryStream(); + Primitives.WritePrimitive(stream, val); + + stream.Position = 0; + + Primitives.ReadPrimitive(new ByteStream(stream), out double read); + Assert.That(read, Is.EqualTo(val)); + } + + // Stream wrapper that only reads one byte at a time to test the reading code. + private sealed class ByteStream : Stream + { + private readonly Stream _parent; + + public ByteStream(Stream parent) + { + _parent = parent; + } + + public override void Flush() + { + _parent.Flush(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return _parent.Seek(offset, origin); + } + + public override void SetLength(long value) + { + _parent.SetLength(value); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _parent.Read(buffer, offset, 1); + } + + public override void Write(byte[] buffer, int offset, int count) + { + _parent.Write(buffer, offset, count); + } + + public override bool CanRead => _parent.CanRead; + + public override bool CanSeek => _parent.CanSeek; + + public override bool CanWrite => _parent.CanWrite; + + public override long Length => _parent.Length; + + public override long Position + { + get => _parent.Position; + set => _parent.Position = value; + } + } + } +} diff --git a/NetSerializer.sln b/NetSerializer.sln index 8be0ec5..d1139da 100644 --- a/NetSerializer.sln +++ b/NetSerializer.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrimitiveTest", "PrimitiveTest\PrimitiveTest.csproj", "{CBA6D818-4B6A-4A80-95D5-7F5EC3FBB3C4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetSerializer.UnitTests", "NetSerializer.UnitTests\NetSerializer.UnitTests.csproj", "{17DC557B-DB9E-464C-A311-5AB83E5684AC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {CBA6D818-4B6A-4A80-95D5-7F5EC3FBB3C4}.Debug|Any CPU.Build.0 = Debug|Any CPU {CBA6D818-4B6A-4A80-95D5-7F5EC3FBB3C4}.Release|Any CPU.ActiveCfg = Release|Any CPU {CBA6D818-4B6A-4A80-95D5-7F5EC3FBB3C4}.Release|Any CPU.Build.0 = Release|Any CPU + {17DC557B-DB9E-464C-A311-5AB83E5684AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17DC557B-DB9E-464C-A311-5AB83E5684AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17DC557B-DB9E-464C-A311-5AB83E5684AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17DC557B-DB9E-464C-A311-5AB83E5684AC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/NetSerializer/NetSerializer.csproj b/NetSerializer/NetSerializer.csproj index e933d9a..bcee252 100644 --- a/NetSerializer/NetSerializer.csproj +++ b/NetSerializer/NetSerializer.csproj @@ -1,6 +1,6 @@  - netstandard2.1;net45;netcoreapp3.0 + netstandard2.1;net45;netcoreapp3.1;net5.0 A very fast and minimal serializer Tomi Valkeinen Copyright © 2012-2019 Tomi Valkeinen diff --git a/NetSerializer/Primitives.cs b/NetSerializer/Primitives.cs index 566ce82..d9d9e45 100644 --- a/NetSerializer/Primitives.cs +++ b/NetSerializer/Primitives.cs @@ -12,7 +12,9 @@ using System.Text; using System.Diagnostics; #if NETCOREAPP +using System.Runtime.CompilerServices; using System.Text.Unicode; +using System.Buffers.Binary; using System.Buffers; #endif @@ -219,26 +221,41 @@ public static void ReadPrimitive(Stream stream, out long value) public static unsafe void WritePrimitive(Stream stream, float value) { uint v = *(uint*)(&value); - WriteVarint32(stream, v); + WriteUInt32(stream, v); } public static unsafe void ReadPrimitive(Stream stream, out float value) { - uint v = ReadVarint32(stream); + uint v = ReadUInt32(stream); value = *(float*)(&v); } public static unsafe void WritePrimitive(Stream stream, double value) { ulong v = *(ulong*)(&value); - WriteVarint64(stream, v); + WriteUInt64(stream, v); } public static unsafe void ReadPrimitive(Stream stream, out double value) { - ulong v = ReadVarint64(stream); + ulong v = ReadUInt64(stream); value = *(double*)(&v); } + +#if NET5_0 + public static void WritePrimitive(Stream stream, Half value) + { + ushort v = Unsafe.As(ref value); + WriteUInt16(stream, v); + } + + public static void ReadPrimitive(Stream stream, out Half value) + { + var v = ReadUInt16(stream); + value = Unsafe.As(ref v); + } +#endif + #else public static void WritePrimitive(Stream stream, float value) { @@ -255,14 +272,159 @@ public static void ReadPrimitive(Stream stream, out float value) public static void WritePrimitive(Stream stream, double value) { ulong v = (ulong)BitConverter.DoubleToInt64Bits(value); - WriteVarint64(stream, v); + WriteUInt64(stream, v); } public static void ReadPrimitive(Stream stream, out double value) { - ulong v = ReadVarint64(stream); + ulong v = ReadUInt64(stream); value = BitConverter.Int64BitsToDouble((long)v); } + +#if NET5_0 + public static void WritePrimitive(Stream stream, Half value) + { + WritePrimitive(stream, (double)value); + } + + public static void ReadPrimitive(Stream stream, out Half value) + { + double v; + ReadPrimitive(stream, out v); + value = (Half)v; + } +#endif + +#endif + + private static void WriteUInt16(Stream stream, ushort value) + { + stream.WriteByte((byte) value); + stream.WriteByte((byte) (value >> 8)); + } + + private static ushort ReadUInt16(Stream stream) + { + ushort a = 0; + + for (var i = 0; i < 16; i += 8) + { + var val = stream.ReadByte(); + if (val == -1) + throw new EndOfStreamException(); + + a |= (ushort) (val << i); + } + + return a; + } + + // 32 and 64 bit variants use stackalloc when everything is available since it's faster. + +#if !NETCOREAPP + private static void WriteUInt32(Stream stream, uint value) + { + stream.WriteByte((byte) value); + stream.WriteByte((byte) (value >> 8)); + stream.WriteByte((byte) (value >> 16)); + stream.WriteByte((byte) (value >> 24)); + } + + private static void WriteUInt64(Stream stream, ulong value) + { + stream.WriteByte((byte) value); + stream.WriteByte((byte) (value >> 8)); + stream.WriteByte((byte) (value >> 16)); + stream.WriteByte((byte) (value >> 24)); + stream.WriteByte((byte) (value >> 32)); + stream.WriteByte((byte) (value >> 40)); + stream.WriteByte((byte) (value >> 48)); + stream.WriteByte((byte) (value >> 56)); + } + + private static uint ReadUInt32(Stream stream) + { + uint a = 0; + + for (var i = 0; i < 32; i += 8) + { + var val = stream.ReadByte(); + if (val < 0) + throw new EndOfStreamException(); + + a |= (uint)val << i; + } + + return a; + } + + private static ulong ReadUInt64(Stream stream) + { + ulong a = 0; + + for (var i = 0; i < 64; i += 8) + { + var val = stream.ReadByte(); + if (val < 0) + throw new EndOfStreamException(); + + a |= (ulong)val << i; + } + + return a; + } +#else + private static void WriteUInt32(Stream stream, uint value) + { + Span buf = stackalloc byte[4]; + BinaryPrimitives.WriteUInt32LittleEndian(buf, value); + + stream.Write(buf); + } + + private static void WriteUInt64(Stream stream, ulong value) + { + Span buf = stackalloc byte[8]; + BinaryPrimitives.WriteUInt64LittleEndian(buf, value); + + stream.Write(buf); + } + + private static uint ReadUInt32(Stream stream) + { + Span buf = stackalloc byte[4]; + var wSpan = buf; + + while (true) + { + var read = stream.Read(wSpan); + if (read == 0) + throw new EndOfStreamException(); + if (read == wSpan.Length) + break; + wSpan = wSpan[read..]; + } + + return BinaryPrimitives.ReadUInt32LittleEndian(buf); + } + + private static ulong ReadUInt64(Stream stream) + { + Span buf = stackalloc byte[8]; + var wSpan = buf; + + while (true) + { + var read = stream.Read(wSpan); + if (read == 0) + throw new EndOfStreamException(); + if (read == wSpan.Length) + break; + wSpan = wSpan[read..]; + } + + return BinaryPrimitives.ReadUInt64LittleEndian(buf); + } #endif public static void WritePrimitive(Stream stream, DateTime value) diff --git a/NetSerializer/TypeSerializers/PrimitivesSerializer.cs b/NetSerializer/TypeSerializers/PrimitivesSerializer.cs index 26c6918..aad5954 100644 --- a/NetSerializer/TypeSerializers/PrimitivesSerializer.cs +++ b/NetSerializer/TypeSerializers/PrimitivesSerializer.cs @@ -1,6 +1,6 @@ /* * Copyright 2015 Tomi Valkeinen - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -29,6 +29,9 @@ sealed class PrimitivesSerializer : IStaticTypeSerializer typeof(DateTime), typeof(byte[]), typeof(Decimal), +#if NET5_0 + typeof(Half), +#endif }; public bool Handles(Type type) diff --git a/PrimitiveTest/PrimitiveTest.csproj b/PrimitiveTest/PrimitiveTest.csproj index 144e502..9d9edae 100644 --- a/PrimitiveTest/PrimitiveTest.csproj +++ b/PrimitiveTest/PrimitiveTest.csproj @@ -1,6 +1,6 @@  - netcoreapp3.0;net45 + netcoreapp3.1;net45;net5.0 Exe diff --git a/Test/Test.csproj b/Test/Test.csproj index 418c1cb..ee49692 100644 --- a/Test/Test.csproj +++ b/Test/Test.csproj @@ -1,6 +1,6 @@  - netcoreapp3.0;net45 + netcoreapp3.1;net45;net5.0 Exe