1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2008 Google Inc. All rights reserved. 4 // https://developers.google.com/protocol-buffers/ 5 // 6 // Redistribution and use in source and binary forms, with or without 7 // modification, are permitted provided that the following conditions are 8 // met: 9 // 10 // * Redistributions of source code must retain the above copyright 11 // notice, this list of conditions and the following disclaimer. 12 // * Redistributions in binary form must reproduce the above 13 // copyright notice, this list of conditions and the following disclaimer 14 // in the documentation and/or other materials provided with the 15 // distribution. 16 // * Neither the name of Google Inc. nor the names of its 17 // contributors may be used to endorse or promote products derived from 18 // this software without specific prior written permission. 19 // 20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 #endregion 32 33 using System; 34 using System.Buffers; 35 using System.Diagnostics; 36 37 namespace Google.Protobuf.Buffers 38 { 39 /// <summary> 40 /// Represents a heap-based, array-backed output sink into which <typeparam name="T"/> data can be written. 41 /// 42 /// ArrayBufferWriter is originally from corefx, and has been contributed to Protobuf 43 /// https://github.com/dotnet/runtime/blob/071da4c41aa808c949a773b92dca6f88de9d11f3/src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs 44 /// </summary> 45 internal sealed class ArrayBufferWriter<T> : IBufferWriter<T> 46 { 47 private T[] _buffer; 48 private int _index; 49 50 private const int DefaultInitialBufferSize = 256; 51 52 /// <summary> 53 /// Creates an instance of an <see cref="ArrayBufferWriter{T}"/>, in which data can be written to, 54 /// with the default initial capacity. 55 /// </summary> ArrayBufferWriter()56 public ArrayBufferWriter() 57 { 58 _buffer = new T[0]; 59 _index = 0; 60 } 61 62 /// <summary> 63 /// Userful for testing writing to buffer writer with a lot of small segments. 64 /// If set, it limits the max number of bytes by which the buffer grows by at once. 65 /// </summary> 66 public int? MaxGrowBy { get; set; } 67 68 /// <summary> 69 /// Creates an instance of an <see cref="ArrayBufferWriter{T}"/>, in which data can be written to, 70 /// with an initial capacity specified. 71 /// </summary> 72 /// <param name="initialCapacity">The minimum capacity with which to initialize the underlying buffer.</param> 73 /// <exception cref="ArgumentException"> 74 /// Thrown when <paramref name="initialCapacity"/> is not positive (i.e. less than or equal to 0). 75 /// </exception> ArrayBufferWriter(int initialCapacity)76 public ArrayBufferWriter(int initialCapacity) 77 { 78 if (initialCapacity <= 0) 79 throw new ArgumentException(nameof(initialCapacity)); 80 81 _buffer = new T[initialCapacity]; 82 _index = 0; 83 } 84 85 /// <summary> 86 /// Returns the data written to the underlying buffer so far, as a <see cref="ReadOnlyMemory{T}"/>. 87 /// </summary> 88 public ReadOnlyMemory<T> WrittenMemory => _buffer.AsMemory(0, _index); 89 90 /// <summary> 91 /// Returns the data written to the underlying buffer so far, as a <see cref="ReadOnlySpan{T}"/>. 92 /// </summary> 93 public ReadOnlySpan<T> WrittenSpan => _buffer.AsSpan(0, _index); 94 95 /// <summary> 96 /// Returns the amount of data written to the underlying buffer so far. 97 /// </summary> 98 public int WrittenCount => _index; 99 100 /// <summary> 101 /// Returns the total amount of space within the underlying buffer. 102 /// </summary> 103 public int Capacity => _buffer.Length; 104 105 /// <summary> 106 /// Returns the amount of space available that can still be written into without forcing the underlying buffer to grow. 107 /// </summary> 108 public int FreeCapacity => _buffer.Length - _index; 109 110 /// <summary> 111 /// Clears the data written to the underlying buffer. 112 /// </summary> 113 /// <remarks> 114 /// You must clear the <see cref="ArrayBufferWriter{T}"/> before trying to re-use it. 115 /// </remarks> Clear()116 public void Clear() 117 { 118 Debug.Assert(_buffer.Length >= _index); 119 _buffer.AsSpan(0, _index).Clear(); 120 _index = 0; 121 } 122 123 /// <summary> 124 /// 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}"/> 125 /// </summary> 126 /// <exception cref="ArgumentException"> 127 /// Thrown when <paramref name="count"/> is negative. 128 /// </exception> 129 /// <exception cref="InvalidOperationException"> 130 /// Thrown when attempting to advance past the end of the underlying buffer. 131 /// </exception> 132 /// <remarks> 133 /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. 134 /// </remarks> Advance(int count)135 public void Advance(int count) 136 { 137 if (count < 0) 138 throw new ArgumentException(nameof(count)); 139 140 if (_index > _buffer.Length - count) 141 throw new InvalidOperationException("Advanced past capacity."); 142 143 _index += count; 144 } 145 146 /// <summary> 147 /// Returns a <see cref="Memory{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>). 148 /// If no <paramref name="sizeHint"/> is provided (or it's equal to <code>0</code>), some non-empty buffer is returned. 149 /// </summary> 150 /// <exception cref="ArgumentException"> 151 /// Thrown when <paramref name="sizeHint"/> is negative. 152 /// </exception> 153 /// <remarks> 154 /// This will never return an empty <see cref="Memory{T}"/>. 155 /// </remarks> 156 /// <remarks> 157 /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. 158 /// </remarks> 159 /// <remarks> 160 /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. 161 /// </remarks> GetMemory(int sizeHint = 0)162 public Memory<T> GetMemory(int sizeHint = 0) 163 { 164 CheckAndResizeBuffer(sizeHint); 165 Debug.Assert(_buffer.Length > _index); 166 return _buffer.AsMemory(_index); 167 } 168 169 /// <summary> 170 /// Returns a <see cref="Span{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>). 171 /// If no <paramref name="sizeHint"/> is provided (or it's equal to <code>0</code>), some non-empty buffer is returned. 172 /// </summary> 173 /// <exception cref="ArgumentException"> 174 /// Thrown when <paramref name="sizeHint"/> is negative. 175 /// </exception> 176 /// <remarks> 177 /// This will never return an empty <see cref="Span{T}"/>. 178 /// </remarks> 179 /// <remarks> 180 /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. 181 /// </remarks> 182 /// <remarks> 183 /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. 184 /// </remarks> GetSpan(int sizeHint = 0)185 public Span<T> GetSpan(int sizeHint = 0) 186 { 187 CheckAndResizeBuffer(sizeHint); 188 Debug.Assert(_buffer.Length > _index); 189 return _buffer.AsSpan(_index); 190 } 191 CheckAndResizeBuffer(int sizeHint)192 private void CheckAndResizeBuffer(int sizeHint) 193 { 194 if (sizeHint < 0) 195 throw new ArgumentException(nameof(sizeHint)); 196 197 if (sizeHint == 0) 198 { 199 sizeHint = 1; 200 } 201 202 if (sizeHint > FreeCapacity) 203 { 204 int growBy = Math.Max(sizeHint, _buffer.Length); 205 206 if (_buffer.Length == 0) 207 { 208 growBy = Math.Max(growBy, DefaultInitialBufferSize); 209 } 210 211 // enable tests that write to small buffer segments 212 if (MaxGrowBy.HasValue && growBy > MaxGrowBy.Value) 213 { 214 growBy = MaxGrowBy.Value; 215 } 216 217 int newSize = checked(_buffer.Length + growBy); 218 219 Array.Resize(ref _buffer, newSize); 220 } 221 222 Debug.Assert(FreeCapacity > 0 && FreeCapacity >= sizeHint); 223 } 224 } 225 }