// // We want to keep this file as-is since it's shared with other repos. // Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace MessagePack { /// /// A fast access struct that wraps . /// internal ref struct BufferWriter { /// /// The underlying . /// private IBufferWriter _output; /// /// The result of the last call to , less any bytes already "consumed" with . /// Backing field for the property. /// private Span _span; /// /// The result of the last call to , less any bytes already "consumed" with . /// private ArraySegment _segment; /// /// The number of uncommitted bytes (all the calls to since the last call to ). /// private int _buffered; /// /// The total number of bytes written with this writer. /// Backing field for the property. /// private long _bytesCommitted; private SequencePool _sequencePool; private SequencePool.Rental _rental; /// /// Initializes a new instance of the struct. /// /// The to be wrapped. [MethodImpl(MethodImplOptions.AggressiveInlining)] public BufferWriter(IBufferWriter output) { _buffered = 0; _bytesCommitted = 0; _output = output ?? throw new ArgumentNullException(nameof(output)); _sequencePool = default; _rental = default; var memory = _output.GetMemoryCheckResult(); MemoryMarshal.TryGetArray(memory, out _segment); _span = memory.Span; } /// /// Initializes a new instance of the struct. /// /// The pool from which to draw an if required.. /// An array to start with so we can avoid accessing the if possible. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal BufferWriter(SequencePool sequencePool, byte[] array) { _buffered = 0; _bytesCommitted = 0; _sequencePool = sequencePool ?? throw new ArgumentNullException(nameof(sequencePool)); _rental = default; _output = null; _segment = new ArraySegment(array); _span = _segment.AsSpan(); } /// /// Gets the result of the last call to . /// public Span Span => _span; /// /// Gets the total number of bytes written with this writer. /// public long BytesCommitted => _bytesCommitted; /// /// Gets the underlying this instance. /// internal IBufferWriter UnderlyingWriter => _output; internal SequencePool.Rental SequenceRental => _rental; public Span GetSpan(int sizeHint) { Ensure(sizeHint); return this.Span; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref byte GetPointer(int sizeHint) { Ensure(sizeHint); if (_segment.Array != null) { return ref _segment.Array[_segment.Offset + _buffered]; } else { return ref _span.GetPinnableReference(); } } /// /// Calls on the underlying writer /// with the number of uncommitted bytes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Commit() { var buffered = _buffered; if (buffered > 0) { this.MigrateToSequence(); _bytesCommitted += buffered; _buffered = 0; _output.Advance(buffered); _span = default; } } /// /// Used to indicate that part of the buffer has been written to. /// /// The number of bytes written to. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Advance(int count) { _buffered += count; _span = _span.Slice(count); } /// /// Copies the caller's buffer into this writer and calls with the length of the source buffer. /// /// The buffer to copy in. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(ReadOnlySpan source) { if (_span.Length >= source.Length) { source.CopyTo(_span); Advance(source.Length); } else { WriteMultiBuffer(source); } } /// /// Acquires a new buffer if necessary to ensure that some given number of bytes can be written to a single buffer. /// /// The number of bytes that must be allocated in a single buffer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Ensure(int count = 1) { if (_span.Length < count) { EnsureMore(count); } } /// /// Gets the span to the bytes written if they were never committed to the underlying buffer writer. /// /// /// internal bool TryGetUncommittedSpan(out ReadOnlySpan span) { if (this._sequencePool != null) { span = _segment.AsSpan(0, _buffered); return true; } span = default; return false; } /// /// Gets a fresh span to write to, with an optional minimum size. /// /// The minimum size for the next requested buffer. [MethodImpl(MethodImplOptions.NoInlining)] private void EnsureMore(int count = 0) { if (_buffered > 0) { Commit(); } else { this.MigrateToSequence(); } var memory = _output.GetMemoryCheckResult(count); MemoryMarshal.TryGetArray(memory, out _segment); _span = memory.Span; } /// /// Copies the caller's buffer into this writer, potentially across multiple buffers from the underlying writer. /// /// The buffer to copy into this writer. private void WriteMultiBuffer(ReadOnlySpan source) { int copiedBytes = 0; int bytesLeftToCopy = source.Length; while (bytesLeftToCopy > 0) { if (_span.Length == 0) { EnsureMore(); } var writable = Math.Min(bytesLeftToCopy, _span.Length); source.Slice(copiedBytes, writable).CopyTo(_span); copiedBytes += writable; bytesLeftToCopy -= writable; Advance(writable); } } private void MigrateToSequence() { if (this._sequencePool != null) { // We were writing to our private scratch memory, so we have to copy it into the actual writer. _rental = _sequencePool.Rent(); _output = _rental.Value; var realSpan = _output.GetSpan(_buffered); _segment.AsSpan(0, _buffered).CopyTo(realSpan); _sequencePool = null; } } } }