diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs index fbcef472cb5..cb4ec0f2829 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs @@ -2,11 +2,12 @@ // 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.Diagnostics.Contracts; using System.IO; using System.Runtime.CompilerServices; -using Microsoft.Toolkit.HighPerformance.Streams; +using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream; namespace Microsoft.Toolkit.HighPerformance.Extensions { @@ -18,17 +19,18 @@ public static class IMemoryOwnerExtensions /// /// Returns a wrapping the contents of the given of instance. /// - /// The input of instance. - /// A wrapping the data within . + /// The input of instance. + /// A wrapping the data within . /// /// The caller does not need to track the lifetime of the input of /// instance, as the returned will take care of disposing that buffer when it is closed. /// + /// Thrown when has an invalid data store. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Stream AsStream(this IMemoryOwner memory) + public static Stream AsStream(this IMemoryOwner memoryOwner) { - return new IMemoryOwnerStream(memory); + return MemoryStream.Create(memoryOwner); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs index 8c1e053a186..794339fbbed 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs @@ -26,11 +26,12 @@ public static class MemoryExtensions /// In particular, the caller must ensure that the target buffer is not disposed as long /// as the returned is in use, to avoid unexpected issues. /// + /// Thrown when has an invalid data store. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Stream AsStream(this Memory memory) { - return new MemoryStream(memory); + return MemoryStream.Create(memory, false); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs index 46101e3db89..eab50592776 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs @@ -5,7 +5,6 @@ using System; using System.Diagnostics.Contracts; using System.IO; -using System.Runtime.CompilerServices; using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream; namespace Microsoft.Toolkit.HighPerformance.Extensions @@ -26,11 +25,11 @@ public static class ReadOnlyMemoryExtensions /// In particular, the caller must ensure that the target buffer is not disposed as long /// as the returned is in use, to avoid unexpected issues. /// + /// Thrown when has an invalid data store. [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Stream AsStream(this ReadOnlyMemory memory) { - return new MemoryStream(memory); + return MemoryStream.Create(memory, true); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream.cs b/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs similarity index 50% rename from Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream.cs rename to Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs index c9a0a428e4b..2dfe66e215e 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs @@ -2,6 +2,7 @@ // 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; @@ -10,21 +11,24 @@ namespace Microsoft.Toolkit.HighPerformance.Streams /// /// A implementation wrapping an of instance. /// - internal sealed class IMemoryOwnerStream : MemoryStream + /// The type of source to use for the underlying data. + internal sealed class IMemoryOwnerStream : MemoryStream + where TSource : struct, ISpanOwner { /// - /// The of instance currently in use. + /// The instance currently in use. /// - private readonly IMemoryOwner memory; + private readonly IDisposable disposable; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The input of instance to use. - public IMemoryOwnerStream(IMemoryOwner memory) - : base(memory.Memory) + /// The input instance to use. + /// The instance currently in use. + public IMemoryOwnerStream(TSource source, IDisposable disposable) + : base(source, false) { - this.memory = memory; + this.disposable = disposable; } /// @@ -32,7 +36,7 @@ protected override void Dispose(bool disposing) { base.Dispose(disposing); - this.memory.Dispose(); + this.disposable.Dispose(); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs index 209c935e4a5..cdfc24112d5 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -8,16 +8,41 @@ namespace Microsoft.Toolkit.HighPerformance.Streams { /// - /// A implementation wrapping a or instance. + /// A factory class to produce instances. /// - internal partial class MemoryStream + internal static partial class MemoryStream { + /// + /// Throws an when trying to write too many bytes to the target stream. + /// + public static void ThrowArgumentExceptionForEndOfStreamOnWrite() + { + throw new ArgumentException("The current stream can't contain the requested input data."); + } + + /// + /// Throws a when trying to set the length of the stream. + /// + public static void ThrowNotSupportedExceptionForSetLength() + { + throw new NotSupportedException("Setting the length is not supported for this stream."); + } + + /// + /// Throws an when using an invalid seek mode. + /// + /// Nothing, as this method throws unconditionally. + public static long ThrowArgumentExceptionForSeekOrigin() + { + throw new ArgumentException("The input seek mode is not valid.", "origin"); + } + /// /// Throws an when setting the property. /// private static void ThrowArgumentOutOfRangeExceptionForPosition() { - throw new ArgumentOutOfRangeException(nameof(Position), "The value for the property was not in the valid range."); + throw new ArgumentOutOfRangeException(nameof(Stream.Position), "The value for the property was not in the valid range."); } /// @@ -60,37 +85,12 @@ private static void ThrowNotSupportedExceptionForCanWrite() throw new NotSupportedException("The current stream doesn't support writing."); } - /// - /// Throws an when trying to write too many bytes to the target stream. - /// - private static void ThrowArgumentExceptionForEndOfStreamOnWrite() - { - throw new ArgumentException("The current stream can't contain the requested input data."); - } - - /// - /// Throws a when trying to set the length of the stream. - /// - private static void ThrowNotSupportedExceptionForSetLength() - { - throw new NotSupportedException("Setting the length is not supported for this stream."); - } - - /// - /// Throws an when using an invalid seek mode. - /// - /// Nothing, as this method throws unconditionally. - private static long ThrowArgumentExceptionForSeekOrigin() - { - throw new ArgumentException("The input seek mode is not valid.", "origin"); - } - /// /// Throws an when using a disposed instance. /// private static void ThrowObjectDisposedException() { - throw new ObjectDisposedException(nameof(memory), "The current stream has already been disposed"); + throw new ObjectDisposedException("source", "The current stream has already been disposed"); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.Validate.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.Validate.cs index 7f5cf07c29f..9cd0ff62936 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.Validate.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.Validate.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -8,9 +8,9 @@ namespace Microsoft.Toolkit.HighPerformance.Streams { /// - /// A implementation wrapping a or instance. + /// A factory class to produce instances. /// - internal partial class MemoryStream + internal static partial class MemoryStream { /// /// Validates the argument. @@ -18,7 +18,7 @@ internal partial class MemoryStream /// The new value being set. /// The maximum length of the target . [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ValidatePosition(long position, int length) + public static void ValidatePosition(long position, int length) { if ((ulong)position >= (ulong)length) { @@ -33,7 +33,7 @@ private static void ValidatePosition(long position, int length) /// The offset within . /// The number of elements to process within . [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ValidateBuffer(byte[]? buffer, int offset, int count) + public static void ValidateBuffer(byte[]? buffer, int offset, int count) { if (buffer is null) { @@ -57,24 +57,24 @@ private static void ValidateBuffer(byte[]? buffer, int offset, int count) } /// - /// Validates the property. + /// Validates the property. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ValidateCanWrite() + public static void ValidateCanWrite(bool canWrite) { - if (!CanWrite) + if (!canWrite) { ThrowNotSupportedExceptionForCanWrite(); } } /// - /// Validates that the current instance hasn't been disposed. + /// Validates that a given instance hasn't been disposed. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ValidateDisposed() + public static void ValidateDisposed(bool disposed) { - if (this.disposed) + if (disposed) { ThrowObjectDisposedException(); } diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs index e8682014721..cd2908bd881 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs @@ -1,315 +1,95 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.Diagnostics.Contracts; using System.IO; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; namespace Microsoft.Toolkit.HighPerformance.Streams { /// - /// A implementation wrapping a or instance. + /// A factory class to produce instances. /// - /// - /// This type is not marked as so that it can be inherited by - /// , which adds the support for - /// the wrapped buffer. We're not worried about the performance penalty here caused by the JIT - /// not being able to resolve the instruction, as this type is - /// only exposed as a anyway, so the generated code would be the same. - /// - internal partial class MemoryStream : Stream + internal static partial class MemoryStream { /// - /// Indicates whether was actually a instance. + /// Creates a new from the input of instance. /// - private readonly bool isReadOnly; - - /// - /// The instance currently in use. - /// - private Memory memory; - - /// - /// The current position within . - /// - private int position; - - /// - /// Indicates whether or not the current instance has been disposed - /// - private bool disposed; - - /// - /// Initializes a new instance of the class. - /// - /// The input instance to use. - public MemoryStream(Memory memory) - { - this.memory = memory; - this.position = 0; - this.isReadOnly = false; - } - - /// - /// Initializes a new instance of the class. - /// - /// The input instance to use. - public MemoryStream(ReadOnlyMemory memory) - { - this.memory = MemoryMarshal.AsMemory(memory); - this.position = 0; - this.isReadOnly = true; - } - - /// - public sealed override bool CanRead - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => !this.disposed; - } - - /// - public sealed override bool CanSeek - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => !this.disposed; - } - - /// - public sealed override bool CanWrite - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => !this.isReadOnly && !this.disposed; - } - - /// - public sealed override long Length - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - ValidateDisposed(); - - return this.memory.Length; - } - } - - /// - public sealed override long Position - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - ValidateDisposed(); - - return this.position; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - ValidateDisposed(); - ValidatePosition(value, this.memory.Length); - - this.position = unchecked((int)value); - } - } - - /// - public sealed override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + /// The input instance. + /// Indicates whether or not can be written to. + /// A wrapping the underlying data for . + /// Thrown when has an invalid data store. + [Pure] + public static Stream Create(ReadOnlyMemory memory, bool isReadOnly) { - if (cancellationToken.IsCancellationRequested) + if (memory.IsEmpty) { - return Task.FromCanceled(cancellationToken); + // Return an empty stream if the memory was empty + return new MemoryStream(ArrayOwner.Empty, isReadOnly); } - try + if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) { - CopyTo(destination, bufferSize); + var arraySpanSource = new ArrayOwner(segment.Array!, segment.Offset, segment.Count); - return Task.CompletedTask; - } - catch (OperationCanceledException e) - { - return Task.FromCanceled(e.CancellationToken); - } - catch (Exception e) - { - return Task.FromException(e); + return new MemoryStream(arraySpanSource, isReadOnly); } - } - - /// - public sealed override void Flush() - { - } - /// - public sealed override Task FlushAsync(CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) + if (MemoryMarshal.TryGetMemoryManager>(memory, out var memoryManager, out int start, out int length)) { - return Task.FromCanceled(cancellationToken); - } - - return Task.CompletedTask; - } + MemoryManagerOwner memoryManagerSpanSource = new MemoryManagerOwner(memoryManager, start, length); - /// - public sealed override Task ReadAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); + return new MemoryStream(memoryManagerSpanSource, isReadOnly); } - try - { - int result = Read(buffer, offset, count); - - return Task.FromResult(result); - } - catch (OperationCanceledException e) - { - return Task.FromCanceled(e.CancellationToken); - } - catch (Exception e) - { - return Task.FromException(e); - } + return ThrowNotSupportedExceptionForInvalidMemory(); } - /// - public sealed override Task WriteAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken) + /// + /// Creates a new from the input of instance. + /// + /// The input instance. + /// A wrapping the underlying data for . + /// Thrown when has an invalid data store. + [Pure] + public static Stream Create(IMemoryOwner memoryOwner) { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } + Memory memory = memoryOwner.Memory; - try + if (memory.IsEmpty) { - Write(buffer, offset, count); - - return Task.CompletedTask; - } - catch (OperationCanceledException e) - { - return Task.FromCanceled(e.CancellationToken); + // Return an empty stream if the memory was empty + return new IMemoryOwnerStream(ArrayOwner.Empty, memoryOwner); } - catch (Exception e) - { - return Task.FromException(e); - } - } - - /// - public sealed override long Seek(long offset, SeekOrigin origin) - { - ValidateDisposed(); - long index = origin switch + if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) { - SeekOrigin.Begin => offset, - SeekOrigin.Current => this.position + offset, - SeekOrigin.End => this.memory.Length + offset, - _ => ThrowArgumentExceptionForSeekOrigin() - }; - - ValidatePosition(index, this.memory.Length); - - this.position = unchecked((int)index); - - return index; - } - - /// - public sealed override void SetLength(long value) - { - ThrowNotSupportedExceptionForSetLength(); - } - - /// - public sealed override int Read(byte[]? buffer, int offset, int count) - { - ValidateDisposed(); - ValidateBuffer(buffer, offset, count); - - int - bytesAvailable = this.memory.Length - this.position, - bytesCopied = Math.Min(bytesAvailable, count); - - Span - source = this.memory.Span.Slice(this.position, bytesCopied), - destination = buffer.AsSpan(offset, bytesCopied); - - source.CopyTo(destination); - - this.position += bytesCopied; + var arraySpanSource = new ArrayOwner(segment.Array!, segment.Offset, segment.Count); - return bytesCopied; - } - - /// - public sealed override int ReadByte() - { - ValidateDisposed(); - - if (this.position == this.memory.Length) - { - return -1; + return new IMemoryOwnerStream(arraySpanSource, memoryOwner); } - return this.memory.Span[this.position++]; - } - - /// - public sealed override void Write(byte[]? buffer, int offset, int count) - { - ValidateDisposed(); - ValidateCanWrite(); - ValidateBuffer(buffer, offset, count); - - Span - source = buffer.AsSpan(offset, count), - destination = this.memory.Span.Slice(this.position); - - if (!source.TryCopyTo(destination)) + if (MemoryMarshal.TryGetMemoryManager>(memory, out var memoryManager, out int start, out int length)) { - ThrowArgumentExceptionForEndOfStreamOnWrite(); - } - - this.position += source.Length; - } - - /// - public sealed override void WriteByte(byte value) - { - ValidateDisposed(); - ValidateCanWrite(); + MemoryManagerOwner memoryManagerSpanSource = new MemoryManagerOwner(memoryManager, start, length); - if (this.position == this.memory.Length) - { - ThrowArgumentExceptionForEndOfStreamOnWrite(); + return new IMemoryOwnerStream(memoryManagerSpanSource, memoryOwner); } - this.memory.Span[this.position++] = value; + return ThrowNotSupportedExceptionForInvalidMemory(); } - /// - protected override void Dispose(bool disposing) + /// + /// Throws a when a given + /// or instance has an unsupported backing store. + /// + /// Nothing, this method always throws. + private static Stream ThrowNotSupportedExceptionForInvalidMemory() { - if (this.disposed) - { - return; - } - - this.disposed = true; - this.memory = default; + throw new ArgumentException("The input instance doesn't have a valid underlying data store."); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.NETStandard21.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream{TSource}.Memory.cs similarity index 80% rename from Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.NETStandard21.cs rename to Microsoft.Toolkit.HighPerformance/Streams/MemoryStream{TSource}.Memory.cs index e576b94d8bf..e8c9c6fd76e 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.NETStandard21.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream{TSource}.Memory.cs @@ -11,17 +11,15 @@ namespace Microsoft.Toolkit.HighPerformance.Streams { - /// - /// A implementation wrapping a or instance. - /// - internal partial class MemoryStream + /// + internal partial class MemoryStream { /// public sealed override void CopyTo(Stream destination, int bufferSize) { - ValidateDisposed(); + MemoryStream.ValidateDisposed(this.disposed); - Span source = this.memory.Span.Slice(this.position); + Span source = this.source.Span.Slice(this.position); this.position += source.Length; @@ -79,13 +77,13 @@ public sealed override ValueTask WriteAsync(ReadOnlyMemory buffer, Cancell /// public sealed override int Read(Span buffer) { - ValidateDisposed(); + MemoryStream.ValidateDisposed(this.disposed); int - bytesAvailable = this.memory.Length - this.position, + bytesAvailable = this.source.Length - this.position, bytesCopied = Math.Min(bytesAvailable, buffer.Length); - Span source = this.memory.Span.Slice(this.position, bytesCopied); + Span source = this.source.Span.Slice(this.position, bytesCopied); source.CopyTo(buffer); @@ -97,14 +95,14 @@ public sealed override int Read(Span buffer) /// public sealed override void Write(ReadOnlySpan buffer) { - ValidateDisposed(); - ValidateCanWrite(); + MemoryStream.ValidateDisposed(this.disposed); + MemoryStream.ValidateCanWrite(CanWrite); - Span destination = this.memory.Span.Slice(this.position); + Span destination = this.source.Span.Slice(this.position); if (!buffer.TryCopyTo(destination)) { - ThrowArgumentExceptionForEndOfStreamOnWrite(); + MemoryStream.ThrowArgumentExceptionForEndOfStreamOnWrite(); } this.position += buffer.Length; diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream{TSource}.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream{TSource}.cs new file mode 100644 index 00000000000..b0dcd8bc908 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream{TSource}.cs @@ -0,0 +1,305 @@ +// 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.IO; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Toolkit.HighPerformance.Streams +{ + /// + /// A implementation wrapping a or instance. + /// + /// The type of source to use for the underlying data. + /// + /// This type is not marked as so that it can be inherited by + /// , which adds the support for + /// the wrapped buffer. We're not worried about the performance penalty here caused by the JIT + /// not being able to resolve the instruction, as this type is + /// only exposed as a anyway, so the generated code would be the same. + /// + internal partial class MemoryStream : Stream + where TSource : struct, ISpanOwner + { + /// + /// Indicates whether can be written to. + /// + private readonly bool isReadOnly; + + /// + /// The instance currently in use. + /// + private TSource source; + + /// + /// The current position within . + /// + private int position; + + /// + /// Indicates whether or not the current instance has been disposed + /// + private bool disposed; + + /// + /// Initializes a new instance of the class. + /// + /// The input instance to use. + /// Indicates whether can be written to. + public MemoryStream(TSource source, bool isReadOnly) + { + this.source = source; + this.isReadOnly = isReadOnly; + } + + /// + public sealed override bool CanRead + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => !this.disposed; + } + + /// + public sealed override bool CanSeek + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => !this.disposed; + } + + /// + public sealed override bool CanWrite + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => !this.isReadOnly && !this.disposed; + } + + /// + public sealed override long Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + MemoryStream.ValidateDisposed(this.disposed); + + return this.source.Length; + } + } + + /// + public sealed override long Position + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + MemoryStream.ValidateDisposed(this.disposed); + + return this.position; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + MemoryStream.ValidateDisposed(this.disposed); + MemoryStream.ValidatePosition(value, this.source.Length); + + this.position = unchecked((int)value); + } + } + + /// + public sealed override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + try + { + CopyTo(destination, bufferSize); + + return Task.CompletedTask; + } + catch (OperationCanceledException e) + { + return Task.FromCanceled(e.CancellationToken); + } + catch (Exception e) + { + return Task.FromException(e); + } + } + + /// + public sealed override void Flush() + { + } + + /// + public sealed override Task FlushAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + return Task.CompletedTask; + } + + /// + public sealed override Task ReadAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + try + { + int result = Read(buffer, offset, count); + + return Task.FromResult(result); + } + catch (OperationCanceledException e) + { + return Task.FromCanceled(e.CancellationToken); + } + catch (Exception e) + { + return Task.FromException(e); + } + } + + /// + public sealed 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); + } + } + + /// + public sealed override long Seek(long offset, SeekOrigin origin) + { + MemoryStream.ValidateDisposed(this.disposed); + + long index = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => this.position + offset, + SeekOrigin.End => this.source.Length + offset, + _ => MemoryStream.ThrowArgumentExceptionForSeekOrigin() + }; + + MemoryStream.ValidatePosition(index, this.source.Length); + + this.position = unchecked((int)index); + + return index; + } + + /// + public sealed override void SetLength(long value) + { + MemoryStream.ThrowNotSupportedExceptionForSetLength(); + } + + /// + public sealed override int Read(byte[]? buffer, int offset, int count) + { + MemoryStream.ValidateDisposed(this.disposed); + MemoryStream.ValidateBuffer(buffer, offset, count); + + int + bytesAvailable = this.source.Length - this.position, + bytesCopied = Math.Min(bytesAvailable, count); + + Span + source = this.source.Span.Slice(this.position, bytesCopied), + destination = buffer.AsSpan(offset, bytesCopied); + + source.CopyTo(destination); + + this.position += bytesCopied; + + return bytesCopied; + } + + /// + public sealed override int ReadByte() + { + MemoryStream.ValidateDisposed(this.disposed); + + if (this.position == this.source.Length) + { + return -1; + } + + return this.source.Span[this.position++]; + } + + /// + public sealed override void Write(byte[]? buffer, int offset, int count) + { + MemoryStream.ValidateDisposed(this.disposed); + MemoryStream.ValidateCanWrite(CanWrite); + MemoryStream.ValidateBuffer(buffer, offset, count); + + Span + source = buffer.AsSpan(offset, count), + destination = this.source.Span.Slice(this.position); + + if (!source.TryCopyTo(destination)) + { + MemoryStream.ThrowArgumentExceptionForEndOfStreamOnWrite(); + } + + this.position += source.Length; + } + + /// + public sealed override void WriteByte(byte value) + { + MemoryStream.ValidateDisposed(this.disposed); + MemoryStream.ValidateCanWrite(CanWrite); + + if (this.position == this.source.Length) + { + MemoryStream.ThrowArgumentExceptionForEndOfStreamOnWrite(); + } + + this.source.Span[this.position++] = value; + } + + /// + protected override void Dispose(bool disposing) + { + if (this.disposed) + { + return; + } + + this.disposed = true; + this.source = default; + } + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs b/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs new file mode 100644 index 00000000000..8749ab7682e --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs @@ -0,0 +1,79 @@ +// 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.Runtime.CompilerServices; +#if SPAN_RUNTIME_SUPPORT +using System.Runtime.InteropServices; +using Microsoft.Toolkit.HighPerformance.Extensions; +#endif + +namespace Microsoft.Toolkit.HighPerformance.Streams +{ + /// + /// An implementation wrapping an array. + /// + internal readonly struct ArrayOwner : ISpanOwner + { + /// + /// The wrapped array. + /// + private readonly byte[] array; + + /// + /// The starting offset within . + /// + private readonly int offset; + + /// + /// The usable length within . + /// + private readonly int length; + + /// + /// Initializes a new instance of the struct. + /// + /// The wrapped array. + /// The starting offset within . + /// The usable length within . + public ArrayOwner(byte[] array, int offset, int length) + { + this.array = array; + this.offset = offset; + this.length = length; + } + + /// + /// Gets an empty instance. + /// + public static ArrayOwner Empty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new ArrayOwner(Array.Empty(), 0, 0); + } + + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.length; + } + + /// + public Span Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { +#if SPAN_RUNTIME_SUPPORT + ref byte r0 = ref this.array.DangerousGetReferenceAt(this.offset); + + return MemoryMarshal.CreateSpan(ref r0, this.length); +#else + return this.array.AsSpan(this.offset, this.length); +#endif + } + } + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs b/Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs new file mode 100644 index 00000000000..22fd2a77a18 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs @@ -0,0 +1,24 @@ +// 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; + +namespace Microsoft.Toolkit.HighPerformance.Streams +{ + /// + /// An interface for types acting as sources for instances. + /// + internal interface ISpanOwner + { + /// + /// Gets the length of the underlying memory area. + /// + int Length { get; } + + /// + /// Gets a instance wrapping the underlying memory area. + /// + Span Span { get; } + } +} diff --git a/Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs b/Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs new file mode 100644 index 00000000000..f42eb7a1ac5 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs @@ -0,0 +1,65 @@ +// 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.Runtime.CompilerServices; + +namespace Microsoft.Toolkit.HighPerformance.Streams +{ + /// + /// An implementation wrapping a of instance. + /// + internal readonly struct MemoryManagerOwner : ISpanOwner + { + /// + /// The wrapped instance. + /// + private readonly MemoryManager memoryManager; + + /// + /// The starting offset within . + /// + private readonly int offset; + + /// + /// The usable length within . + /// + private readonly int length; + + /// + /// Initializes a new instance of the struct. + /// + /// The wrapped instance. + /// The starting offset within . + /// The usable length within . + public MemoryManagerOwner(MemoryManager memoryManager, int offset, int length) + { + this.memoryManager = memoryManager; + this.offset = offset; + this.length = length; + } + + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.length; + } + + /// + public Span Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + // We can't use the same trick we use for arrays to optimize the creation of + // the offset span, as otherwise a bugged MemoryManager instance returning + // a span of an incorrect size could cause an access violation. Instead, we just + // get the span and then slice it, which will validate both offset and length. + return this.memoryManager.GetSpan().Slice(this.offset, this.length); + } + } + } +}