|
|
|
@ -0,0 +1,213 @@ |
|
|
|
|
#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 ArrayBufferWriter<T> : IBufferWriter<T> |
|
|
|
|
{ |
|
|
|
|
private T[] _buffer; |
|
|
|
|
private int _index; |
|
|
|
|
|
|
|
|
|
private const int DefaultInitialBufferSize = 256; |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Creates an instance of an <see cref="ArrayBufferWriter{T}"/>, in which data can be written to, |
|
|
|
|
/// with the default initial capacity. |
|
|
|
|
/// </summary> |
|
|
|
|
public ArrayBufferWriter() |
|
|
|
|
{ |
|
|
|
|
_buffer = new T[0]; |
|
|
|
|
_index = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Creates an instance of an <see cref="ArrayBufferWriter{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 ArrayBufferWriter(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="ArrayBufferWriter{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); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int newSize = checked(_buffer.Length + growBy); |
|
|
|
|
|
|
|
|
|
Array.Resize(ref _buffer, newSize); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Debug.Assert(FreeCapacity > 0 && FreeCapacity >= sizeHint); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |