9b1ef54dc9
Former-commit-id: ed8637df0061cb6f128ae1d26d26db7734e0fa0d
449 lines
14 KiB
C#
449 lines
14 KiB
C#
// 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 System.Diagnostics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
using Internal.Runtime.CompilerServices;
|
|
|
|
namespace System.Buffers
|
|
{
|
|
#if __MonoCS__
|
|
public ref partial struct SequenceReader<T> where T : IEquatable<T>
|
|
#else
|
|
public ref partial struct SequenceReader<T> where T : unmanaged, IEquatable<T>
|
|
#endif
|
|
{
|
|
private SequencePosition _currentPosition;
|
|
private SequencePosition _nextPosition;
|
|
private bool _moreData;
|
|
#if __MonoCS__
|
|
private long _length;
|
|
#else
|
|
private readonly long _length;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Create a <see cref="SequenceReader{T}"/> over the given <see cref="ReadOnlySequence{T}"/>.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public SequenceReader(ReadOnlySequence<T> sequence)
|
|
{
|
|
CurrentSpanIndex = 0;
|
|
Consumed = 0;
|
|
Sequence = sequence;
|
|
_currentPosition = sequence.Start;
|
|
_length = -1;
|
|
|
|
sequence.GetFirstSpan(out ReadOnlySpan<T> first, out _nextPosition);
|
|
CurrentSpan = first;
|
|
_moreData = first.Length > 0;
|
|
|
|
if (!_moreData && !sequence.IsSingleSegment)
|
|
{
|
|
_moreData = true;
|
|
GetNextSpan();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// True when there is no more data in the <see cref="Sequence"/>.
|
|
/// </summary>
|
|
#if __MonoCS__
|
|
public bool End => !_moreData;
|
|
#else
|
|
public readonly bool End => !_moreData;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// The underlying <see cref="ReadOnlySequence{T}"/> for the reader.
|
|
/// </summary>
|
|
#if __MonoCS__
|
|
public ReadOnlySequence<T> Sequence { get; }
|
|
#else
|
|
public readonly ReadOnlySequence<T> Sequence { get; }
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// The current position in the <see cref="Sequence"/>.
|
|
/// </summary>
|
|
#if __MonoCS__
|
|
public SequencePosition Position
|
|
=> Sequence.GetPosition(CurrentSpanIndex, _currentPosition);
|
|
#else
|
|
public readonly SequencePosition Position
|
|
=> Sequence.GetPosition(CurrentSpanIndex, _currentPosition);
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// The current segment in the <see cref="Sequence"/> as a span.
|
|
/// </summary>
|
|
#if __MonoCS__
|
|
public ReadOnlySpan<T> CurrentSpan { get; private set; }
|
|
#else
|
|
public ReadOnlySpan<T> CurrentSpan { readonly get; private set; }
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// The index in the <see cref="CurrentSpan"/>.
|
|
/// </summary>
|
|
#if __MonoCS__
|
|
public int CurrentSpanIndex { get; private set; }
|
|
#else
|
|
public int CurrentSpanIndex { readonly get; private set; }
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// The unread portion of the <see cref="CurrentSpan"/>.
|
|
/// </summary>
|
|
#if __MonoCS__
|
|
public ReadOnlySpan<T> UnreadSpan
|
|
#else
|
|
public readonly ReadOnlySpan<T> UnreadSpan
|
|
#endif
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get => CurrentSpan.Slice(CurrentSpanIndex);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The total number of <typeparamref name="T"/>'s processed by the reader.
|
|
/// </summary>
|
|
#if __MonoCS__
|
|
public long Consumed { get; private set; }
|
|
#else
|
|
public long Consumed { readonly get; private set; }
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Remaining <typeparamref name="T"/>'s in the reader's <see cref="Sequence"/>.
|
|
/// </summary>
|
|
#if __MonoCS__
|
|
public long Remaining => Length - Consumed;
|
|
#else
|
|
public readonly long Remaining => Length - Consumed;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Count of <typeparamref name="T"/> in the reader's <see cref="Sequence"/>.
|
|
/// </summary>
|
|
#if __MonoCS__
|
|
public long Length
|
|
#else
|
|
public readonly long Length
|
|
#endif
|
|
{
|
|
get
|
|
{
|
|
if (_length < 0)
|
|
{
|
|
unsafe {
|
|
fixed (long* lenPtr = &_length)
|
|
// Cast-away readonly to initialize lazy field
|
|
Volatile.Write(ref Unsafe.AsRef<long>(lenPtr), Sequence.Length);
|
|
}
|
|
}
|
|
return _length;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Peeks at the next value without advancing the reader.
|
|
/// </summary>
|
|
/// <param name="value">The next value or default if at the end.</param>
|
|
/// <returns>False if at the end of the reader.</returns>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
#if __MonoCS__
|
|
public bool TryPeek(out T value)
|
|
#else
|
|
public readonly bool TryPeek(out T value)
|
|
#endif
|
|
{
|
|
if (_moreData)
|
|
{
|
|
value = CurrentSpan[CurrentSpanIndex];
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
value = default;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the next value and advance the reader.
|
|
/// </summary>
|
|
/// <param name="value">The next value or default if at the end.</param>
|
|
/// <returns>False if at the end of the reader.</returns>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public bool TryRead(out T value)
|
|
{
|
|
if (End)
|
|
{
|
|
value = default;
|
|
return false;
|
|
}
|
|
|
|
value = CurrentSpan[CurrentSpanIndex];
|
|
CurrentSpanIndex++;
|
|
Consumed++;
|
|
|
|
if (CurrentSpanIndex >= CurrentSpan.Length)
|
|
{
|
|
GetNextSpan();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the reader back the specified number of items.
|
|
/// </summary>
|
|
/// <exception cref="ArgumentOutOfRangeException">
|
|
/// Thrown if trying to rewind a negative amount or more than <see cref="Consumed"/>.
|
|
/// </exception>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Rewind(long count)
|
|
{
|
|
if ((ulong)count > (ulong)Consumed)
|
|
{
|
|
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count);
|
|
}
|
|
|
|
Consumed -= count;
|
|
|
|
if (CurrentSpanIndex >= count)
|
|
{
|
|
CurrentSpanIndex -= (int)count;
|
|
_moreData = true;
|
|
}
|
|
else
|
|
{
|
|
// Current segment doesn't have enough data, scan backward through segments
|
|
RetreatToPreviousSpan(Consumed);
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private void RetreatToPreviousSpan(long consumed)
|
|
{
|
|
ResetReader();
|
|
Advance(consumed);
|
|
}
|
|
|
|
private void ResetReader()
|
|
{
|
|
CurrentSpanIndex = 0;
|
|
Consumed = 0;
|
|
_currentPosition = Sequence.Start;
|
|
_nextPosition = _currentPosition;
|
|
|
|
if (Sequence.TryGet(ref _nextPosition, out ReadOnlyMemory<T> memory, advance: true))
|
|
{
|
|
_moreData = true;
|
|
|
|
if (memory.Length == 0)
|
|
{
|
|
CurrentSpan = default;
|
|
// No data in the first span, move to one with data
|
|
GetNextSpan();
|
|
}
|
|
else
|
|
{
|
|
CurrentSpan = memory.Span;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No data in any spans and at end of sequence
|
|
_moreData = false;
|
|
CurrentSpan = default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the next segment with available data, if any.
|
|
/// </summary>
|
|
private void GetNextSpan()
|
|
{
|
|
if (!Sequence.IsSingleSegment)
|
|
{
|
|
SequencePosition previousNextPosition = _nextPosition;
|
|
while (Sequence.TryGet(ref _nextPosition, out ReadOnlyMemory<T> memory, advance: true))
|
|
{
|
|
_currentPosition = previousNextPosition;
|
|
if (memory.Length > 0)
|
|
{
|
|
CurrentSpan = memory.Span;
|
|
CurrentSpanIndex = 0;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
CurrentSpan = default;
|
|
CurrentSpanIndex = 0;
|
|
previousNextPosition = _nextPosition;
|
|
}
|
|
}
|
|
}
|
|
_moreData = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the reader ahead the specified number of items.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Advance(long count)
|
|
{
|
|
const long TooBigOrNegative = unchecked((long)0xFFFFFFFF80000000);
|
|
if ((count & TooBigOrNegative) == 0 && CurrentSpan.Length - CurrentSpanIndex > (int)count)
|
|
{
|
|
CurrentSpanIndex += (int)count;
|
|
Consumed += count;
|
|
}
|
|
else
|
|
{
|
|
// Can't satisfy from the current span
|
|
AdvanceToNextSpan(count);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unchecked helper to avoid unnecessary checks where you know count is valid.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal void AdvanceCurrentSpan(long count)
|
|
{
|
|
Debug.Assert(count >= 0);
|
|
|
|
Consumed += count;
|
|
CurrentSpanIndex += (int)count;
|
|
if (CurrentSpanIndex >= CurrentSpan.Length)
|
|
GetNextSpan();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Only call this helper if you know that you are advancing in the current span
|
|
/// with valid count and there is no need to fetch the next one.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal void AdvanceWithinSpan(long count)
|
|
{
|
|
Debug.Assert(count >= 0);
|
|
|
|
Consumed += count;
|
|
CurrentSpanIndex += (int)count;
|
|
|
|
Debug.Assert(CurrentSpanIndex < CurrentSpan.Length);
|
|
}
|
|
|
|
private void AdvanceToNextSpan(long count)
|
|
{
|
|
if (count < 0)
|
|
{
|
|
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count);
|
|
}
|
|
|
|
Consumed += count;
|
|
while (_moreData)
|
|
{
|
|
int remaining = CurrentSpan.Length - CurrentSpanIndex;
|
|
|
|
if (remaining > count)
|
|
{
|
|
CurrentSpanIndex += (int)count;
|
|
count = 0;
|
|
break;
|
|
}
|
|
|
|
// As there may not be any further segments we need to
|
|
// push the current index to the end of the span.
|
|
CurrentSpanIndex += remaining;
|
|
count -= remaining;
|
|
Debug.Assert(count >= 0);
|
|
|
|
GetNextSpan();
|
|
|
|
if (count == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (count != 0)
|
|
{
|
|
// Not enough data left- adjust for where we actually ended and throw
|
|
Consumed -= count;
|
|
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies data from the current <see cref="Position"/> to the given <paramref name="destination"/> span if there
|
|
/// is enough data to fill it.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This API is used to copy a fixed amount of data out of the sequence if possible. It does not advance
|
|
/// the reader. To look ahead for a specific stream of data <see cref="IsNext(ReadOnlySpan{T}, bool)"/> can be used.
|
|
/// </remarks>
|
|
/// <param name="destination">Destination span to copy to.</param>
|
|
/// <returns>True if there is enough data to completely fill the <paramref name="destination"/> span.</returns>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
#if __MonoCS__
|
|
public bool TryCopyTo(Span<T> destination)
|
|
#else
|
|
public readonly bool TryCopyTo(Span<T> destination)
|
|
#endif
|
|
{
|
|
// This API doesn't advance to facilitate conditional advancement based on the data returned.
|
|
// We don't provide an advance option to allow easier utilizing of stack allocated destination spans.
|
|
// (Because we can make this method readonly we can guarantee that we won't capture the span.)
|
|
|
|
ReadOnlySpan<T> firstSpan = UnreadSpan;
|
|
if (firstSpan.Length >= destination.Length)
|
|
{
|
|
firstSpan.Slice(0, destination.Length).CopyTo(destination);
|
|
return true;
|
|
}
|
|
|
|
// Not enough in the current span to satisfy the request, fall through to the slow path
|
|
return TryCopyMultisegment(destination);
|
|
}
|
|
|
|
#if __MonoCS__
|
|
internal bool TryCopyMultisegment(Span<T> destination)
|
|
#else
|
|
internal readonly bool TryCopyMultisegment(Span<T> destination)
|
|
#endif
|
|
{
|
|
// If we don't have enough to fill the requested buffer, return false
|
|
if (Remaining < destination.Length)
|
|
return false;
|
|
|
|
ReadOnlySpan<T> firstSpan = UnreadSpan;
|
|
Debug.Assert(firstSpan.Length < destination.Length);
|
|
firstSpan.CopyTo(destination);
|
|
int copied = firstSpan.Length;
|
|
|
|
SequencePosition next = _nextPosition;
|
|
while (Sequence.TryGet(ref next, out ReadOnlyMemory<T> nextSegment, true))
|
|
{
|
|
if (nextSegment.Length > 0)
|
|
{
|
|
ReadOnlySpan<T> nextSpan = nextSegment.Span;
|
|
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 true;
|
|
}
|
|
}
|
|
} |