• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }