diff --git a/src/PbfLite.Benchmark/PbfBlockWriterCollectionsBenchmarks.cs b/src/PbfLite.Benchmark/PbfBlockWriterCollectionsBenchmarks.cs index c2e288f..6e974b7 100644 --- a/src/PbfLite.Benchmark/PbfBlockWriterCollectionsBenchmarks.cs +++ b/src/PbfLite.Benchmark/PbfBlockWriterCollectionsBenchmarks.cs @@ -1,4 +1,5 @@ using BenchmarkDotNet.Attributes; +using System; using System.Linq; namespace PbfLite.Benchmark; @@ -13,7 +14,7 @@ public class PbfBlockWriterCollectionsBenchmarks public int WriteSmallSingleCollection() { var writer = PbfBlockWriter.Create(buffer); - writer.WriteSingleCollection(SingleValuesSmall); + writer.WriteSingleCollection(SingleValuesSmall.AsSpan()); return writer.Position; } @@ -21,7 +22,7 @@ public int WriteSmallSingleCollection() public int WriteLargeSingleCollection() { var writer = PbfBlockWriter.Create(buffer); - writer.WriteSingleCollection(SingleValuesLarge); + writer.WriteSingleCollection(SingleValuesLarge.AsSpan()); return writer.Position; } } \ No newline at end of file diff --git a/src/PbfLite.Tests/CollectionsRoundTripTests.cs b/src/PbfLite.Tests/CollectionsRoundTripTests.cs index 33b8c54..e46380f 100644 --- a/src/PbfLite.Tests/CollectionsRoundTripTests.cs +++ b/src/PbfLite.Tests/CollectionsRoundTripTests.cs @@ -13,7 +13,7 @@ public void UIntCollection_RoundTrip_SingleElement() // Write var writer = PbfBlockWriter.Create(buffer); - writer.WriteUIntCollection(originalData); + writer.WriteUIntCollection(originalData.AsSpan()); // Read var reader = PbfBlockReader.Create(writer.Block); diff --git a/src/PbfLite.Tests/PbfBlockReaderStreamReaderRoundTripTests.cs b/src/PbfLite.Tests/PbfBlockReaderStreamReaderRoundTripTests.cs index f2e63ad..4e5ce6a 100644 --- a/src/PbfLite.Tests/PbfBlockReaderStreamReaderRoundTripTests.cs +++ b/src/PbfLite.Tests/PbfBlockReaderStreamReaderRoundTripTests.cs @@ -124,7 +124,7 @@ public void StreamReader_WithCollections_MatchesBlockReader() // Write a collection uint[] testData = { 10, 20, 30, 40, 50 }; - writer.WriteUIntCollection(testData); + writer.WriteUIntCollection(testData.AsSpan()); // Read with BlockReader var blockReader = PbfBlockReader.Create(writer.Block); diff --git a/src/PbfLite.Tests/PbfBlockWriterTests.Collections.cs b/src/PbfLite.Tests/PbfBlockWriterTests.Collections.cs index 60de5c4..a0229f9 100644 --- a/src/PbfLite.Tests/PbfBlockWriterTests.Collections.cs +++ b/src/PbfLite.Tests/PbfBlockWriterTests.Collections.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Xunit; @@ -18,7 +19,7 @@ public void WriteUIntCollection_WritesLongCollection() var expectedBytes = encodedLength.Concat(expectedContent).ToArray(); var writer = PbfBlockWriter.Create(new byte[expectedBytes.Length]); - writer.WriteUIntCollection(items); + writer.WriteUIntCollection(items.AsSpan()); SpanAssert.Equal(expectedBytes, writer.Block); } @@ -34,6 +35,17 @@ public void WriteUIntCollection_MultipleElements_WritesDataWithLengthPrefix(uint SpanAssert.Equal(expectedBytes, writer.Block); } + [Theory] + [InlineData(new uint[] { 0, 128, 16384, 2097152, 268435456, 4294967295 }, new byte[] { 0x14, 0x00, 0x80, 0x01, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x80, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F })] + public void WriteUIntCollection_WithIEnumerable_WritesDataWithLengthPrefix(uint[] items, byte[] expectedBytes) + { + var writer = PbfBlockWriter.Create(new byte[expectedBytes.Length]); + + writer.WriteUIntCollection((IEnumerable)items); + + SpanAssert.Equal(expectedBytes, writer.Block); + } + [Theory] [InlineData(new ulong[] { 0, 1, 18446744073709551615UL }, new byte[] { 0x0C, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 })] public void WriteULongCollection_MultipleElements_WritesDataWithLengthPrefix(ulong[] items, byte[] expectedBytes) @@ -45,6 +57,17 @@ public void WriteULongCollection_MultipleElements_WritesDataWithLengthPrefix(ulo SpanAssert.Equal(expectedBytes, writer.Block); } + [Theory] + [InlineData(new ulong[] { 0, 1, 18446744073709551615UL }, new byte[] { 0x0C, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 })] + public void WriteULongCollection_WithIEnumerable_WritesDataWithLengthPrefix(ulong[] items, byte[] expectedBytes) + { + var writer = PbfBlockWriter.Create(new byte[expectedBytes.Length]); + + writer.WriteULongCollection((IEnumerable)items); + + SpanAssert.Equal(expectedBytes, writer.Block); + } + [Theory] [InlineData(new int[] { 0, 1, -1, 2147483647, -2147483648 }, new byte[] { 0x11, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x80, 0x80, 0x80, 0x08 })] public void WriteIntCollection_MultipleElements_WritesDataWithLengthPrefix(int[] items, byte[] expectedBytes) @@ -56,6 +79,17 @@ public void WriteIntCollection_MultipleElements_WritesDataWithLengthPrefix(int[] SpanAssert.Equal(expectedBytes, writer.Block); } + [Theory] + [InlineData(new int[] { 0, 1, -1, 2147483647, -2147483648 }, new byte[] { 0x11, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x80, 0x80, 0x80, 0x08 })] + public void WriteIntCollection_WithIEnumerable_WritesDataWithLengthPrefix(int[] items, byte[] expectedBytes) + { + var writer = PbfBlockWriter.Create(new byte[expectedBytes.Length]); + + writer.WriteIntCollection((IEnumerable)items); + + SpanAssert.Equal(expectedBytes, writer.Block); + } + [Theory] [InlineData(new int[] { 0, -1, 1, 2147483647, -2147483648 }, new byte[] { 0x0D, 0x00, 0x01, 0x02, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F })] public void WriteSignedIntCollection_MultipleElements_WritesDataWithLengthPrefix(int[] items, byte[] expectedBytes) @@ -67,6 +101,17 @@ public void WriteSignedIntCollection_MultipleElements_WritesDataWithLengthPrefix SpanAssert.Equal(expectedBytes, writer.Block); } + [Theory] + [InlineData(new int[] { 0, -1, 1, 2147483647, -2147483648 }, new byte[] { 0x0D, 0x00, 0x01, 0x02, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F })] + public void WriteSignedIntCollection_WithIEnumerable_WritesDataWithLengthPrefix(int[] items, byte[] expectedBytes) + { + var writer = PbfBlockWriter.Create(new byte[expectedBytes.Length]); + + writer.WriteSignedIntCollection((IEnumerable)items); + + SpanAssert.Equal(expectedBytes, writer.Block); + } + [Theory] [InlineData(new long[] { 0, 1, -1, 9223372036854775807, -9223372036854775808 }, new byte[] { 0x1F, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 })] public void WriteLongCollection_MultipleElements_WritesDataWithLengthPrefix(long[] items, byte[] expectedBytes) @@ -78,6 +123,17 @@ public void WriteLongCollection_MultipleElements_WritesDataWithLengthPrefix(long SpanAssert.Equal(expectedBytes, writer.Block); } + [Theory] + [InlineData(new long[] { 0, 1, -1, 9223372036854775807, -9223372036854775808 }, new byte[] { 0x1F, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 })] + public void WriteLongCollection_WithIEnumerable_WritesDataWithLengthPrefix(long[] items, byte[] expectedBytes) + { + var writer = PbfBlockWriter.Create(new byte[expectedBytes.Length]); + + writer.WriteLongCollection((IEnumerable)items); + + SpanAssert.Equal(expectedBytes, writer.Block); + } + [Theory] [InlineData(new long[] { 0, -1, 1, 9223372036854775807, -9223372036854775808 }, new byte[] { 0x17, 0x00, 0x01, 0x02, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 })] public void WriteSignedLongCollection_MultipleElements_WritesDataWithLengthPrefix(long[] items, byte[] expectedBytes) @@ -89,6 +145,17 @@ public void WriteSignedLongCollection_MultipleElements_WritesDataWithLengthPrefi SpanAssert.Equal(expectedBytes, writer.Block); } + [Theory] + [InlineData(new long[] { 0, -1, 1, 9223372036854775807, -9223372036854775808 }, new byte[] { 0x17, 0x00, 0x01, 0x02, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 })] + public void WriteSignedLongCollection_WithIEnumerable_WritesDataWithLengthPrefix(long[] items, byte[] expectedBytes) + { + var writer = PbfBlockWriter.Create(new byte[expectedBytes.Length]); + + writer.WriteSignedLongCollection((IEnumerable)items); + + SpanAssert.Equal(expectedBytes, writer.Block); + } + [Theory] [InlineData(new bool[] { false, true }, new byte[] { 0x02, 0x00, 0x01 })] public void WriteBooleanCollection_MultipleElements_WritesDataWithLengthPrefix(bool[] items, byte[] expectedBytes) @@ -100,6 +167,17 @@ public void WriteBooleanCollection_MultipleElements_WritesDataWithLengthPrefix(b SpanAssert.Equal(expectedBytes, writer.Block); } + [Theory] + [InlineData(new bool[] { false, true }, new byte[] { 0x02, 0x00, 0x01 })] + public void WriteBooleanCollection_WithIEnumerable_WritesDataWithLengthPrefix(bool[] items, byte[] expectedBytes) + { + var writer = PbfBlockWriter.Create(new byte[expectedBytes.Length]); + + writer.WriteBooleanCollection((IEnumerable)items); + + SpanAssert.Equal(expectedBytes, writer.Block); + } + [Theory] [InlineData(new float[] { 0, 3.14f, 0.00014f, 12000.1f }, new byte[] { 0x10, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xF5, 0x48, 0x40, 0xF7, 0xCC, 0x12, 0x39, 0x66, 0x80, 0x3B, 0x46 })] public void WriteSingleCollection_MultipleElements_WritesDataWithLengthPrefix(float[] items, byte[] expectedBytes) @@ -111,6 +189,17 @@ public void WriteSingleCollection_MultipleElements_WritesDataWithLengthPrefix(fl SpanAssert.Equal(expectedBytes, writer.Block); } + [Theory] + [InlineData(new float[] { 0, 3.14f, 0.00014f, 12000.1f }, new byte[] { 0x10, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xF5, 0x48, 0x40, 0xF7, 0xCC, 0x12, 0x39, 0x66, 0x80, 0x3B, 0x46 })] + public void WriteSingleCollection_WithIEnumerable_WritesDataWithLengthPrefix(float[] items, byte[] expectedBytes) + { + var writer = PbfBlockWriter.Create(new byte[expectedBytes.Length]); + + writer.WriteSingleCollection((IEnumerable)items); + + SpanAssert.Equal(expectedBytes, writer.Block); + } + [Theory] [InlineData(new double[] { 0, 3.14, 0.00014, 12000.1 }, new byte[] { 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40, 0xD2, 0xFB, 0xC6, 0xD7, 0x9E, 0x59, 0x22, 0x3F, 0xCD, 0xCC, 0xCC, 0xCC, 0x0C, 0x70, 0xC7, 0x40 })] public void WriteDoubleCollection_MultipleElements_WritesDataWithLengthPrefix(double[] items, byte[] expectedBytes) @@ -121,5 +210,16 @@ public void WriteDoubleCollection_MultipleElements_WritesDataWithLengthPrefix(do SpanAssert.Equal(expectedBytes, writer.Block); } + + [Theory] + [InlineData(new double[] { 0, 3.14, 0.00014, 12000.1 }, new byte[] { 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40, 0xD2, 0xFB, 0xC6, 0xD7, 0x9E, 0x59, 0x22, 0x3F, 0xCD, 0xCC, 0xCC, 0xCC, 0x0C, 0x70, 0xC7, 0x40 })] + public void WriteDoubleCollection_WithIEnumerable_WritesDataWithLengthPrefix(double[] items, byte[] expectedBytes) + { + var writer = PbfBlockWriter.Create(new byte[expectedBytes.Length]); + + writer.WriteDoubleCollection((IEnumerable)items); + + SpanAssert.Equal(expectedBytes, writer.Block); + } } } \ No newline at end of file diff --git a/src/PbfLite/PbfBlockWriter.Collections.cs b/src/PbfLite/PbfBlockWriter.Collections.cs index 6bb0bf8..db86d23 100644 --- a/src/PbfLite/PbfBlockWriter.Collections.cs +++ b/src/PbfLite/PbfBlockWriter.Collections.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; namespace PbfLite; @@ -28,6 +30,42 @@ private void WriteScalarCollection(ReadOnlySpan items, ItemWriterDelegate< } } + private void WriteScalarCollection(IEnumerable items, ItemWriterDelegate itemWriter) + { + var estimatedLengthBytes = 1; + if (items.TryGetNonEnumeratedCount(out var itemsCount)) + { + estimatedLengthBytes = itemsCount; + } + + var block = StartLengthPrefixedBlock(estimatedLengthBytes); + + foreach (var item in items) + { + itemWriter(ref this, item); + } + + FinalizeLengthPrefixedBlock(block); + } + + private void WriteScalarCollection(IEnumerable items, ItemWriterDelegate itemWriter, Func contentLengthCalculator) + { + var estimatedLengthBytes = contentLengthCalculator(1); + if (items.TryGetNonEnumeratedCount(out var itemsCount)) + { + estimatedLengthBytes = contentLengthCalculator(itemsCount); + } + + var block = StartLengthPrefixedBlock(estimatedLengthBytes); + + foreach (var item in items) + { + itemWriter(ref this, item); + } + + FinalizeLengthPrefixedBlock(block); + } + private void WriteVarInt32At(int position, uint value) { var originalPosition = _position; @@ -55,67 +93,156 @@ public void WriteUIntCollection(ReadOnlySpan items) WriteScalarCollection(items, WriteUintDelegate); } + /// + /// Writes a collection of unsigned 32-bit integers as a length-prefixed packed field. + /// + /// The items to write. + public void WriteUIntCollection(IEnumerable items) + { + WriteScalarCollection(items, WriteUintDelegate); + } + /// /// Writes a collection of unsigned 64-bit integers as a length-prefixed packed field. /// + /// The items to write. public void WriteULongCollection(ReadOnlySpan items) { WriteScalarCollection(items, WriteULongDelegate); } + /// + /// Writes a collection of unsigned 64-bit integers as a length-prefixed packed field. + /// + /// The items to write. + public void WriteULongCollection(IEnumerable items) + { + WriteScalarCollection(items, WriteULongDelegate); + } + /// /// Writes a collection of 32-bit integers as a length-prefixed packed field. /// + /// The items to write. public void WriteIntCollection(ReadOnlySpan items) { WriteScalarCollection(items, WriteIntDelegate); } + /// + /// Writes a collection of 32-bit integers as a length-prefixed packed field. + /// + /// The items to write. + public void WriteIntCollection(IEnumerable items) + { + WriteScalarCollection(items, WriteIntDelegate); + } + /// /// Writes a collection of 64-bit integers as a length-prefixed packed field. /// + /// The items to write. public void WriteLongCollection(ReadOnlySpan items) { WriteScalarCollection(items, WriteLongDelegate); } + /// + /// Writes a collection of 64-bit integers as a length-prefixed packed field. + /// + /// The items to write. + public void WriteLongCollection(IEnumerable items) + { + WriteScalarCollection(items, WriteLongDelegate); + } + /// /// Writes a collection of zigzag-encoded signed 32-bit integers as a packed field. /// + /// The items to write. public void WriteSignedIntCollection(ReadOnlySpan items) { WriteScalarCollection(items, WriteSignedIntDelegate); } + /// + /// Writes a collection of zigzag-encoded signed 32-bit integers as a packed field. + /// + /// The items to write. + public void WriteSignedIntCollection(IEnumerable items) + { + WriteScalarCollection(items, WriteSignedIntDelegate); + } + /// /// Writes a collection of zigzag-encoded signed 64-bit integers as a packed field. /// + /// The items to write. public void WriteSignedLongCollection(ReadOnlySpan items) { WriteScalarCollection(items, WriteSignedLongDelegate); } + /// + /// Writes a collection of zigzag-encoded signed 64-bit integers as a packed field. + /// + /// The items to write. + public void WriteSignedLongCollection(IEnumerable items) + { + WriteScalarCollection(items, WriteSignedLongDelegate); + } + /// /// Writes a collection of boolean values as a length-prefixed packed field. /// + /// The items to write. public void WriteBooleanCollection(ReadOnlySpan items) { WriteScalarCollection(items, WriteBooleanDelegate, items.Length); } + /// + /// Writes a collection of boolean values as a length-prefixed packed field. + /// + /// The items to write. + public void WriteBooleanCollection(IEnumerable items) + { + WriteScalarCollection(items, WriteBooleanDelegate, count => count); + } + /// /// Writes a collection of 32-bit floats as a length-prefixed packed field. /// + /// The items to write. public void WriteSingleCollection(ReadOnlySpan items) { WriteScalarCollection(items, WriteSingleDelegate, items.Length * 4); } + /// + /// Writes a collection of 32-bit floats as a length-prefixed packed field. + /// + /// The items to write. + public void WriteSingleCollection(IEnumerable items) + { + WriteScalarCollection(items, WriteSingleDelegate, count => count * 4); + } + /// /// Writes a collection of 64-bit floats as a length-prefixed packed field. /// + /// The items to write. public void WriteDoubleCollection(ReadOnlySpan items) { WriteScalarCollection(items, WriteDoubleDelegate, items.Length * 8); } + + /// + /// Writes a collection of 64-bit floats as a length-prefixed packed field. + /// + /// The items to write. + public void WriteDoubleCollection(IEnumerable items) + { + WriteScalarCollection(items, WriteDoubleDelegate, count => count * 8); + } } \ No newline at end of file diff --git a/src/PbfLite/PbfLite.csproj b/src/PbfLite/PbfLite.csproj index d709a5d..d960651 100644 --- a/src/PbfLite/PbfLite.csproj +++ b/src/PbfLite/PbfLite.csproj @@ -5,7 +5,7 @@ PbfLite - 0.4.0 + 0.5.0 Lukas Kabrt PbfLITE is a low-level .NET Protocol Buffers implementation. It is intended for scenarios where you need fine-grained control over serialization and deserialization without the overhead of reflection, but it requires developers to manually implement the serialization / deserialization logic. MIT