// // 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;
}
}
}
}