| #region Copyright notice and license |
| // Protocol Buffers - Google's data interchange format |
| // Copyright 2008 Google Inc. All rights reserved. |
| // https://developers.google.com/protocol-buffers/ |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| #endregion |
| |
| using System; |
| using System.Buffers; |
| using System.Diagnostics; |
| |
| namespace Google.Protobuf.Buffers |
| { |
| /// <summary> |
| /// Represents a heap-based, array-backed output sink into which <typeparam name="T"/> data can be written. |
| /// |
| /// ArrayBufferWriter is originally from corefx, and has been contributed to Protobuf |
| /// https://github.com/dotnet/runtime/blob/071da4c41aa808c949a773b92dca6f88de9d11f3/src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs |
| /// </summary> |
| internal sealed class TestArrayBufferWriter<T> : IBufferWriter<T> |
| { |
| private T[] _buffer; |
| private int _index; |
| |
| private const int DefaultInitialBufferSize = 256; |
| |
| /// <summary> |
| /// Creates an instance of an <see cref="TestArrayBufferWriter{T}"/>, in which data can be written to, |
| /// with the default initial capacity. |
| /// </summary> |
| public TestArrayBufferWriter() |
| { |
| _buffer = new T[0]; |
| _index = 0; |
| } |
| |
| /// <summary> |
| /// Useful for testing writing to buffer writer with a lot of small segments. |
| /// If set, it limits the max number of bytes by which the buffer grows by at once. |
| /// </summary> |
| public int? MaxGrowBy { get; set; } |
| |
| /// <summary> |
| /// Creates an instance of an <see cref="TestArrayBufferWriter{T}"/>, in which data can be written to, |
| /// with an initial capacity specified. |
| /// </summary> |
| /// <param name="initialCapacity">The minimum capacity with which to initialize the underlying buffer.</param> |
| /// <exception cref="ArgumentException"> |
| /// Thrown when <paramref name="initialCapacity"/> is not positive (i.e. less than or equal to 0). |
| /// </exception> |
| public TestArrayBufferWriter(int initialCapacity) |
| { |
| if (initialCapacity <= 0) |
| throw new ArgumentException(nameof(initialCapacity)); |
| |
| _buffer = new T[initialCapacity]; |
| _index = 0; |
| } |
| |
| /// <summary> |
| /// Returns the data written to the underlying buffer so far, as a <see cref="ReadOnlyMemory{T}"/>. |
| /// </summary> |
| public ReadOnlyMemory<T> WrittenMemory => _buffer.AsMemory(0, _index); |
| |
| /// <summary> |
| /// Returns the data written to the underlying buffer so far, as a <see cref="ReadOnlySpan{T}"/>. |
| /// </summary> |
| public ReadOnlySpan<T> WrittenSpan => _buffer.AsSpan(0, _index); |
| |
| /// <summary> |
| /// Returns the amount of data written to the underlying buffer so far. |
| /// </summary> |
| public int WrittenCount => _index; |
| |
| /// <summary> |
| /// Returns the total amount of space within the underlying buffer. |
| /// </summary> |
| public int Capacity => _buffer.Length; |
| |
| /// <summary> |
| /// Returns the amount of space available that can still be written into without forcing the underlying buffer to grow. |
| /// </summary> |
| public int FreeCapacity => _buffer.Length - _index; |
| |
| /// <summary> |
| /// Clears the data written to the underlying buffer. |
| /// </summary> |
| /// <remarks> |
| /// You must clear the <see cref="TestArrayBufferWriter{T}"/> before trying to re-use it. |
| /// </remarks> |
| public void Clear() |
| { |
| Debug.Assert(_buffer.Length >= _index); |
| _buffer.AsSpan(0, _index).Clear(); |
| _index = 0; |
| } |
| |
| /// <summary> |
| /// Notifies <see cref="IBufferWriter{T}"/> that <paramref name="count"/> amount of data was written to the output <see cref="Span{T}"/>/<see cref="Memory{T}"/> |
| /// </summary> |
| /// <exception cref="ArgumentException"> |
| /// Thrown when <paramref name="count"/> is negative. |
| /// </exception> |
| /// <exception cref="InvalidOperationException"> |
| /// Thrown when attempting to advance past the end of the underlying buffer. |
| /// </exception> |
| /// <remarks> |
| /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. |
| /// </remarks> |
| public void Advance(int count) |
| { |
| if (count < 0) |
| throw new ArgumentException(nameof(count)); |
| |
| if (_index > _buffer.Length - count) |
| throw new InvalidOperationException("Advanced past capacity."); |
| |
| _index += count; |
| } |
| |
| /// <summary> |
| /// Returns a <see cref="Memory{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>). |
| /// If no <paramref name="sizeHint"/> is provided (or it's equal to <code>0</code>), some non-empty buffer is returned. |
| /// </summary> |
| /// <exception cref="ArgumentException"> |
| /// Thrown when <paramref name="sizeHint"/> is negative. |
| /// </exception> |
| /// <remarks> |
| /// This will never return an empty <see cref="Memory{T}"/>. |
| /// </remarks> |
| /// <remarks> |
| /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. |
| /// </remarks> |
| /// <remarks> |
| /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. |
| /// </remarks> |
| public Memory<T> GetMemory(int sizeHint = 0) |
| { |
| CheckAndResizeBuffer(sizeHint); |
| Debug.Assert(_buffer.Length > _index); |
| return _buffer.AsMemory(_index); |
| } |
| |
| /// <summary> |
| /// Returns a <see cref="Span{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>). |
| /// If no <paramref name="sizeHint"/> is provided (or it's equal to <code>0</code>), some non-empty buffer is returned. |
| /// </summary> |
| /// <exception cref="ArgumentException"> |
| /// Thrown when <paramref name="sizeHint"/> is negative. |
| /// </exception> |
| /// <remarks> |
| /// This will never return an empty <see cref="Span{T}"/>. |
| /// </remarks> |
| /// <remarks> |
| /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. |
| /// </remarks> |
| /// <remarks> |
| /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. |
| /// </remarks> |
| public Span<T> GetSpan(int sizeHint = 0) |
| { |
| CheckAndResizeBuffer(sizeHint); |
| Debug.Assert(_buffer.Length > _index); |
| return _buffer.AsSpan(_index); |
| } |
| |
| private void CheckAndResizeBuffer(int sizeHint) |
| { |
| if (sizeHint < 0) |
| throw new ArgumentException(nameof(sizeHint)); |
| |
| if (sizeHint == 0) |
| { |
| sizeHint = 1; |
| } |
| |
| if (sizeHint > FreeCapacity) |
| { |
| int growBy = Math.Max(sizeHint, _buffer.Length); |
| |
| if (_buffer.Length == 0) |
| { |
| growBy = Math.Max(growBy, DefaultInitialBufferSize); |
| } |
| |
| // enable tests that write to small buffer segments |
| if (MaxGrowBy.HasValue && growBy > MaxGrowBy.Value) |
| { |
| growBy = MaxGrowBy.Value; |
| } |
| |
| int newSize = checked(_buffer.Length + growBy); |
| |
| Array.Resize(ref _buffer, newSize); |
| } |
| |
| Debug.Assert(FreeCapacity > 0 && FreeCapacity >= sizeHint); |
| } |
| } |
| } |