Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Buffers;
using Microsoft.Toolkit.HighPerformance.Streams;
using Microsoft.Toolkit.HighPerformance.Streams.Sources;

namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="ArrayPoolBufferWriter{T}"/> type.
/// </summary>
public static class ArrayPoolBufferWriterExtensions
{
/// <summary>
/// Returns a <see cref="Stream"/> that can be used to write to a target an <see cref="ArrayPoolBufferWriter{T}"/> of <see cref="byte"/> instance.
/// </summary>
/// <param name="writer">The target <see cref="ArrayPoolBufferWriter{T}"/> instance.</param>
/// <returns>A <see cref="Stream"/> wrapping <paramref name="writer"/> and writing data to its underlying buffer.</returns>
/// <remarks>The returned <see cref="Stream"/> can only be written to and does not support seeking.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stream AsStream(this ArrayPoolBufferWriter<byte> writer)
{
return new IBufferWriterStream<ArrayBufferWriterOwner>(new ArrayBufferWriterOwner(writer));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@

using System;
using System.Buffers;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers;
using Microsoft.Toolkit.HighPerformance.Streams;
using Microsoft.Toolkit.HighPerformance.Streams.Sources;

namespace Microsoft.Toolkit.HighPerformance.Extensions
{
Expand All @@ -14,6 +19,28 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// </summary>
public static class IBufferWriterExtensions
{
/// <summary>
/// Returns a <see cref="Stream"/> that can be used to write to a target an <see cref="IBufferWriter{T}"/> of <see cref="byte"/> instance.
/// </summary>
/// <param name="writer">The target <see cref="IBufferWriter{T}"/> instance.</param>
/// <returns>A <see cref="Stream"/> wrapping <paramref name="writer"/> and writing data to its underlying buffer.</returns>
/// <remarks>The returned <see cref="Stream"/> can only be written to and does not support seeking.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stream AsStream(this IBufferWriter<byte> writer)
{
if (writer.GetType() == typeof(ArrayPoolBufferWriter<byte>))
{
// If the input writer is of type ArrayPoolBufferWriter<byte>, we can use the type
// specific buffer writer owner to let the JIT elide callvirts when accessing it.
var internalWriter = Unsafe.As<ArrayPoolBufferWriter<byte>>(writer)!;

return new IBufferWriterStream<ArrayBufferWriterOwner>(new ArrayBufferWriterOwner(internalWriter));
}

return new IBufferWriterStream<IBufferWriterOwner>(new IBufferWriterOwner(writer));
}

/// <summary>
/// Writes a value of a specified type into a target <see cref="IBufferWriter{T}"/> instance.
/// </summary>
Expand Down
18 changes: 9 additions & 9 deletions Microsoft.Toolkit.HighPerformance/Extensions/StreamExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,24 +167,24 @@ public static void Write(this Stream stream, ReadOnlySpan<byte> buffer)
/// <returns>The <typeparamref name="T"/> value read from <paramref name="stream"/>.</returns>
/// <exception cref="InvalidOperationException">Thrown if <paramref name="stream"/> reaches the end.</exception>
#if SPAN_RUNTIME_SUPPORT
// Avoid inlining as we're renting a stack buffer, which
// cause issues if this method was called inside a loop
[MethodImpl(MethodImplOptions.NoInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public static T Read<T>(this Stream stream)
where T : unmanaged
{
#if SPAN_RUNTIME_SUPPORT
Span<byte> span = stackalloc byte[Unsafe.SizeOf<T>()];
T result = default;
int length = Unsafe.SizeOf<T>();

if (stream.Read(span) != span.Length)
unsafe
{
ThrowInvalidOperationExceptionForEndOfStream();
if (stream.Read(new Span<byte>(&result, length)) != length)
{
ThrowInvalidOperationExceptionForEndOfStream();
}
}

ref byte r0 = ref MemoryMarshal.GetReference(span);

return Unsafe.ReadUnaligned<T>(ref r0);
return result;
#else
int length = Unsafe.SizeOf<T>();
byte[] buffer = ArrayPool<byte>.Shared.Rent(length);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#if SPAN_RUNTIME_SUPPORT

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Toolkit.HighPerformance.Streams
{
/// <inheritdoc cref="IBufferWriterStream{TWriter}"/>
internal sealed partial class IBufferWriterStream<TWriter>
{
/// <inheritdoc/>
public override void CopyTo(Stream destination, int bufferSize)
{
throw MemoryStream.GetNotSupportedException();
}

/// <inheritdoc/>
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
throw MemoryStream.GetNotSupportedException();
}

/// <inheritdoc/>
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested)
{
return new ValueTask(Task.FromCanceled(cancellationToken));
}

try
{
Write(buffer.Span);

return default;
}
catch (OperationCanceledException e)
{
return new ValueTask(Task.FromCanceled(e.CancellationToken));
}
catch (Exception e)
{
return new ValueTask(Task.FromException(e));
}
}

/// <inheritdoc/>
public override int Read(Span<byte> buffer)
{
throw MemoryStream.GetNotSupportedException();
}

/// <inheritdoc/>
public override void Write(ReadOnlySpan<byte> buffer)
{
MemoryStream.ValidateDisposed(this.disposed);

Span<byte> destination = this.bufferWriter.GetSpan(buffer.Length);

if (!buffer.TryCopyTo(destination))
{
MemoryStream.ThrowArgumentExceptionForEndOfStreamOnWrite();
}

this.bufferWriter.Advance(buffer.Length);
}
}
}

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Toolkit.HighPerformance.Streams
{
/// <summary>
/// A <see cref="Stream"/> implementation wrapping an <see cref="IBufferWriter{T}"/> instance.
/// </summary>
/// <typeparam name="TWriter">The type of buffer writer to use.</typeparam>
internal sealed partial class IBufferWriterStream<TWriter> : Stream
where TWriter : struct, IBufferWriter<byte>
{
/// <summary>
/// The target <typeparamref name="TWriter"/> instance to use.
/// </summary>
private readonly TWriter bufferWriter;

/// <summary>
/// Indicates whether or not the current instance has been disposed
/// </summary>
private bool disposed;

/// <summary>
/// Initializes a new instance of the <see cref="IBufferWriterStream{TWriter}"/> class.
/// </summary>
/// <param name="bufferWriter">The target <see cref="IBufferWriter{T}"/> instance to use.</param>
public IBufferWriterStream(TWriter bufferWriter)
{
this.bufferWriter = bufferWriter;
}

/// <inheritdoc/>
public override bool CanRead => false;

/// <inheritdoc/>
public override bool CanSeek => false;

/// <inheritdoc/>
public override bool CanWrite
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => !this.disposed;
}

/// <inheritdoc/>
public override long Length => throw MemoryStream.GetNotSupportedException();

/// <inheritdoc/>
public override long Position
{
get => throw MemoryStream.GetNotSupportedException();
set => throw MemoryStream.GetNotSupportedException();
}

/// <inheritdoc/>
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
throw MemoryStream.GetNotSupportedException();
}

/// <inheritdoc/>
public override void Flush()
{
}

/// <inheritdoc/>
public override Task FlushAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}

return Task.CompletedTask;
}

/// <inheritdoc/>
public override Task<int> ReadAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
{
throw MemoryStream.GetNotSupportedException();
}

/// <inheritdoc/>
public override Task WriteAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}

try
{
Write(buffer, offset, count);

return Task.CompletedTask;
}
catch (OperationCanceledException e)
{
return Task.FromCanceled(e.CancellationToken);
}
catch (Exception e)
{
return Task.FromException(e);
}
}

/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin)
{
throw MemoryStream.GetNotSupportedException();
}

/// <inheritdoc/>
public override void SetLength(long value)
{
throw MemoryStream.GetNotSupportedException();
}

/// <inheritdoc/>
public override int Read(byte[]? buffer, int offset, int count)
{
throw MemoryStream.GetNotSupportedException();
}

/// <inheritdoc/>
public override int ReadByte()
{
throw MemoryStream.GetNotSupportedException();
}

/// <inheritdoc/>
public override void Write(byte[]? buffer, int offset, int count)
{
MemoryStream.ValidateDisposed(this.disposed);
MemoryStream.ValidateBuffer(buffer, offset, count);

Span<byte>
source = buffer.AsSpan(offset, count),
destination = this.bufferWriter.GetSpan(count);

if (!source.TryCopyTo(destination))
{
MemoryStream.ThrowArgumentExceptionForEndOfStreamOnWrite();
}

this.bufferWriter.Advance(count);
}

/// <inheritdoc/>
public override void WriteByte(byte value)
{
MemoryStream.ValidateDisposed(this.disposed);

this.bufferWriter.GetSpan(1)[0] = value;

this.bufferWriter.Advance(1);
}

/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
this.disposed = true;
}
}
}
Loading