message extensions + refactor
diff --git a/csharp/src/Google.Protobuf/CodedOutputStream.cs b/csharp/src/Google.Protobuf/CodedOutputStream.cs
index b4c9045..1c4e40b 100644
--- a/csharp/src/Google.Protobuf/CodedOutputStream.cs
+++ b/csharp/src/Google.Protobuf/CodedOutputStream.cs
@@ -636,7 +636,7 @@
public void Flush()
{
var span = new Span<byte>(buffer);
- state.writeBufferHelper.Flush(ref span, ref state);
+ WriteBufferHelper.Flush(ref span, ref state);
/*if (output != null)
{
@@ -648,36 +648,18 @@
/// Verifies that SpaceLeft returns zero. It's common to create a byte array
/// that is exactly big enough to hold a message, then write to it with
/// a CodedOutputStream. Calling CheckNoSpaceLeft after writing verifies that
- /// the message was actually as big as expected, which can help bugs.
+ /// the message was actually as big as expected, which can help finding bugs.
/// </summary>
public void CheckNoSpaceLeft()
{
- if (SpaceLeft != 0)
- {
- throw new InvalidOperationException("Did not write as much data as expected.");
- }
+ WriteBufferHelper.CheckNoSpaceLeft(ref state);
}
/// <summary>
/// If writing to a flat array, returns the space left in the array. Otherwise,
/// throws an InvalidOperationException.
/// </summary>
- public int SpaceLeft
- {
- get
- {
- if (output == null)
- {
- return state.limit - state.position;
- }
- else
- {
- throw new InvalidOperationException(
- "SpaceLeft can only be called on CodedOutputStreams that are " +
- "writing to a flat array.");
- }
- }
- }
+ public int SpaceLeft => WriteBufferHelper.GetSpaceLeft(ref state);
internal byte[] InternalBuffer => buffer;
diff --git a/csharp/src/Google.Protobuf/MessageExtensions.cs b/csharp/src/Google.Protobuf/MessageExtensions.cs
index e9a408c..d0db44b 100644
--- a/csharp/src/Google.Protobuf/MessageExtensions.cs
+++ b/csharp/src/Google.Protobuf/MessageExtensions.cs
@@ -33,6 +33,7 @@
using Google.Protobuf.Reflection;
using System.Buffers;
using System.Collections;
+using System;
using System.IO;
using System.Linq;
using System.Security;
@@ -146,6 +147,40 @@
}
/// <summary>
+ /// Writes the given message data to the given buffer writer in protobuf encoding.
+ /// </summary>
+ /// <param name="message">The message to write to the stream.</param>
+ /// <param name="output">The stream to write to.</param>
+ public static void WriteTo(this IMessage message, IBufferWriter<byte> output)
+ {
+ ProtoPreconditions.CheckNotNull(message, nameof(message));
+ ProtoPreconditions.CheckNotNull(output, nameof(output));
+
+ WriteContext.Initialize(output, out WriteContext ctx);
+ WritingPrimitivesMessages.WriteRawMessage(ref ctx, message);
+ ctx.Flush();
+
+ // TODO: handling errors when IBufferWriter is used?
+ }
+
+ /// <summary>
+ /// Writes the given message data to the given span in protobuf encoding.
+ /// The size of the destination span needs to fit the serialized size
+ /// of the message exactly, otherwise an exception is thrown.
+ /// </summary>
+ /// <param name="message">The message to write to the stream.</param>
+ /// <param name="output">The span to write to. Size must match size of the message exactly.</param>
+ public static void WriteTo(this IMessage message, Span<byte> output)
+ {
+ ProtoPreconditions.CheckNotNull(message, nameof(message));
+
+ WriteContext.Initialize(ref output, out WriteContext ctx);
+ WritingPrimitivesMessages.WriteRawMessage(ref ctx, message);
+ ctx.Flush();
+ ctx.CheckNoSpaceLeft();
+ }
+
+ /// <summary>
/// Checks if all required fields in a message have values set. For proto3 messages, this returns true
/// </summary>
public static bool IsInitialized(this IMessage message)
diff --git a/csharp/src/Google.Protobuf/WriteBufferHelper.cs b/csharp/src/Google.Protobuf/WriteBufferHelper.cs
index bf29b22..9bd5061 100644
--- a/csharp/src/Google.Protobuf/WriteBufferHelper.cs
+++ b/csharp/src/Google.Protobuf/WriteBufferHelper.cs
@@ -62,7 +62,7 @@
}
/// <summary>
- /// Initialize an instance with a coded output stream.
+ /// 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>
@@ -74,21 +74,65 @@
buffer = default; // TODO: initialize the initial buffer so that the first write is not via slowpath.
}
- public void RefreshBuffer(ref Span<byte> buffer, ref WriterInternalState state)
+ /// <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)
{
- if (codedOutputStream?.InternalOutputStream != null)
+ 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.AggressiveInlining)]
+ 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.
- codedOutputStream.InternalOutputStream.Write(codedOutputStream.InternalBuffer, 0, state.position);
+ 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 (bufferWriter != null)
+ else if (state.writeBufferHelper.bufferWriter != null)
{
// commit the bytes and get a new buffer to write to.
- bufferWriter.Advance(state.position);
+ state.writeBufferHelper.bufferWriter.Advance(state.position);
state.position = 0;
- buffer = bufferWriter.GetSpan();
+ buffer = state.writeBufferHelper.bufferWriter.GetSpan();
state.limit = buffer.Length;
}
else
@@ -98,17 +142,18 @@
}
}
- public void Flush(ref Span<byte> buffer, ref WriterInternalState state)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Flush(ref Span<byte> buffer, ref WriterInternalState state)
{
- if (codedOutputStream?.InternalOutputStream != null)
+ if (state.writeBufferHelper.codedOutputStream?.InternalOutputStream != null)
{
// because we're using coded output stream, we know that "buffer" and codedOutputStream.InternalBuffer are identical.
- codedOutputStream.InternalOutputStream.Write(codedOutputStream.InternalBuffer, 0, state.position);
+ state.writeBufferHelper.codedOutputStream.InternalOutputStream.Write(state.writeBufferHelper.codedOutputStream.InternalBuffer, 0, state.position);
state.position = 0;
}
- else if (bufferWriter != null)
+ else if (state.writeBufferHelper.bufferWriter != null)
{
- bufferWriter.Advance(state.position);
+ state.writeBufferHelper.bufferWriter.Advance(state.position);
state.position = 0;
state.limit = 0;
buffer = default; // invalidate the current buffer
diff --git a/csharp/src/Google.Protobuf/WriteContext.cs b/csharp/src/Google.Protobuf/WriteContext.cs
index c4d0343..e822e1d 100644
--- a/csharp/src/Google.Protobuf/WriteContext.cs
+++ b/csharp/src/Google.Protobuf/WriteContext.cs
@@ -87,6 +87,16 @@
ctx.state.position = 0;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void Initialize(ref Span<byte> buffer, out WriteContext ctx)
+ {
+ ctx.buffer = buffer;
+ ctx.state = default;
+ ctx.state.limit = ctx.buffer.Length;
+ ctx.state.position = 0;
+ WriteBufferHelper.InitializeNonRefreshable(out ctx.state.writeBufferHelper);
+ }
+
/// <summary>
/// Writes a double field value, without a tag.
/// </summary>
@@ -340,8 +350,12 @@
internal void Flush()
{
- // TODO: should the method be static or not?
- state.writeBufferHelper.Flush(ref buffer, ref state);
+ WriteBufferHelper.Flush(ref buffer, ref state);
+ }
+
+ internal void CheckNoSpaceLeft()
+ {
+ WriteBufferHelper.CheckNoSpaceLeft(ref state);
}
internal void CopyStateTo(CodedOutputStream output)
diff --git a/csharp/src/Google.Protobuf/WritingPrimitives.cs b/csharp/src/Google.Protobuf/WritingPrimitives.cs
index 76df2df..f618789 100644
--- a/csharp/src/Google.Protobuf/WritingPrimitives.cs
+++ b/csharp/src/Google.Protobuf/WritingPrimitives.cs
@@ -376,7 +376,7 @@
{
if (state.position == state.limit)
{
- state.writeBufferHelper.RefreshBuffer(ref buffer, ref state);
+ WriteBufferHelper.RefreshBuffer(ref buffer, ref state);
}
buffer[state.position++] = value;
@@ -429,7 +429,7 @@
value.Slice(bytesWritten, length).CopyTo(buffer.Slice(state.position, length));
bytesWritten += length;
state.position += length;
- state.writeBufferHelper.RefreshBuffer(ref buffer, ref state);
+ WriteBufferHelper.RefreshBuffer(ref buffer, ref state);
}
// copy the remaining data