MemoryOwner.cs
1 #nullable enable 2 using System; 3 using System.Buffers; 4 using System.Diagnostics.CodeAnalysis; 5 using System.Runtime.CompilerServices; 6 using System.Runtime.InteropServices; 7 using System.Threading; 8 9 namespace Ryujinx.Common.Memory 10 { 11 /// <summary> 12 /// An <see cref="IMemoryOwner{T}"/> implementation with an embedded length and fast <see cref="Span{T}"/> 13 /// accessor, with memory allocated from <seealso cref="ArrayPool{T}.Shared"/>. 14 /// </summary> 15 /// <typeparam name="T">The type of item to store.</typeparam> 16 public sealed class MemoryOwner<T> : IMemoryOwner<T> 17 { 18 private readonly int _length; 19 private T[]? _array; 20 21 /// <summary> 22 /// Initializes a new instance of the <see cref="MemoryOwner{T}"/> class with the specified parameters. 23 /// </summary> 24 /// <param name="length">The length of the new memory buffer to use</param> 25 private MemoryOwner(int length) 26 { 27 _length = length; 28 _array = ArrayPool<T>.Shared.Rent(length); 29 } 30 31 /// <summary> 32 /// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified length. 33 /// </summary> 34 /// <param name="length">The length of the new memory buffer to use</param> 35 /// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length</returns> 36 /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception> 37 [MethodImpl(MethodImplOptions.AggressiveInlining)] 38 public static MemoryOwner<T> Rent(int length) => new(length); 39 40 /// <summary> 41 /// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified length and the content cleared. 42 /// </summary> 43 /// <param name="length">The length of the new memory buffer to use</param> 44 /// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length and the content cleared</returns> 45 /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception> 46 [MethodImpl(MethodImplOptions.AggressiveInlining)] 47 public static MemoryOwner<T> RentCleared(int length) 48 { 49 MemoryOwner<T> result = new(length); 50 51 result._array.AsSpan(0, length).Clear(); 52 53 return result; 54 } 55 56 /// <summary> 57 /// Creates a new <see cref="MemoryOwner{T}"/> instance with the content copied from the specified buffer. 58 /// </summary> 59 /// <param name="buffer">The buffer to copy</param> 60 /// <returns>A <see cref="MemoryOwner{T}"/> instance with the same length and content as <paramref name="buffer"/></returns> 61 public static MemoryOwner<T> RentCopy(ReadOnlySpan<T> buffer) 62 { 63 MemoryOwner<T> result = new(buffer.Length); 64 65 buffer.CopyTo(result._array); 66 67 return result; 68 } 69 70 /// <summary> 71 /// Gets the number of items in the current instance. 72 /// </summary> 73 public int Length 74 { 75 [MethodImpl(MethodImplOptions.AggressiveInlining)] 76 get => _length; 77 } 78 79 /// <inheritdoc/> 80 public Memory<T> Memory 81 { 82 [MethodImpl(MethodImplOptions.AggressiveInlining)] 83 get 84 { 85 T[]? array = _array; 86 87 if (array is null) 88 { 89 ThrowObjectDisposedException(); 90 } 91 92 return new(array, 0, _length); 93 } 94 } 95 96 /// <summary> 97 /// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance. 98 /// </summary> 99 /// <remarks> 100 /// Uses a trick made possible by the .NET 6+ runtime array layout. 101 /// </remarks> 102 public Span<T> Span 103 { 104 [MethodImpl(MethodImplOptions.AggressiveInlining)] 105 get 106 { 107 T[]? array = _array; 108 109 if (array is null) 110 { 111 ThrowObjectDisposedException(); 112 } 113 114 ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(array); 115 116 return MemoryMarshal.CreateSpan(ref firstElementRef, _length); 117 } 118 } 119 120 /// <inheritdoc/> 121 public void Dispose() 122 { 123 T[]? array = Interlocked.Exchange(ref _array, null); 124 125 if (array is not null) 126 { 127 ArrayPool<T>.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences<T>()); 128 } 129 } 130 131 /// <summary> 132 /// Throws an <see cref="ObjectDisposedException"/> when <see cref="_array"/> is <see langword="null"/>. 133 /// </summary> 134 [DoesNotReturn] 135 private static void ThrowObjectDisposedException() 136 { 137 throw new ObjectDisposedException(nameof(MemoryOwner<T>), "The buffer has already been disposed."); 138 } 139 } 140 }