#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.");
}
}
}