Browse Source

message extensions + refactor

Jan Tattermusch 5 years ago
parent
commit
9039103637

+ 4 - 22
csharp/src/Google.Protobuf/CodedOutputStream.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;
 

+ 35 - 0
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;
@@ -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>

+ 57 - 12
csharp/src/Google.Protobuf/WriteBufferHelper.cs

@@ -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

+ 16 - 2
csharp/src/Google.Protobuf/WriteContext.cs

@@ -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)

+ 2 - 2
csharp/src/Google.Protobuf/WritingPrimitives.cs

@@ -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