|
@@ -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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|