#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 { /// /// Represents a heap-based, array-backed output sink into which 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 /// internal sealed class ArrayBufferWriter : IBufferWriter { private T[] _buffer; private int _index; private const int DefaultInitialBufferSize = 256; /// /// Creates an instance of an , in which data can be written to, /// with the default initial capacity. /// public ArrayBufferWriter() { _buffer = new T[0]; _index = 0; } /// /// Userful 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. /// public int? MaxGrowBy { get; set; } /// /// Creates an instance of an , in which data can be written to, /// with an initial capacity specified. /// /// The minimum capacity with which to initialize the underlying buffer. /// /// Thrown when is not positive (i.e. less than or equal to 0). /// public ArrayBufferWriter(int initialCapacity) { if (initialCapacity <= 0) throw new ArgumentException(nameof(initialCapacity)); _buffer = new T[initialCapacity]; _index = 0; } /// /// Returns the data written to the underlying buffer so far, as a . /// public ReadOnlyMemory WrittenMemory => _buffer.AsMemory(0, _index); /// /// Returns the data written to the underlying buffer so far, as a . /// public ReadOnlySpan WrittenSpan => _buffer.AsSpan(0, _index); /// /// Returns the amount of data written to the underlying buffer so far. /// public int WrittenCount => _index; /// /// Returns the total amount of space within the underlying buffer. /// public int Capacity => _buffer.Length; /// /// Returns the amount of space available that can still be written into without forcing the underlying buffer to grow. /// public int FreeCapacity => _buffer.Length - _index; /// /// Clears the data written to the underlying buffer. /// /// /// You must clear the before trying to re-use it. /// public void Clear() { Debug.Assert(_buffer.Length >= _index); _buffer.AsSpan(0, _index).Clear(); _index = 0; } /// /// Notifies that amount of data was written to the output / /// /// /// Thrown when is negative. /// /// /// Thrown when attempting to advance past the end of the underlying buffer. /// /// /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. /// 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; } /// /// Returns a to write to that is at least the requested length (specified by ). /// If no is provided (or it's equal to 0), some non-empty buffer is returned. /// /// /// Thrown when is negative. /// /// /// This will never return an empty . /// /// /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. /// /// /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. /// public Memory GetMemory(int sizeHint = 0) { CheckAndResizeBuffer(sizeHint); Debug.Assert(_buffer.Length > _index); return _buffer.AsMemory(_index); } /// /// Returns a to write to that is at least the requested length (specified by ). /// If no is provided (or it's equal to 0), some non-empty buffer is returned. /// /// /// Thrown when is negative. /// /// /// This will never return an empty . /// /// /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. /// /// /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. /// public Span 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); } } }