1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#endregion
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Security;
namespace Google.Protobuf
{
/// <summary>
/// Abstraction for writing to a steam / IBufferWriter
/// </summary>
[SecuritySafeCritical]
internal struct WriteBufferHelper
{
private IBufferWriter<byte> bufferWriter;
private CodedOutputStream codedOutputStream;
public CodedOutputStream CodedOutputStream => codedOutputStream;
/// <summary>
/// Initialize an instance with a coded output stream.
/// This approach is faster than using a constructor because the instance to initialize is passed by reference
/// and we can write directly into it without copying.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Initialize(CodedOutputStream codedOutputStream, out WriteBufferHelper instance)
{
instance.bufferWriter = null;
instance.codedOutputStream = codedOutputStream;
}
/// <summary>
/// Initialize an instance with a buffer writer.
/// This approach is faster than using a constructor because the instance to initialize is passed by reference
/// and we can write directly into it without copying.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Initialize(IBufferWriter<byte> bufferWriter, out WriteBufferHelper instance, out Span<byte> buffer)
{
instance.bufferWriter = bufferWriter;
instance.codedOutputStream = null;
buffer = default; // TODO: initialize the initial buffer so that the first write is not via slowpath.
}
/// <summary>
/// Initialize an instance with a buffer represented by a single span (i.e. buffer cannot be refreshed)
/// This approach is faster than using a constructor because the instance to initialize is passed by reference
/// and we can write directly into it without copying.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void InitializeNonRefreshable(out WriteBufferHelper instance)
{
instance.bufferWriter = null;
instance.codedOutputStream = null;
}
/// <summary>
/// Verifies that SpaceLeft returns zero.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CheckNoSpaceLeft(ref WriterInternalState state)
{
if (GetSpaceLeft(ref state) != 0)
{
throw new InvalidOperationException("Did not write as much data as expected.");
}
}
/// <summary>
/// If writing to a flat array, returns the space left in the array. Otherwise,
/// throws an InvalidOperationException.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetSpaceLeft(ref WriterInternalState state)
{
if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream == null && state.writeBufferHelper.bufferWriter == null)
{
return state.limit - state.position;
}
else
{
throw new InvalidOperationException(
"SpaceLeft can only be called on CodedOutputStreams that are " +
"writing to a flat array or when writing to a single span.");
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void RefreshBuffer(ref Span<byte> buffer, ref WriterInternalState state)
{
if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream != null)
{
// because we're using coded output stream, we know that "buffer" and codedOutputStream.InternalBuffer are identical.
state.writeBufferHelper.codedOutputStream.InternalOutputStream.Write(state.writeBufferHelper.codedOutputStream.InternalBuffer, 0, state.position);
// reset position, limit stays the same because we are reusing the codedOutputStream's internal buffer.
state.position = 0;
}
else if (state.writeBufferHelper.bufferWriter != null)
{
// commit the bytes and get a new buffer to write to.
state.writeBufferHelper.bufferWriter.Advance(state.position);
state.position = 0;
buffer = state.writeBufferHelper.bufferWriter.GetSpan();
state.limit = buffer.Length;
}
else
{
// We're writing to a single buffer.
throw new CodedOutputStream.OutOfSpaceException();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Flush(ref Span<byte> buffer, ref WriterInternalState state)
{
if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream != null)
{
// because we're using coded output stream, we know that "buffer" and codedOutputStream.InternalBuffer are identical.
state.writeBufferHelper.codedOutputStream.InternalOutputStream.Write(state.writeBufferHelper.codedOutputStream.InternalBuffer, 0, state.position);
state.position = 0;
}
else if (state.writeBufferHelper.bufferWriter != null)
{
// calling Advance invalidates the current buffer and we must not continue writing to it,
// so we set the current buffer to point to an empty Span. If any subsequent writes happen,
// the first subsequent write will trigger refresing of the buffer.
state.writeBufferHelper.bufferWriter.Advance(state.position);
state.position = 0;
state.limit = 0;
buffer = default; // invalidate the current buffer
}
}
}
}