message extensions + refactor

pull/7576/head
Jan Tattermusch 5 years ago
parent 8dbf707aa0
commit 9039103637
  1. 26
      csharp/src/Google.Protobuf/CodedOutputStream.cs
  2. 35
      csharp/src/Google.Protobuf/MessageExtensions.cs
  3. 69
      csharp/src/Google.Protobuf/WriteBufferHelper.cs
  4. 18
      csharp/src/Google.Protobuf/WriteContext.cs
  5. 4
      csharp/src/Google.Protobuf/WritingPrimitives.cs

@ -636,7 +636,7 @@ namespace Google.Protobuf
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 @@ namespace Google.Protobuf
/// 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;

@ -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;
@ -145,6 +146,40 @@ namespace Google.Protobuf
return ByteString.AttachBytes(message.ToByteArray());
}
/// <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>

@ -62,7 +62,7 @@ namespace Google.Protobuf
}
/// <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 @@ namespace Google.Protobuf
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)
{
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 (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);
// 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 @@ namespace Google.Protobuf
}
}
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

@ -87,6 +87,16 @@ namespace Google.Protobuf
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 @@ namespace Google.Protobuf
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)

@ -376,7 +376,7 @@ namespace Google.Protobuf
{
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 @@ namespace Google.Protobuf
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

Loading…
Cancel
Save