// 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 where T : unmanaged, IEquatable { /// /// A value indicating whether we're using (as opposed to . /// private bool usingSequence; /// /// Backing for the entire sequence when we're not using . /// private ReadOnlySequence sequence; /// /// The position at the start of the . /// private SequencePosition currentPosition; /// /// The position at the end of the . /// private SequencePosition nextPosition; /// /// Backing for the entire sequence when we're not using . /// private ReadOnlyMemory memory; /// /// A value indicating whether there is unread data remaining. /// private bool moreData; /// /// The total number of elements in the sequence. /// private long length; /// /// Initializes a new instance of the struct /// over the given . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SequenceReader(in ReadOnlySequence sequence) { this.usingSequence = true; this.CurrentSpanIndex = 0; this.Consumed = 0; this.sequence = sequence; this.memory = default; this.currentPosition = sequence.Start; this.length = -1; ReadOnlySpan 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(); } } /// /// Initializes a new instance of the struct /// over the given . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SequenceReader(ReadOnlyMemory 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; } /// /// Gets a value indicating whether there is no more data in the . /// public bool End => !this.moreData; /// /// Gets the underlying for the reader. /// public ReadOnlySequence 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(this.memory); this.currentPosition = this.sequence.Start; this.nextPosition = this.sequence.End; } return this.sequence; } } /// /// Gets the current position in the . /// public SequencePosition Position => this.Sequence.GetPosition(this.CurrentSpanIndex, this.currentPosition); /// /// Gets the current segment in the as a span. /// public ReadOnlySpan CurrentSpan { get; private set; } /// /// Gets the index in the . /// public int CurrentSpanIndex { get; private set; } /// /// Gets the unread portion of the . /// public ReadOnlySpan UnreadSpan { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.CurrentSpan.Slice(this.CurrentSpanIndex); } /// /// Gets the total number of 's processed by the reader. /// public long Consumed { get; private set; } /// /// Gets remaining 's in the reader's . /// public long Remaining => this.Length - this.Consumed; /// /// Gets count of in the reader's . /// public long Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if (this.length < 0) { // Cache the length this.length = this.Sequence.Length; } return this.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)] public bool TryPeek(out T value) { if (this.moreData) { value = this.CurrentSpan[this.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 (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; } /// /// Move the reader back the specified number of items. /// [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 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; } } /// /// Get the next segment with available data, if any. /// 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 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; } /// /// 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 && 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)); } } /// /// Unchecked helper to avoid unnecessary checks where you know count is valid. /// [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(); } } /// /// 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, "count >= 0"); this.Consumed += count; this.CurrentSpanIndex += (int)count; Debug.Assert(this.CurrentSpanIndex < this.CurrentSpan.Length, "this.CurrentSpanIndex < this.CurrentSpan.Length"); } /// /// Move the reader ahead the specified number of items /// if there are enough elements remaining in the sequence. /// /// true if there were enough elements to advance; otherwise false. 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)); } } /// /// Copies data from the current to the given span. /// /// Destination to copy to. /// True if there is enough data to copy to the . [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryCopyTo(Span destination) { ReadOnlySpan 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 destination) { if (this.Remaining < destination.Length) { return false; } ReadOnlySpan 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 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; } } }