Skip to content
This repository was archived by the owner on Aug 2, 2023. It is now read-only.
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace System.Buffers.Reader
{
public ref struct BufferReader
public ref partial struct BufferReader
{
private SequencePosition _currentPosition;
private SequencePosition _nextPosition;
Expand Down Expand Up @@ -190,40 +190,64 @@ private void AdvanceNextSegment(int byteCount)
}
}

internal static int Peek(in BufferReader buffer, Span<byte> destination)
/// <summary>
/// Peek forward the number of bytes specified by <paramref name="count"/>.
/// </summary>
/// <returns>Span over the peeked data.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<byte> Peek(int count)
{
ReadOnlySpan<byte> firstSpan = buffer.UnreadSegment;
if (firstSpan.Length > destination.Length)
ReadOnlySpan<byte> firstSpan = UnreadSegment;
if (firstSpan.Length >= count)
{
firstSpan.Slice(0, destination.Length).CopyTo(destination);
return destination.Length;
return firstSpan.Slice(0, count);
}
else if (firstSpan.Length == destination.Length)

return PeekSlow(new byte[count]);
}

/// <summary>
/// Peek forward the number of bytes in <paramref name="copyBuffer"/>, copying into
/// <paramref name="copyBuffer"/> if needed.
/// </summary>
/// <param name="copyBuffer">
/// Temporary buffer to copy into if there isn't a contiguous span within the existing data to return.
/// Also describes the count of bytes to peek.
/// </param>
/// <returns>Span over the peeked data.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<byte> Peek(Span<byte> copyBuffer)
{
ReadOnlySpan<byte> firstSpan = UnreadSegment;
if (firstSpan.Length >= copyBuffer.Length)
{
firstSpan.CopyTo(destination);
return destination.Length;
return firstSpan.Slice(0, copyBuffer.Length);
}
else
{
firstSpan.CopyTo(destination);
int copied = firstSpan.Length;

SequencePosition next = buffer._nextPosition;
while (buffer.Sequence.TryGet(ref next, out ReadOnlyMemory<byte> nextSegment, true))
return PeekSlow(copyBuffer);
}

private ReadOnlySpan<byte> PeekSlow(Span<byte> destination)
{
ReadOnlySpan<byte> firstSpan = UnreadSegment;
firstSpan.CopyTo(destination);
int copied = firstSpan.Length;

SequencePosition next = _nextPosition;
while (Sequence.TryGet(ref next, out ReadOnlyMemory<byte> nextSegment, true))
{
ReadOnlySpan<byte> nextSpan = nextSegment.Span;
if (nextSpan.Length > 0)
{
ReadOnlySpan<byte> nextSpan = nextSegment.Span;
if (nextSpan.Length > 0)
{
int toCopy = Math.Min(nextSpan.Length, destination.Length - copied);
nextSpan.Slice(0, toCopy).CopyTo(destination.Slice(copied));
copied += toCopy;
if (copied >= destination.Length)
break;
}
int toCopy = Math.Min(nextSpan.Length, destination.Length - copied);
nextSpan.Slice(0, toCopy).CopyTo(destination.Slice(copied));
copied += toCopy;
if (copied >= destination.Length)
break;
}

return copied;
}

return destination.Slice(0, copied);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,167 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Buffers.Binary;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace System.Buffers.Reader
{
public static partial class BufferReaderExtensions
public ref partial struct BufferReader
{
public static bool TryRead(ref BufferReader reader, out int value, bool littleEndian = false)
/// <summary>
/// Try to read the given type out of the buffer if possible.
/// </summary>
/// <remarks>
/// The read is unaligned.
/// </remarks>
/// <returns>
/// True if successful. <paramref name="value"/> will be default if failed.
/// </returns>
public unsafe bool TryRead<T>(out T value) where T : unmanaged
{
var unread = reader.UnreadSegment;
if (littleEndian)
ReadOnlySpan<byte> span = UnreadSegment;
if (span.Length < sizeof(T))
return TryReadSlow(out value);

value = MemoryMarshal.Read<T>(span);
Advance(sizeof(T));
return true;
}

private unsafe bool TryReadSlow<T>(out T value) where T : unmanaged
{
Debug.Assert(UnreadSegment.Length < sizeof(T));

// Not enough data in the current segment, try to peek for the data we need.
byte* buffer = stackalloc byte[sizeof(T)];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't this directly using stackalloc Span?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it won't let you pass it to Peek (for fear of being captured as the reader is a ref struct). Readonly methods should address some of this. I'm discussing with @jaredpar etc. about the possibilities of adding a feature that allows methods to take spans and specify that they won't stash the incoming span argument.

Span<byte> tempSpan = new Span<byte>(buffer, sizeof(T));

if (Peek(tempSpan).Length < sizeof(T))
{
if (BinaryPrimitives.TryReadInt32LittleEndian(unread, out value))
{
reader.Advance(sizeof(int));
return true;
}
value = default;
return false;
}
else if (BinaryPrimitives.TryReadInt32BigEndian(unread, out value))

value = MemoryMarshal.Read<T>(tempSpan);
Advance(sizeof(T));
return true;
}

/// <summary>
/// Reads an <see cref="Int16"/> as little endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="Int16"/>.</returns>
public bool TryReadInt16LittleEndian(out short value)
{
if (BitConverter.IsLittleEndian)
{
reader.Advance(sizeof(int));
return TryRead(out value);
}

return TryReadReverseEndianness(out value);
}

/// <summary>
/// Reads an <see cref="Int16"/> as big endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="Int16"/>.</returns>
public bool TryReadInt16BigEndian(out short value)
{
if (!BitConverter.IsLittleEndian)
{
return TryRead(out value);
}

return TryReadReverseEndianness(out value);
}

private bool TryReadReverseEndianness(out short value)
{
if (TryRead(out value))
{
value = BinaryPrimitives.ReverseEndianness(value);
return true;
}

Span<byte> tempSpan = stackalloc byte[4];
var copied = Peek(reader, tempSpan);
if (copied < 4)
return false;
}

/// <summary>
/// Reads an <see cref="Int32"/> as little endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="Int32"/>.</returns>
public bool TryReadInt32LittleEndian(out int value)
{
if (BitConverter.IsLittleEndian)
{
value = default;
return false;
return TryRead(out value);
}

if (littleEndian)
return TryReadReverseEndianness(out value);
}

/// <summary>
/// Reads an <see cref="Int32"/> as big endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="Int32"/>.</returns>
public bool TryReadInt32BigEndian(out int value)
{
if (!BitConverter.IsLittleEndian)
{
value = BinaryPrimitives.ReadInt32LittleEndian(tempSpan);
return TryRead(out value);
}
else

return TryReadReverseEndianness(out value);
}

private bool TryReadReverseEndianness(out int value)
{
if (TryRead(out value))
{
value = BinaryPrimitives.ReadInt32BigEndian(tempSpan);
value = BinaryPrimitives.ReverseEndianness(value);
return true;
}
reader.Advance(sizeof(int));
return true;

return false;
}
}

public static partial class BufferReaderExtensions
{
public static int Peek(BufferReader reader, Span<byte> destination)
=> BufferReader.Peek(reader, destination);
/// <summary>
/// Reads an <see cref="Int64"/> as little endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="Int64"/>.</returns>
public bool TryReadInt64LittleEndian(out long value)
{
if (BitConverter.IsLittleEndian)
{
return TryRead(out value);
}

return TryReadReverseEndianness(out value);
}

/// <summary>
/// Reads an <see cref="Int64"/> as big endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="Int64"/>.</returns>
public bool TryReadInt64BigEndian(out long value)
{
if (!BitConverter.IsLittleEndian)
{
return TryRead(out value);
}

return TryReadReverseEndianness(out value);
}

private bool TryReadReverseEndianness(out long value)
{
if (TryRead(out value))
{
value = BinaryPrimitives.ReverseEndianness(value);
return true;
}

return false;
}
}
}
Loading