Jan Tattermusch | 90d4969 | 2020-05-29 14:24:26 +0200 | [diff] [blame] | 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> |
James Newton-King | 8932446 | 2020-12-16 12:56:14 +1300 | [diff] [blame] | 45 | internal sealed class TestArrayBufferWriter<T> : IBufferWriter<T> |
Jan Tattermusch | 90d4969 | 2020-05-29 14:24:26 +0200 | [diff] [blame] | 46 | { |
| 47 | private T[] _buffer; |
| 48 | private int _index; |
| 49 | |
| 50 | private const int DefaultInitialBufferSize = 256; |
| 51 | |
| 52 | /// <summary> |
James Newton-King | 8932446 | 2020-12-16 12:56:14 +1300 | [diff] [blame] | 53 | /// Creates an instance of an <see cref="TestArrayBufferWriter{T}"/>, in which data can be written to, |
Jan Tattermusch | 90d4969 | 2020-05-29 14:24:26 +0200 | [diff] [blame] | 54 | /// with the default initial capacity. |
| 55 | /// </summary> |
James Newton-King | 8932446 | 2020-12-16 12:56:14 +1300 | [diff] [blame] | 56 | public TestArrayBufferWriter() |
Jan Tattermusch | 90d4969 | 2020-05-29 14:24:26 +0200 | [diff] [blame] | 57 | { |
| 58 | _buffer = new T[0]; |
| 59 | _index = 0; |
| 60 | } |
| 61 | |
| 62 | /// <summary> |
Peter Newman | e2cc2de | 2020-08-10 19:08:25 +0100 | [diff] [blame] | 63 | /// Useful for testing writing to buffer writer with a lot of small segments. |
Jan Tattermusch | 5637289 | 2020-06-03 16:58:02 +0200 | [diff] [blame] | 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> |
James Newton-King | 8932446 | 2020-12-16 12:56:14 +1300 | [diff] [blame] | 69 | /// Creates an instance of an <see cref="TestArrayBufferWriter{T}"/>, in which data can be written to, |
Jan Tattermusch | 90d4969 | 2020-05-29 14:24:26 +0200 | [diff] [blame] | 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> |
James Newton-King | 8932446 | 2020-12-16 12:56:14 +1300 | [diff] [blame] | 76 | public TestArrayBufferWriter(int initialCapacity) |
Jan Tattermusch | 90d4969 | 2020-05-29 14:24:26 +0200 | [diff] [blame] | 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> |
James Newton-King | 8932446 | 2020-12-16 12:56:14 +1300 | [diff] [blame] | 114 | /// You must clear the <see cref="TestArrayBufferWriter{T}"/> before trying to re-use it. |
Jan Tattermusch | 90d4969 | 2020-05-29 14:24:26 +0200 | [diff] [blame] | 115 | /// </remarks> |
| 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> |
| 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> |
| 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> |
| 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 | |
| 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 | |
Jan Tattermusch | 5637289 | 2020-06-03 16:58:02 +0200 | [diff] [blame] | 211 | // enable tests that write to small buffer segments |
| 212 | if (MaxGrowBy.HasValue && growBy > MaxGrowBy.Value) |
| 213 | { |
| 214 | growBy = MaxGrowBy.Value; |
| 215 | } |
| 216 | |
Jan Tattermusch | 90d4969 | 2020-05-29 14:24:26 +0200 | [diff] [blame] | 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 | } |