diff options
Diffstat (limited to 'src/Ryujinx.Common')
| -rw-r--r-- | src/Ryujinx.Common/Memory/MemoryOwner.cs | 140 | ||||
| -rw-r--r-- | src/Ryujinx.Common/Memory/SpanOwner.cs | 114 |
2 files changed, 254 insertions, 0 deletions
diff --git a/src/Ryujinx.Common/Memory/MemoryOwner.cs b/src/Ryujinx.Common/Memory/MemoryOwner.cs new file mode 100644 index 00000000..5e567ab8 --- /dev/null +++ b/src/Ryujinx.Common/Memory/MemoryOwner.cs @@ -0,0 +1,140 @@ +#nullable enable +using System; +using System.Buffers; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Common.Memory +{ + /// <summary> + /// An <see cref="IMemoryOwner{T}"/> implementation with an embedded length and fast <see cref="Span{T}"/> + /// accessor, with memory allocated from <seealso cref="ArrayPool{T}.Shared"/>. + /// </summary> + /// <typeparam name="T">The type of item to store.</typeparam> + public sealed class MemoryOwner<T> : IMemoryOwner<T> + { + private readonly int _length; + private T[]? _array; + + /// <summary> + /// Initializes a new instance of the <see cref="MemoryOwner{T}"/> class with the specified parameters. + /// </summary> + /// <param name="length">The length of the new memory buffer to use</param> + private MemoryOwner(int length) + { + _length = length; + _array = ArrayPool<T>.Shared.Rent(length); + } + + /// <summary> + /// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified length. + /// </summary> + /// <param name="length">The length of the new memory buffer to use</param> + /// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length</returns> + /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemoryOwner<T> Rent(int length) => new(length); + + /// <summary> + /// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified length and the content cleared. + /// </summary> + /// <param name="length">The length of the new memory buffer to use</param> + /// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length and the content cleared</returns> + /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemoryOwner<T> RentCleared(int length) + { + MemoryOwner<T> result = new(length); + + result._array.AsSpan(0, length).Clear(); + + return result; + } + + /// <summary> + /// Creates a new <see cref="MemoryOwner{T}"/> instance with the content copied from the specified buffer. + /// </summary> + /// <param name="buffer">The buffer to copy</param> + /// <returns>A <see cref="MemoryOwner{T}"/> instance with the same length and content as <paramref name="buffer"/></returns> + public static MemoryOwner<T> RentCopy(ReadOnlySpan<T> buffer) + { + MemoryOwner<T> result = new(buffer.Length); + + buffer.CopyTo(result._array); + + return result; + } + + /// <summary> + /// Gets the number of items in the current instance. + /// </summary> + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _length; + } + + /// <inheritdoc/> + public Memory<T> Memory + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + T[]? array = _array; + + if (array is null) + { + ThrowObjectDisposedException(); + } + + return new(array, 0, _length); + } + } + + /// <summary> + /// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance. + /// </summary> + /// <remarks> + /// Uses a trick made possible by the .NET 6+ runtime array layout. + /// </remarks> + public Span<T> Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + T[]? array = _array; + + if (array is null) + { + ThrowObjectDisposedException(); + } + + ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(array); + + return MemoryMarshal.CreateSpan(ref firstElementRef, _length); + } + } + + /// <inheritdoc/> + public void Dispose() + { + T[]? array = Interlocked.Exchange(ref _array, null); + + if (array is not null) + { + ArrayPool<T>.Shared.Return(array); + } + } + + /// <summary> + /// Throws an <see cref="ObjectDisposedException"/> when <see cref="_array"/> is <see langword="null"/>. + /// </summary> + [DoesNotReturn] + private static void ThrowObjectDisposedException() + { + throw new ObjectDisposedException(nameof(MemoryOwner<T>), "The buffer has already been disposed."); + } + } +} diff --git a/src/Ryujinx.Common/Memory/SpanOwner.cs b/src/Ryujinx.Common/Memory/SpanOwner.cs new file mode 100644 index 00000000..a4b4adf3 --- /dev/null +++ b/src/Ryujinx.Common/Memory/SpanOwner.cs @@ -0,0 +1,114 @@ +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.Memory +{ + /// <summary> + /// A stack-only type that rents a buffer of a specified length from <seealso cref="ArrayPool{T}.Shared"/>. + /// It does not implement <see cref="IDisposable"/> to avoid being boxed, but should still be disposed. This + /// is easy since C# 8, which allows use of C# `using` constructs on any type that has a public Dispose() method. + /// To keep this type simple, fast, and read-only, it does not check or guard against multiple disposals. + /// For all these reasons, all usage should be with a `using` block or statement. + /// </summary> + /// <typeparam name="T">The type of item to store.</typeparam> + public readonly ref struct SpanOwner<T> + { + private readonly int _length; + private readonly T[] _array; + + /// <summary> + /// Initializes a new instance of the <see cref="SpanOwner{T}"/> struct with the specified parameters. + /// </summary> + /// <param name="length">The length of the new memory buffer to use</param> + private SpanOwner(int length) + { + _length = length; + _array = ArrayPool<T>.Shared.Rent(length); + } + + /// <summary> + /// Gets an empty <see cref="SpanOwner{T}"/> instance. + /// </summary> + public static SpanOwner<T> Empty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(0); + } + + /// <summary> + /// Creates a new <see cref="SpanOwner{T}"/> instance with the specified length. + /// </summary> + /// <param name="length">The length of the new memory buffer to use</param> + /// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length</returns> + /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SpanOwner<T> Rent(int length) => new(length); + + /// <summary> + /// Creates a new <see cref="SpanOwner{T}"/> instance with the length and the content cleared. + /// </summary> + /// <param name="length">The length of the new memory buffer to use</param> + /// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length and the content cleared</returns> + /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SpanOwner<T> RentCleared(int length) + { + SpanOwner<T> result = new(length); + + result._array.AsSpan(0, length).Clear(); + + return result; + } + + /// <summary> + /// Creates a new <see cref="SpanOwner{T}"/> instance with the content copied from the specified buffer. + /// </summary> + /// <param name="buffer">The buffer to copy</param> + /// <returns>A <see cref="SpanOwner{T}"/> instance with the same length and content as <paramref name="buffer"/></returns> + public static SpanOwner<T> RentCopy(ReadOnlySpan<T> buffer) + { + SpanOwner<T> result = new(buffer.Length); + + buffer.CopyTo(result._array); + + return result; + } + + /// <summary> + /// Gets the number of items in the current instance + /// </summary> + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _length; + } + + /// <summary> + /// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance. + /// </summary> + /// <remarks> + /// Uses a trick made possible by the .NET 6+ runtime array layout. + /// </remarks> + public Span<T> Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(_array); + + return MemoryMarshal.CreateSpan(ref firstElementRef, _length); + } + } + + /// <summary> + /// Implements the duck-typed <see cref="IDisposable.Dispose"/> method. + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + ArrayPool<T>.Shared.Return(_array); + } + } +} |
