| #region Copyright notice and license | |
| // Protocol Buffers - Google's data interchange format | |
| // Copyright 2008 Google Inc. All rights reserved. | |
| // | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file or at | |
| // https://developers.google.com/open-source/licenses/bsd | |
| #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); | |
| } | |
| } | |
| } |