498 lines
17 KiB
C#
498 lines
17 KiB
C#
// Copyright (c) All contributors. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
|
|
/* 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;
|
|
using System.Buffers;
|
|
using System.Diagnostics;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace MessagePack
|
|
{
|
|
internal ref partial struct SequenceReader<T>
|
|
where T : unmanaged, IEquatable<T>
|
|
{
|
|
/// <summary>
|
|
/// A value indicating whether we're using <see cref="sequence"/> (as opposed to <see cref="memory"/>.
|
|
/// </summary>
|
|
private bool usingSequence;
|
|
|
|
/// <summary>
|
|
/// Backing for the entire sequence when we're not using <see cref="memory"/>.
|
|
/// </summary>
|
|
private ReadOnlySequence<T> sequence;
|
|
|
|
/// <summary>
|
|
/// The position at the start of the <see cref="CurrentSpan"/>.
|
|
/// </summary>
|
|
private SequencePosition currentPosition;
|
|
|
|
/// <summary>
|
|
/// The position at the end of the <see cref="CurrentSpan"/>.
|
|
/// </summary>
|
|
private SequencePosition nextPosition;
|
|
|
|
/// <summary>
|
|
/// Backing for the entire sequence when we're not using <see cref="sequence"/>.
|
|
/// </summary>
|
|
private ReadOnlyMemory<T> memory;
|
|
|
|
/// <summary>
|
|
/// A value indicating whether there is unread data remaining.
|
|
/// </summary>
|
|
private bool moreData;
|
|
|
|
/// <summary>
|
|
/// The total number of elements in the sequence.
|
|
/// </summary>
|
|
private long length;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SequenceReader{T}"/> struct
|
|
/// over the given <see cref="ReadOnlySequence{T}"/>.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public SequenceReader(in ReadOnlySequence<T> sequence)
|
|
{
|
|
this.usingSequence = true;
|
|
this.CurrentSpanIndex = 0;
|
|
this.Consumed = 0;
|
|
this.sequence = sequence;
|
|
this.memory = default;
|
|
this.currentPosition = sequence.Start;
|
|
this.length = -1;
|
|
|
|
ReadOnlySpan<T> first = sequence.First.Span;
|
|
this.nextPosition = sequence.GetPosition(first.Length);
|
|
this.CurrentSpan = first;
|
|
this.moreData = first.Length > 0;
|
|
|
|
if (!this.moreData && !sequence.IsSingleSegment)
|
|
{
|
|
this.moreData = true;
|
|
this.GetNextSpan();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SequenceReader{T}"/> struct
|
|
/// over the given <see cref="ReadOnlyMemory{T}"/>.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public SequenceReader(ReadOnlyMemory<T> memory)
|
|
{
|
|
this.usingSequence = false;
|
|
this.CurrentSpanIndex = 0;
|
|
this.Consumed = 0;
|
|
this.memory = memory;
|
|
this.CurrentSpan = memory.Span;
|
|
this.length = memory.Length;
|
|
this.moreData = memory.Length > 0;
|
|
|
|
this.currentPosition = default;
|
|
this.nextPosition = default;
|
|
this.sequence = default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether there is no more data in the <see cref="Sequence"/>.
|
|
/// </summary>
|
|
public bool End => !this.moreData;
|
|
|
|
/// <summary>
|
|
/// Gets the underlying <see cref="ReadOnlySequence{T}"/> for the reader.
|
|
/// </summary>
|
|
public ReadOnlySequence<T> Sequence
|
|
{
|
|
get
|
|
{
|
|
if (this.sequence.IsEmpty && !this.memory.IsEmpty)
|
|
{
|
|
// We're in memory mode (instead of sequence mode).
|
|
// Lazily fill in the sequence data.
|
|
this.sequence = new ReadOnlySequence<T>(this.memory);
|
|
this.currentPosition = this.sequence.Start;
|
|
this.nextPosition = this.sequence.End;
|
|
}
|
|
|
|
return this.sequence;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current position in the <see cref="Sequence"/>.
|
|
/// </summary>
|
|
public SequencePosition Position
|
|
=> this.Sequence.GetPosition(this.CurrentSpanIndex, this.currentPosition);
|
|
|
|
/// <summary>
|
|
/// Gets the current segment in the <see cref="Sequence"/> as a span.
|
|
/// </summary>
|
|
public ReadOnlySpan<T> CurrentSpan { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the index in the <see cref="CurrentSpan"/>.
|
|
/// </summary>
|
|
public int CurrentSpanIndex { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the unread portion of the <see cref="CurrentSpan"/>.
|
|
/// </summary>
|
|
public ReadOnlySpan<T> UnreadSpan
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get => this.CurrentSpan.Slice(this.CurrentSpanIndex);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the total number of <typeparamref name="T"/>'s processed by the reader.
|
|
/// </summary>
|
|
public long Consumed { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets remaining <typeparamref name="T"/>'s in the reader's <see cref="Sequence"/>.
|
|
/// </summary>
|
|
public long Remaining => this.Length - this.Consumed;
|
|
|
|
/// <summary>
|
|
/// Gets count of <typeparamref name="T"/> in the reader's <see cref="Sequence"/>.
|
|
/// </summary>
|
|
public long Length
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get
|
|
{
|
|
if (this.length < 0)
|
|
{
|
|
// Cache the length
|
|
this.length = this.Sequence.Length;
|
|
}
|
|
|
|
return this.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)]
|
|
public bool TryPeek(out T value)
|
|
{
|
|
if (this.moreData)
|
|
{
|
|
value = this.CurrentSpan[this.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 (this.End)
|
|
{
|
|
value = default;
|
|
return false;
|
|
}
|
|
|
|
value = this.CurrentSpan[this.CurrentSpanIndex];
|
|
this.CurrentSpanIndex++;
|
|
this.Consumed++;
|
|
|
|
if (this.CurrentSpanIndex >= this.CurrentSpan.Length)
|
|
{
|
|
if (this.usingSequence)
|
|
{
|
|
this.GetNextSpan();
|
|
}
|
|
else
|
|
{
|
|
this.moreData = false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the reader back the specified number of items.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Rewind(long count)
|
|
{
|
|
if (count < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(count));
|
|
}
|
|
|
|
this.Consumed -= count;
|
|
|
|
if (this.CurrentSpanIndex >= count)
|
|
{
|
|
this.CurrentSpanIndex -= (int)count;
|
|
this.moreData = true;
|
|
}
|
|
else if (this.usingSequence)
|
|
{
|
|
// Current segment doesn't have enough data, scan backward through segments
|
|
this.RetreatToPreviousSpan(this.Consumed);
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentOutOfRangeException("Rewind went past the start of the memory.");
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private void RetreatToPreviousSpan(long consumed)
|
|
{
|
|
Debug.Assert(this.usingSequence, "usingSequence");
|
|
this.ResetReader();
|
|
this.Advance(consumed);
|
|
}
|
|
|
|
private void ResetReader()
|
|
{
|
|
Debug.Assert(this.usingSequence, "usingSequence");
|
|
this.CurrentSpanIndex = 0;
|
|
this.Consumed = 0;
|
|
this.currentPosition = this.Sequence.Start;
|
|
this.nextPosition = this.currentPosition;
|
|
|
|
if (this.Sequence.TryGet(ref this.nextPosition, out ReadOnlyMemory<T> memory, advance: true))
|
|
{
|
|
this.moreData = true;
|
|
|
|
if (memory.Length == 0)
|
|
{
|
|
this.CurrentSpan = default;
|
|
|
|
// No data in the first span, move to one with data
|
|
this.GetNextSpan();
|
|
}
|
|
else
|
|
{
|
|
this.CurrentSpan = memory.Span;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No data in any spans and at end of sequence
|
|
this.moreData = false;
|
|
this.CurrentSpan = default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the next segment with available data, if any.
|
|
/// </summary>
|
|
private void GetNextSpan()
|
|
{
|
|
Debug.Assert(this.usingSequence, "usingSequence");
|
|
if (!this.Sequence.IsSingleSegment)
|
|
{
|
|
SequencePosition previousNextPosition = this.nextPosition;
|
|
while (this.Sequence.TryGet(ref this.nextPosition, out ReadOnlyMemory<T> memory, advance: true))
|
|
{
|
|
this.currentPosition = previousNextPosition;
|
|
if (memory.Length > 0)
|
|
{
|
|
this.CurrentSpan = memory.Span;
|
|
this.CurrentSpanIndex = 0;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
this.CurrentSpan = default;
|
|
this.CurrentSpanIndex = 0;
|
|
previousNextPosition = this.nextPosition;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.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 && this.CurrentSpan.Length - this.CurrentSpanIndex > (int)count)
|
|
{
|
|
this.CurrentSpanIndex += (int)count;
|
|
this.Consumed += count;
|
|
}
|
|
else if (this.usingSequence)
|
|
{
|
|
// Can't satisfy from the current span
|
|
this.AdvanceToNextSpan(count);
|
|
}
|
|
else if (this.CurrentSpan.Length - this.CurrentSpanIndex == (int)count)
|
|
{
|
|
this.CurrentSpanIndex += (int)count;
|
|
this.Consumed += count;
|
|
this.moreData = false;
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(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, "count >= 0");
|
|
|
|
this.Consumed += count;
|
|
this.CurrentSpanIndex += (int)count;
|
|
if (this.usingSequence && this.CurrentSpanIndex >= this.CurrentSpan.Length)
|
|
{
|
|
this.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, "count >= 0");
|
|
|
|
this.Consumed += count;
|
|
this.CurrentSpanIndex += (int)count;
|
|
|
|
Debug.Assert(this.CurrentSpanIndex < this.CurrentSpan.Length, "this.CurrentSpanIndex < this.CurrentSpan.Length");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the reader ahead the specified number of items
|
|
/// if there are enough elements remaining in the sequence.
|
|
/// </summary>
|
|
/// <returns><c>true</c> if there were enough elements to advance; otherwise <c>false</c>.</returns>
|
|
internal bool TryAdvance(long count)
|
|
{
|
|
if (this.Remaining < count)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
this.Advance(count);
|
|
return true;
|
|
}
|
|
|
|
private void AdvanceToNextSpan(long count)
|
|
{
|
|
Debug.Assert(this.usingSequence, "usingSequence");
|
|
if (count < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(count));
|
|
}
|
|
|
|
this.Consumed += count;
|
|
while (this.moreData)
|
|
{
|
|
int remaining = this.CurrentSpan.Length - this.CurrentSpanIndex;
|
|
|
|
if (remaining > count)
|
|
{
|
|
this.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.
|
|
this.CurrentSpanIndex += remaining;
|
|
count -= remaining;
|
|
Debug.Assert(count >= 0, "count >= 0");
|
|
|
|
this.GetNextSpan();
|
|
|
|
if (count == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (count != 0)
|
|
{
|
|
// Not enough data left- adjust for where we actually ended and throw
|
|
this.Consumed -= count;
|
|
throw new ArgumentOutOfRangeException(nameof(count));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies data from the current <see cref="Position"/> to the given <paramref name="destination"/> span.
|
|
/// </summary>
|
|
/// <param name="destination">Destination to copy to.</param>
|
|
/// <returns>True if there is enough data to copy to the <paramref name="destination"/>.</returns>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public bool TryCopyTo(Span<T> destination)
|
|
{
|
|
ReadOnlySpan<T> firstSpan = this.UnreadSpan;
|
|
if (firstSpan.Length >= destination.Length)
|
|
{
|
|
firstSpan.Slice(0, destination.Length).CopyTo(destination);
|
|
return true;
|
|
}
|
|
|
|
return this.TryCopyMultisegment(destination);
|
|
}
|
|
|
|
internal bool TryCopyMultisegment(Span<T> destination)
|
|
{
|
|
if (this.Remaining < destination.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ReadOnlySpan<T> firstSpan = this.UnreadSpan;
|
|
Debug.Assert(firstSpan.Length < destination.Length, "firstSpan.Length < destination.Length");
|
|
firstSpan.CopyTo(destination);
|
|
int copied = firstSpan.Length;
|
|
|
|
SequencePosition next = this.nextPosition;
|
|
while (this.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;
|
|
}
|
|
}
|
|
}
|