#nullable enable using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; namespace Ryujinx.Common.Memory { /// /// An implementation with an embedded length and fast /// accessor, with memory allocated from . /// /// The type of item to store. public sealed class MemoryOwner : IMemoryOwner { private static class ArrayPooling { public class Holder(T[]? array = null) : IComparable, IComparable { public int SkipCount; public readonly T[]? Array = array; public int CompareTo(Holder? other) { return Array!.Length.CompareTo(other!.Array!.Length); } public int CompareTo(int other) { int self = Array!.Length; if (self < other) { SkipCount++; return -1; } if (self > other * 4) { return 1; } return 0; } } // ReSharper disable once StaticMemberInGenericType private static int _maxCacheCount = 50; private const int MaxSkipCount = 50; static readonly List _pool = new(); // ReSharper disable once StaticMemberInGenericType static readonly Lock _lock = new(); private static int BinarySearch(List list, int size) { int min = 0; int max = list.Count-1; while (min <= max) { int mid = (min + max) / 2; int comparison = list[mid].CompareTo(size); if (comparison == 0) { return mid; } if (comparison < 0) { min = mid+1; } else { max = mid-1; } } return ~min; } public static T[] Get(int minimumSize) { lock (_lock) { int index = BinarySearch(_pool, minimumSize); if (index >= 0) { Holder holder = _pool[index]; _pool.Remove(holder); return holder.Array!; } return new T[minimumSize]; } } public static void Return(T[] array) { lock (_lock) { Holder holder = new(array); int i = _pool.BinarySearch(holder); if (i < 0) { _pool.Insert(~i, holder); } if (_pool.Count >= _maxCacheCount) { for (int index = 0; index < _pool.Count; index++) { Holder h = _pool[index]; if (h.SkipCount >= MaxSkipCount) { _pool.Remove(h); index--; } } _maxCacheCount = _pool.Count * 2; } } } } private readonly int _length; private T[]? _array; /// /// Initializes a new instance of the class with the specified parameters. /// /// The length of the new memory buffer to use private MemoryOwner(int length) { _length = length; _array = ArrayPooling.Get(length); } /// /// Creates a new instance with the specified length. /// /// The length of the new memory buffer to use /// A instance of the requested length /// Thrown when is not valid [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MemoryOwner Rent(int length) => new(length); /// /// Creates a new instance with the specified length and the content cleared. /// /// The length of the new memory buffer to use /// A instance of the requested length and the content cleared /// Thrown when is not valid [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MemoryOwner RentCleared(int length) { MemoryOwner result = new(length); result._array.AsSpan(0, length).Clear(); return result; } /// /// Creates a new instance with the content copied from the specified buffer. /// /// The buffer to copy /// A instance with the same length and content as public static MemoryOwner RentCopy(ReadOnlySpan buffer) { MemoryOwner result = new(buffer.Length); buffer.CopyTo(result._array); return result; } /// /// Gets the number of items in the current instance. /// public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _length; } /// public Memory Memory { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { T[]? array = _array; if (array is null) { ThrowObjectDisposedException(); } return new(array, 0, _length); } } /// /// Gets a wrapping the memory belonging to the current instance. /// /// /// Uses a trick made possible by the .NET 6+ runtime array layout. /// public Span 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); } } /// public void Dispose() { T[]? array = Interlocked.Exchange(ref _array, null); if (array is not null) { ArrayPooling.Return(array); } } /// /// Throws an when is . /// [DoesNotReturn] private static void ThrowObjectDisposedException() { throw new ObjectDisposedException(nameof(MemoryOwner), "The buffer has already been disposed."); } } }