// 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 where T : IEquatable #else public ref partial struct SequenceReader where T : unmanaged, IEquatable #endif { private SequencePosition _currentPosition; private SequencePosition _nextPosition; private bool _moreData; #if __MonoCS__ private long _length; #else private readonly long _length; #endif /// /// Create a over the given . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SequenceReader(ReadOnlySequence sequence) { CurrentSpanIndex = 0; Consumed = 0; Sequence = sequence; _currentPosition = sequence.Start; _length = -1; sequence.GetFirstSpan(out ReadOnlySpan first, out _nextPosition); CurrentSpan = first; _moreData = first.Length > 0; if (!_moreData && !sequence.IsSingleSegment) { _moreData = true; GetNextSpan(); } } /// /// True when there is no more data in the . /// #if __MonoCS__ public bool End => !_moreData; #else public readonly bool End => !_moreData; #endif /// /// The underlying for the reader. /// #if __MonoCS__ public ReadOnlySequence Sequence { get; } #else public readonly ReadOnlySequence Sequence { get; } #endif /// /// The current position in the . /// #if __MonoCS__ public SequencePosition Position => Sequence.GetPosition(CurrentSpanIndex, _currentPosition); #else public readonly SequencePosition Position => Sequence.GetPosition(CurrentSpanIndex, _currentPosition); #endif /// /// The current segment in the as a span. /// #if __MonoCS__ public ReadOnlySpan CurrentSpan { get; private set; } #else public ReadOnlySpan CurrentSpan { readonly get; private set; } #endif /// /// The index in the . /// #if __MonoCS__ public int CurrentSpanIndex { get; private set; } #else public int CurrentSpanIndex { readonly get; private set; } #endif /// /// The unread portion of the . /// #if __MonoCS__ public ReadOnlySpan UnreadSpan #else public readonly ReadOnlySpan UnreadSpan #endif { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => CurrentSpan.Slice(CurrentSpanIndex); } /// /// The total number of 's processed by the reader. /// #if __MonoCS__ public long Consumed { get; private set; } #else public long Consumed { readonly get; private set; } #endif /// /// Remaining 's in the reader's . /// #if __MonoCS__ public long Remaining => Length - Consumed; #else public readonly long Remaining => Length - Consumed; #endif /// /// Count of in the reader's . /// #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(lenPtr), Sequence.Length); } } return _length; } } /// /// Peeks at the next value without advancing the reader. /// /// The next value or default if at the end. /// False if at the end of the reader. [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; } } /// /// Read the next value and advance the reader. /// /// The next value or default if at the end. /// False if at the end of the reader. [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; } /// /// Move the reader back the specified number of items. /// /// /// Thrown if trying to rewind a negative amount or more than . /// [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 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; } } /// /// Get the next segment with available data, if any. /// private void GetNextSpan() { if (!Sequence.IsSingleSegment) { SequencePosition previousNextPosition = _nextPosition; while (Sequence.TryGet(ref _nextPosition, out ReadOnlyMemory memory, advance: true)) { _currentPosition = previousNextPosition; if (memory.Length > 0) { CurrentSpan = memory.Span; CurrentSpanIndex = 0; return; } else { CurrentSpan = default; CurrentSpanIndex = 0; previousNextPosition = _nextPosition; } } } _moreData = false; } /// /// Move the reader ahead the specified number of items. /// [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); } } /// /// Unchecked helper to avoid unnecessary checks where you know count is valid. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void AdvanceCurrentSpan(long count) { Debug.Assert(count >= 0); Consumed += count; CurrentSpanIndex += (int)count; if (CurrentSpanIndex >= CurrentSpan.Length) GetNextSpan(); } /// /// 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. /// [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); } } /// /// Copies data from the current to the given span if there /// is enough data to fill it. /// /// /// 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 can be used. /// /// Destination span to copy to. /// True if there is enough data to completely fill the span. [MethodImpl(MethodImplOptions.AggressiveInlining)] #if __MonoCS__ public bool TryCopyTo(Span destination) #else public readonly bool TryCopyTo(Span 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 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 destination) #else internal readonly bool TryCopyMultisegment(Span destination) #endif { // If we don't have enough to fill the requested buffer, return false if (Remaining < destination.Length) return false; ReadOnlySpan 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 nextSegment, true)) { if (nextSegment.Length > 0) { ReadOnlySpan 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; } } }