Some checks failed
Build Android / Game (Android, Release ARM64) (push) Has been cancelled
Build iOS / Game (iOS, Release ARM64) (push) Has been cancelled
Build Linux / Editor (Linux, Development x64) (push) Has been cancelled
Build Linux / Game (Linux, Release x64) (push) Has been cancelled
Build macOS / Editor (Mac, Development ARM64) (push) Has been cancelled
Build macOS / Game (Mac, Release ARM64) (push) Has been cancelled
Build Windows / Editor (Windows, Development x64) (push) Has been cancelled
Build Windows / Game (Windows, Release x64) (push) Has been cancelled
Cooker / Cook (Mac) (push) Has been cancelled
Tests / Tests (Linux) (push) Has been cancelled
Tests / Tests (Windows) (push) Has been cancelled
977 lines
38 KiB
C#
977 lines
38 KiB
C#
// Copyright (c) Wojciech Figat. All rights reserved.
|
|
#pragma warning disable CS0162
|
|
#define USE_CONCURRENT_DICT
|
|
#define USE_GCHANDLE
|
|
//#define TRACK_HANDLES
|
|
|
|
#if USE_NETCORE
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
using FlaxEngine.Assertions;
|
|
using System.Collections.Concurrent;
|
|
|
|
#pragma warning disable 1591
|
|
|
|
namespace FlaxEngine.Interop
|
|
{
|
|
/// <summary>
|
|
/// Wrapper for managed arrays which are passed to unmanaged code.
|
|
/// </summary>
|
|
#if FLAX_EDITOR
|
|
[HideInEditor]
|
|
#endif
|
|
public unsafe class ManagedArray
|
|
{
|
|
private ManagedHandle _managedHandle;
|
|
private IntPtr _unmanagedData;
|
|
private int _unmanagedAllocationSize;
|
|
private Type _arrayType;
|
|
private Type _elementType;
|
|
private int _elementSize;
|
|
private int _length;
|
|
|
|
[ThreadStatic] private static Dictionary<ManagedArray, ManagedHandle> pooledArrayHandles;
|
|
|
|
public static ManagedArray WrapNewArray(Array arr) => new ManagedArray(arr, arr.GetType());
|
|
|
|
public static ManagedArray WrapNewArray(Array arr, Type arrayType) => new ManagedArray(arr, arrayType);
|
|
|
|
/// <summary>
|
|
/// Returns an instance of ManagedArray from shared pool.
|
|
/// </summary>
|
|
/// <remarks>The resources must be released by calling FreePooled() instead of Free()-method.</remarks>
|
|
public static (ManagedHandle managedHandle, ManagedArray managedArray) WrapPooledArray(Array arr)
|
|
{
|
|
ManagedArray managedArray = ManagedArrayPool.Get();
|
|
managedArray.WrapArray(arr, arr.GetType());
|
|
|
|
if (pooledArrayHandles == null)
|
|
pooledArrayHandles = new();
|
|
if (!pooledArrayHandles.TryGetValue(managedArray, out ManagedHandle handle))
|
|
{
|
|
handle = ManagedHandle.Alloc(managedArray);
|
|
pooledArrayHandles.Add(managedArray, handle);
|
|
}
|
|
return (handle, managedArray);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an instance of ManagedArray from shared pool.
|
|
/// </summary>
|
|
/// <remarks>The resources must be released by calling FreePooled() instead of Free()-method.</remarks>
|
|
public static ManagedHandle WrapPooledArray(Array arr, Type arrayType)
|
|
{
|
|
ManagedArray managedArray = ManagedArrayPool.Get(arr.Length * NativeInterop.GetTypeSize(arr.GetType().GetElementType()));
|
|
managedArray.WrapArray(arr, arrayType);
|
|
|
|
if (pooledArrayHandles == null)
|
|
pooledArrayHandles = new();
|
|
if (!pooledArrayHandles.TryGetValue(managedArray, out ManagedHandle handle))
|
|
{
|
|
handle = ManagedHandle.Alloc(managedArray);
|
|
pooledArrayHandles.Add(managedArray, handle);
|
|
}
|
|
return handle;
|
|
}
|
|
|
|
internal static ManagedArray AllocateNewArray(int length, Type arrayType, Type elementType)
|
|
=> new ManagedArray((IntPtr)NativeInterop.NativeAlloc(length, NativeInterop.GetTypeSize(elementType)), length, arrayType, elementType);
|
|
|
|
internal static ManagedArray AllocateNewArray(IntPtr ptr, int length, Type arrayType, Type elementType)
|
|
=> new ManagedArray(ptr, length, arrayType, elementType);
|
|
|
|
/// <summary>
|
|
/// Returns an instance of ManagedArray from shared pool.
|
|
/// </summary>
|
|
/// <remarks>The resources must be released by calling FreePooled() instead of Free()-method. Do not release the returned ManagedHandle.</remarks>
|
|
public static (ManagedHandle managedHandle, ManagedArray managedArray) AllocatePooledArray<T>(int length) where T : unmanaged
|
|
{
|
|
ManagedArray managedArray = ManagedArrayPool.Get(length * Unsafe.SizeOf<T>());
|
|
managedArray.Allocate<T>(length);
|
|
|
|
if (pooledArrayHandles == null)
|
|
pooledArrayHandles = new();
|
|
if (!pooledArrayHandles.TryGetValue(managedArray, out ManagedHandle handle))
|
|
{
|
|
handle = ManagedHandle.Alloc(managedArray);
|
|
pooledArrayHandles.Add(managedArray, handle);
|
|
}
|
|
return (handle, managedArray);
|
|
}
|
|
|
|
public ManagedArray(Array arr, Type elementType) => WrapArray(arr, elementType);
|
|
|
|
internal void WrapArray(Array arr, Type arrayType)
|
|
{
|
|
if (_unmanagedData != IntPtr.Zero)
|
|
NativeInterop.NativeFree(_unmanagedData.ToPointer());
|
|
if (_managedHandle.IsAllocated)
|
|
_managedHandle.Free();
|
|
_managedHandle = ManagedHandle.Alloc(arr, GCHandleType.Pinned);
|
|
_unmanagedData = Marshal.UnsafeAddrOfPinnedArrayElement(arr, 0);
|
|
_unmanagedAllocationSize = 0;
|
|
_length = arr.Length;
|
|
_arrayType = arrayType;
|
|
_elementType = arr.GetType().GetElementType();
|
|
_elementSize = NativeInterop.GetTypeSize(_elementType);
|
|
}
|
|
|
|
internal void Allocate<T>(int length) where T : unmanaged
|
|
{
|
|
_length = length;
|
|
_arrayType = typeof(T[]);
|
|
_elementType = typeof(T);
|
|
_elementSize = Unsafe.SizeOf<T>();
|
|
|
|
// Try to reuse existing allocated buffer
|
|
if (length * _elementSize > _unmanagedAllocationSize)
|
|
{
|
|
if (_unmanagedAllocationSize > 0)
|
|
NativeInterop.NativeFree(_unmanagedData.ToPointer());
|
|
_unmanagedData = (IntPtr)NativeInterop.NativeAlloc(length, _elementSize);
|
|
_unmanagedAllocationSize = _elementSize * length;
|
|
}
|
|
}
|
|
|
|
private ManagedArray()
|
|
{
|
|
}
|
|
|
|
private ManagedArray(IntPtr ptr, int length, Type arrayType, Type elementType)
|
|
{
|
|
Assert.IsTrue(arrayType.IsArray);
|
|
_elementType = elementType;
|
|
_elementSize = NativeInterop.GetTypeSize(elementType);
|
|
_unmanagedData = ptr;
|
|
_unmanagedAllocationSize = _elementSize * length;
|
|
_length = length;
|
|
_arrayType = arrayType;
|
|
}
|
|
|
|
~ManagedArray()
|
|
{
|
|
if (_unmanagedData != IntPtr.Zero)
|
|
Free();
|
|
}
|
|
|
|
public void Free()
|
|
{
|
|
GC.SuppressFinalize(this);
|
|
if (_managedHandle.IsAllocated)
|
|
{
|
|
_managedHandle.Free();
|
|
_unmanagedData = IntPtr.Zero;
|
|
}
|
|
if (_unmanagedData != IntPtr.Zero)
|
|
{
|
|
NativeInterop.NativeFree(_unmanagedData.ToPointer());
|
|
_unmanagedData = IntPtr.Zero;
|
|
_unmanagedAllocationSize = 0;
|
|
}
|
|
}
|
|
|
|
public void FreePooled()
|
|
{
|
|
if (_managedHandle.IsAllocated)
|
|
{
|
|
_managedHandle.Free();
|
|
_unmanagedData = IntPtr.Zero;
|
|
}
|
|
_arrayType = _elementType = null;
|
|
ManagedArrayPool.Put(this);
|
|
}
|
|
|
|
internal IntPtr Pointer => _unmanagedData;
|
|
|
|
internal int Length => _length;
|
|
|
|
internal int ElementSize => _elementSize;
|
|
|
|
internal Type ElementType => _elementType;
|
|
|
|
internal Type ArrayType => _arrayType;
|
|
|
|
public Span<T> ToSpan<T>() where T : struct => new Span<T>(_unmanagedData.ToPointer(), _length);
|
|
|
|
public T[] ToArray<T>() where T : struct => new Span<T>(_unmanagedData.ToPointer(), _length).ToArray();
|
|
|
|
public Array ToArray() => ArrayCast.ToArray(new Span<byte>(_unmanagedData.ToPointer(), _length * _elementSize), _elementType);
|
|
|
|
/// <summary>
|
|
/// Creates an Array of the specified type from span of bytes.
|
|
/// </summary>
|
|
private static class ArrayCast
|
|
{
|
|
delegate Array GetDelegate(Span<byte> span);
|
|
|
|
[ThreadStatic]
|
|
private static Dictionary<Type, GetDelegate> delegates;
|
|
|
|
internal static Array ToArray(Span<byte> span, Type type)
|
|
{
|
|
if (delegates == null)
|
|
delegates = new();
|
|
if (!delegates.TryGetValue(type, out var deleg))
|
|
{
|
|
deleg = typeof(ArrayCastInternal<>).MakeGenericType(type).GetMethod(nameof(ArrayCastInternal<int>.ToArray), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<GetDelegate>();
|
|
delegates.Add(type, deleg);
|
|
}
|
|
return deleg(span);
|
|
}
|
|
|
|
private static class ArrayCastInternal<T> where T : struct
|
|
{
|
|
internal static Array ToArray(Span<byte> span)
|
|
{
|
|
return MemoryMarshal.Cast<byte, T>(span).ToArray();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides a pool of pre-allocated ManagedArray that can be re-used.
|
|
/// </summary>
|
|
private static class ManagedArrayPool
|
|
{
|
|
[ThreadStatic]
|
|
private static List<(bool inUse, ManagedArray array)> pool;
|
|
|
|
/// <summary>
|
|
/// Borrows an array from the pool.
|
|
/// </summary>
|
|
/// <param name="minimumSize">Minimum size in bytes for the borrowed array. With value of 0, the returned array allocation is always zero.</param>
|
|
/// <remarks>The returned array size may be smaller than the requested minimumSize.</remarks>
|
|
internal static ManagedArray Get(int minimumSize = 0)
|
|
{
|
|
if (pool == null)
|
|
pool = new();
|
|
|
|
int smallest = -1;
|
|
int smallestSize = int.MaxValue;
|
|
var poolSpan = CollectionsMarshal.AsSpan(pool);
|
|
for (int i = 0; i < poolSpan.Length; i++)
|
|
{
|
|
ref var tuple = ref poolSpan[i];
|
|
if (tuple.inUse)
|
|
continue;
|
|
|
|
// Try to get larger arrays than requested in order to avoid reallocations
|
|
if (minimumSize > 0)
|
|
{
|
|
if (tuple.array._unmanagedAllocationSize >= minimumSize && tuple.array._unmanagedAllocationSize < smallestSize)
|
|
smallest = i;
|
|
continue;
|
|
}
|
|
else if (minimumSize == 0 && tuple.Item2._unmanagedAllocationSize != 0)
|
|
continue;
|
|
|
|
tuple.inUse = true;
|
|
return tuple.array;
|
|
}
|
|
if (minimumSize > 0 && smallest != -1)
|
|
{
|
|
ref var tuple = ref poolSpan[smallest];
|
|
tuple.inUse = true;
|
|
return tuple.array;
|
|
}
|
|
|
|
var newTuple = (inUse: true, array: new ManagedArray());
|
|
pool.Add(newTuple);
|
|
return newTuple.array;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the borrowed ManagedArray back to pool.
|
|
/// </summary>
|
|
/// <param name="obj">The array borrowed from the pool</param>
|
|
internal static void Put(ManagedArray obj)
|
|
{
|
|
foreach (ref var tuple in CollectionsMarshal.AsSpan(pool))
|
|
{
|
|
if (tuple.array != obj)
|
|
continue;
|
|
|
|
tuple.inUse = false;
|
|
return;
|
|
}
|
|
|
|
throw new NativeInteropException("Tried to free non-pooled ManagedArray as pooled ManagedArray");
|
|
}
|
|
}
|
|
}
|
|
|
|
#if FLAX_EDITOR
|
|
[HideInEditor]
|
|
#endif
|
|
public static class ManagedString
|
|
{
|
|
internal static ManagedHandle EmptyStringHandle = ManagedHandle.Alloc(string.Empty);
|
|
|
|
//[System.Diagnostics.DebuggerStepThrough]
|
|
public static unsafe IntPtr ToNative(string str)
|
|
{
|
|
if (str == null)
|
|
return IntPtr.Zero;
|
|
//else if (str == string.Empty)
|
|
// return ManagedHandle.ToIntPtr(EmptyStringHandle);
|
|
//return ManagedHandle.ToIntPtr(str);
|
|
|
|
NativeString* nativeString = (NativeString*)NativeMemory.AlignedAlloc((UIntPtr)Unsafe.SizeOf<NativeString>(), 16);
|
|
*nativeString = new NativeString(str);
|
|
return (IntPtr)nativeString;
|
|
}
|
|
|
|
//[System.Diagnostics.DebuggerStepThrough]
|
|
public static unsafe IntPtr ToNativeWeak(string str)
|
|
{
|
|
throw new Exception("not used");
|
|
if (str == null)
|
|
return IntPtr.Zero;
|
|
else if (str == string.Empty)
|
|
return ManagedHandle.ToIntPtr(EmptyStringHandle);
|
|
Assert.IsTrue(str.Length > 0);
|
|
return ManagedHandle.ToIntPtr(str, GCHandleType.Weak);
|
|
}
|
|
|
|
//[System.Diagnostics.DebuggerStepThrough]
|
|
public static unsafe string ToManaged(IntPtr ptr)
|
|
{
|
|
if (ptr == IntPtr.Zero)
|
|
return null;
|
|
//return Unsafe.As<string>(ManagedHandle.FromIntPtr(ptr).Target);
|
|
NativeString* str = (NativeString*)ptr.ToPointer();
|
|
return str->ToString();
|
|
}
|
|
|
|
//[System.Diagnostics.DebuggerStepThrough]
|
|
public static unsafe void Free(IntPtr ptr)
|
|
{
|
|
if (ptr == IntPtr.Zero)
|
|
return;
|
|
//ManagedHandle handle = ManagedHandle.FromIntPtr(ptr);
|
|
//if (handle == EmptyStringHandle)
|
|
// return;
|
|
//handle.Free();
|
|
Free((NativeString*)ptr.ToPointer());
|
|
}
|
|
|
|
//[System.Diagnostics.DebuggerStepThrough]
|
|
public static unsafe void Free(NativeString* str)
|
|
{
|
|
if (str->Data != null)
|
|
NativeMemory.AlignedFree(str->Data);
|
|
}
|
|
|
|
[System.Diagnostics.DebuggerStepThrough]
|
|
public static void Free(ManagedHandle handle)
|
|
{
|
|
throw new Exception("not used");
|
|
if (handle == EmptyStringHandle)
|
|
return;
|
|
handle.Free();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle to managed objects which can be stored in native code.
|
|
/// </summary>
|
|
#if FLAX_EDITOR
|
|
[HideInEditor]
|
|
#endif
|
|
public struct ManagedHandle
|
|
{
|
|
#if USE_GCHANDLE
|
|
private GCHandle handle;
|
|
|
|
#if TRACK_HANDLES
|
|
private static HashSet<IntPtr> _weakHandles = new HashSet<nint>();
|
|
private static ConcurrentDictionary<IntPtr, string> _handles = new();
|
|
private static ConcurrentDictionary<IntPtr, string> _handles2 = new();
|
|
private static long _allocations = 0;
|
|
private static long _deallocations = 0;
|
|
|
|
public static long Allocations => Interlocked.Read(ref _allocations);
|
|
public static long Deallocations => Interlocked.Read(ref _deallocations);
|
|
#else
|
|
public static long Allocations => 0;
|
|
public static long Deallocations => 0;
|
|
#endif
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private ManagedHandle(IntPtr handle) => this.handle = GCHandle.FromIntPtr(handle);
|
|
|
|
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private ManagedHandle(object value, GCHandleType type) //=> handle = GCHandle.Alloc(value, type);
|
|
{
|
|
#if TRACK_HANDLES
|
|
/*if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection)
|
|
{
|
|
type = GCHandleType.Normal;
|
|
}*/
|
|
#endif
|
|
handle = GCHandle.Alloc(value, type);
|
|
#if TRACK_HANDLES
|
|
/*if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection)
|
|
_weakHandles.Add((IntPtr)handle);
|
|
else if (_handles.Count < 14000)
|
|
_handles.TryAdd((IntPtr)handle, value?.GetType().FullName ?? "");
|
|
else
|
|
{
|
|
if (_handles2.Count > 12)
|
|
type = type;
|
|
if (value?.GetType() == typeof(string))
|
|
type = type;
|
|
_handles2.TryAdd((IntPtr)handle, value?.GetType().FullName ?? "");
|
|
}*/
|
|
Interlocked.Increment(ref _allocations);
|
|
#endif
|
|
}
|
|
#else
|
|
private IntPtr handle;
|
|
|
|
private ManagedHandle(IntPtr handle) => this.handle = handle;
|
|
|
|
private ManagedHandle(object value, GCHandleType type) => handle = ManagedHandlePool.AllocateHandle(value, type);
|
|
|
|
#endif
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static ManagedHandle Alloc(object value) => new ManagedHandle(value, GCHandleType.Normal);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static ManagedHandle Alloc(object value, GCHandleType type) => new ManagedHandle(value, type);
|
|
|
|
#if USE_GCHANDLE
|
|
public void Free()// => handle.Free();
|
|
{
|
|
if ((IntPtr)handle == IntPtr.Zero)
|
|
return;
|
|
#if TRACK_HANDLES
|
|
/*if (_weakHandles.Remove((IntPtr)handle))
|
|
{
|
|
if (!handle.IsAllocated)
|
|
handle = handle;
|
|
handle = handle;
|
|
}
|
|
else if (_handles.Remove((IntPtr)handle, out _))
|
|
{
|
|
handle = handle;
|
|
}
|
|
else if (_handles2.Remove((IntPtr)handle, out _))
|
|
{
|
|
handle = handle;
|
|
}*/
|
|
Interlocked.Increment(ref _deallocations);
|
|
#endif
|
|
handle.Free();
|
|
}
|
|
|
|
public object Target
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get => handle.Target;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
set => handle.Target = value;
|
|
}
|
|
|
|
public bool IsAllocated
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get => handle.IsAllocated;
|
|
}
|
|
#else
|
|
public void Free()
|
|
{
|
|
if (handle == IntPtr.Zero)
|
|
return;
|
|
ManagedHandlePool.FreeHandle(handle);
|
|
handle = IntPtr.Zero;
|
|
}
|
|
|
|
public object Target
|
|
{
|
|
get => ManagedHandlePool.GetObject(handle);
|
|
set => ManagedHandlePool.SetObject(handle, value);
|
|
}
|
|
|
|
public bool IsAllocated => handle != IntPtr.Zero;
|
|
#endif
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static explicit operator ManagedHandle(IntPtr value) => FromIntPtr(value);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static ManagedHandle FromIntPtr(IntPtr value) => new ManagedHandle(value);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static explicit operator IntPtr(ManagedHandle value) => ToIntPtr(value);
|
|
|
|
#if USE_GCHANDLE
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static IntPtr ToIntPtr(object value) => (IntPtr)Alloc(value, GCHandleType.Normal).handle;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static IntPtr ToIntPtr(object value, GCHandleType type) => (IntPtr)Alloc(value, type).handle;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static IntPtr ToIntPtr(ManagedHandle value) => (IntPtr)value.handle;
|
|
#else
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static IntPtr ToIntPtr(object value) => ManagedHandlePool.AllocateHandle(value, GCHandleType.Normal);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static IntPtr ToIntPtr(object value, GCHandleType type) => ManagedHandlePool.AllocateHandle(value, type);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static IntPtr ToIntPtr(ManagedHandle value) => value.handle;
|
|
#endif
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override int GetHashCode() => handle.GetHashCode();
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override bool Equals(object obj) => obj is ManagedHandle other && handle == other.handle;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public bool Equals(ManagedHandle other) => handle == other.handle;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static bool operator ==(ManagedHandle a, ManagedHandle b) => a.handle == b.handle;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static bool operator !=(ManagedHandle a, ManagedHandle b) => a.handle != b.handle;
|
|
|
|
#if USE_GCHANDLE
|
|
/// <summary>
|
|
/// A pool of shared managed handles used with short-lived temporary GCHandle allocations during interop.
|
|
/// </summary>
|
|
internal static class ManagedHandlePool
|
|
{
|
|
[ThreadStatic]
|
|
private static List<(bool InUse, ManagedHandle Handle)> __poolField;
|
|
private static List<(bool InUse, ManagedHandle Handle)> _pool => __poolField ??= [];
|
|
|
|
internal static void TryCollectWeakHandles(bool force = false)
|
|
{
|
|
}
|
|
|
|
internal static ManagedHandle Rent()
|
|
{
|
|
foreach (ref var tuple in CollectionsMarshal.AsSpan(_pool))
|
|
{
|
|
if (tuple.InUse)
|
|
continue;
|
|
|
|
tuple.InUse = true;
|
|
return tuple.Handle;
|
|
}
|
|
|
|
var newTuple = (InUse: true, Array: new ManagedHandle());
|
|
_pool.Add(newTuple);
|
|
return newTuple.Array;
|
|
}
|
|
|
|
internal static void Return(ManagedHandle handle)
|
|
{
|
|
foreach (ref var tuple in CollectionsMarshal.AsSpan(_pool))
|
|
{
|
|
if (tuple.Handle != handle)
|
|
continue;
|
|
|
|
tuple.InUse = false;
|
|
handle.Target = null;
|
|
return;
|
|
}
|
|
|
|
throw new Exception("Tried to return non-pooled ManagedHandle");
|
|
}
|
|
}
|
|
#else
|
|
|
|
internal static class ManagedHandlePool
|
|
{
|
|
private const int WeakPoolCollectionSizeThreshold = 10000000;
|
|
private const int WeakPoolCollectionTimeThreshold = 500;
|
|
|
|
// Rolling numbers for handles, two bits reserved for the type
|
|
private static ulong normalHandleAccumulator = ((ulong)GCHandleType.Normal << 62) & 0xC000000000000000;
|
|
private static ulong pinnedHandleAccumulator = ((ulong)GCHandleType.Pinned << 62) & 0xC000000000000000;
|
|
private static ulong weakHandleAccumulator = ((ulong)GCHandleType.Weak << 62) & 0xC000000000000000;
|
|
|
|
// Dictionaries for storing the valid handles.
|
|
// Note: Using locks seems to be generally the fastest when adding or fetching from the dictionary.
|
|
// Concurrent dictionaries could also be considered, but they perform much slower when adding to the dictionary.
|
|
#if USE_CONCURRENT_DICT
|
|
private static ConcurrentDictionary<IntPtr, object> persistentPool = new();
|
|
private static ConcurrentDictionary<IntPtr, GCHandle> pinnedPool = new();
|
|
#else
|
|
private static Dictionary<IntPtr, object> persistentPool = new();
|
|
private static Dictionary<IntPtr, GCHandle> pinnedPool = new();
|
|
private static Lock persistentPoolLock = new();
|
|
private static Lock pinnedPoolLock = new();
|
|
#endif
|
|
|
|
// TODO: Performance of pinned handles are poor at the moment due to GCHandle wrapping.
|
|
// TODO: .NET8: Experiment with pinned arrays for faster pinning: https://github.com/dotnet/runtime/pull/89293
|
|
|
|
// Manage double-buffered pool for weak handles in order to avoid collecting in-flight handles.
|
|
// Periodically when the pools are being accessed and conditions are met, the other pool is cleared and swapped.
|
|
private static int weakPoolSize = 1;
|
|
private static int weakPoolOtherSize = 1;
|
|
#if USE_CONCURRENT_DICT
|
|
private static ConcurrentDictionary<IntPtr, object> weakPool = new(-1, weakPoolSize);
|
|
private static ConcurrentDictionary<IntPtr, object> weakPoolOther = new(-1, weakPoolOtherSize);
|
|
#else
|
|
private static Dictionary<IntPtr, object> weakPool = new(weakPoolSize);
|
|
private static Dictionary<IntPtr, object> weakPoolOther = new(weakPoolOtherSize);
|
|
#endif
|
|
private static Lock weakPoolLock = new();
|
|
private static ulong nextWeakPoolCollection;
|
|
private static int nextWeakPoolGCCollection;
|
|
private static long lastWeakPoolCollectionTime;
|
|
|
|
/// <summary>
|
|
/// Tries to free all references to old weak handles so GC can collect them.
|
|
/// </summary>
|
|
internal static void TryCollectWeakHandles(bool force = false)
|
|
{
|
|
if (!force)
|
|
{
|
|
if (weakHandleAccumulator < nextWeakPoolCollection)
|
|
return;
|
|
|
|
nextWeakPoolCollection = weakHandleAccumulator + 1000;
|
|
|
|
// Try to swap pools after garbage collection or whenever the pool gets too large
|
|
var gc0CollectionCount = GC.CollectionCount(0);
|
|
if (gc0CollectionCount < nextWeakPoolGCCollection && weakPool.Count < WeakPoolCollectionSizeThreshold)
|
|
return;
|
|
nextWeakPoolGCCollection = gc0CollectionCount + 1;
|
|
|
|
// Prevent huge allocations from swapping the pools in the middle of the operation
|
|
if (System.Diagnostics.Stopwatch.GetElapsedTime(lastWeakPoolCollectionTime).TotalMilliseconds < WeakPoolCollectionTimeThreshold)
|
|
return;
|
|
}
|
|
lastWeakPoolCollectionTime = System.Diagnostics.Stopwatch.GetTimestamp();
|
|
|
|
// Swap the pools and release the oldest pool for GC
|
|
(weakPool, weakPoolOther) = (weakPoolOther, weakPool);
|
|
(weakPoolSize, weakPoolOtherSize) = (weakPoolOtherSize, weakPoolSize);
|
|
#if USE_CONCURRENT_DICT
|
|
if (weakPool.Count > weakPoolSize)
|
|
{
|
|
var newMax = Math.Max(Math.Max(weakPoolSize, weakPoolOtherSize), weakPool.Count);
|
|
//FlaxEditor.Editor.Log($"growth from {weakPoolSize} to {weakPool.Count}, max {newMax}");
|
|
weakPoolSize = newMax;
|
|
weakPool = new(-1, weakPoolSize);
|
|
}
|
|
//else if (weakPool.Count > 0)
|
|
// weakPool.Clear();
|
|
//else
|
|
#endif
|
|
weakPool.Clear();
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static IntPtr NewHandle(GCHandleType type) => type switch
|
|
{
|
|
GCHandleType.Normal => (IntPtr)Interlocked.Increment(ref normalHandleAccumulator),
|
|
GCHandleType.Pinned => (IntPtr)Interlocked.Increment(ref pinnedHandleAccumulator),
|
|
GCHandleType.Weak => (IntPtr)Interlocked.Increment(ref weakHandleAccumulator),
|
|
GCHandleType.WeakTrackResurrection => (IntPtr)Interlocked.Increment(ref weakHandleAccumulator),
|
|
_ => throw new NotImplementedException(type.ToString())
|
|
};
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static GCHandleType GetHandleType(IntPtr handle) => (GCHandleType)(((ulong)handle & 0xC000000000000000) >> 62);
|
|
|
|
internal static IntPtr AllocateHandle(object value, GCHandleType type)
|
|
{
|
|
IntPtr handle = NewHandle(type);
|
|
#if USE_CONCURRENT_DICT
|
|
switch (type)
|
|
{
|
|
case GCHandleType.Normal:
|
|
//lock (persistentPoolLock)
|
|
persistentPool.TryAdd(handle, value);
|
|
//if (value?.GetType().Name.Contains("RenderContext") ?? false)
|
|
// value = value;
|
|
break;
|
|
case GCHandleType.Pinned:
|
|
//lock (pinnedPoolLock)
|
|
pinnedPool.TryAdd(handle, GCHandle.Alloc(value, GCHandleType.Pinned));
|
|
break;
|
|
case GCHandleType.Weak:
|
|
case GCHandleType.WeakTrackResurrection:
|
|
lock (weakPoolLock)
|
|
{
|
|
TryCollectWeakHandles();
|
|
//weakPool.TryAdd(handle, value);
|
|
weakPool[handle] = value;
|
|
|
|
//if (value?.GetType().Name.Contains("RenderContext") ?? false)
|
|
// value = value;
|
|
}
|
|
break;
|
|
}
|
|
#else
|
|
switch (type)
|
|
{
|
|
case GCHandleType.Normal:
|
|
lock (persistentPoolLock)
|
|
persistentPool.Add(handle, value);
|
|
//if (value?.GetType().Name.Contains("RenderContext") ?? false)
|
|
// value = value;
|
|
break;
|
|
case GCHandleType.Pinned:
|
|
lock (pinnedPoolLock)
|
|
pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned));
|
|
break;
|
|
case GCHandleType.Weak:
|
|
case GCHandleType.WeakTrackResurrection:
|
|
lock (weakPoolLock)
|
|
{
|
|
TryCollectWeakHandles();
|
|
weakPool.Add(handle, value);
|
|
//weakPool[handle] = value;
|
|
|
|
//if (value?.GetType().Name.Contains("RenderContext") ?? false)
|
|
// value = value;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
return handle;
|
|
}
|
|
|
|
internal static object GetObject(IntPtr handle)
|
|
{
|
|
GCHandleType type = GetHandleType(handle);
|
|
|
|
switch (type)
|
|
{
|
|
case GCHandleType.Normal:
|
|
#if !USE_CONCURRENT_DICT
|
|
lock (persistentPoolLock)
|
|
#endif
|
|
{
|
|
if (persistentPool.TryGetValue(handle, out object value))
|
|
return value;
|
|
}
|
|
break;
|
|
case GCHandleType.Pinned:
|
|
#if !USE_CONCURRENT_DICT
|
|
lock (pinnedPoolLock)
|
|
#endif
|
|
{
|
|
if (pinnedPool.TryGetValue(handle, out GCHandle gcHandle))
|
|
return gcHandle.Target;
|
|
}
|
|
break;
|
|
case GCHandleType.Weak:
|
|
case GCHandleType.WeakTrackResurrection:
|
|
lock (weakPoolLock)
|
|
{
|
|
TryCollectWeakHandles();
|
|
if (weakPool.TryGetValue(handle, out object value))
|
|
return value;
|
|
else if (weakPoolOther.TryGetValue(handle, out value))
|
|
return value;
|
|
}
|
|
break;
|
|
}
|
|
throw new NativeInteropException($"Invalid ManagedHandle of type '{type}'");
|
|
}
|
|
|
|
internal static void SetObject(IntPtr handle, object value)
|
|
{
|
|
GCHandleType type = GetHandleType(handle);
|
|
#if USE_CONCURRENT_DICT
|
|
switch (type)
|
|
{
|
|
case GCHandleType.Normal:
|
|
//lock (persistentPoolLock)
|
|
{
|
|
//ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(persistentPool, handle);
|
|
if (persistentPool.TryGetValue(handle, out var oldValue))
|
|
{
|
|
if (persistentPool.TryUpdate(handle, value, oldValue))
|
|
//if (!Unsafe.IsNullRef(ref obj))
|
|
{
|
|
//obj = value;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case GCHandleType.Pinned:
|
|
//lock (pinnedPoolLock)
|
|
{
|
|
/*ref GCHandle gcHandle = ref CollectionsMarshal.GetValueRefOrNullRef(pinnedPool, handle);
|
|
if (!Unsafe.IsNullRef(ref gcHandle))
|
|
{
|
|
gcHandle.Target = value;
|
|
return;
|
|
}*/
|
|
if (pinnedPool.TryGetValue(handle, out var gcHandle))
|
|
{
|
|
gcHandle.Target = value;
|
|
//if (pinnedPool.TryUpdate(handle, value, oldValue))
|
|
//if (!Unsafe.IsNullRef(ref obj))
|
|
//{
|
|
//obj = value;
|
|
return;
|
|
}
|
|
//}
|
|
}
|
|
break;
|
|
case GCHandleType.Weak:
|
|
case GCHandleType.WeakTrackResurrection:
|
|
lock (weakPoolLock)
|
|
{
|
|
TryCollectWeakHandles();
|
|
if (weakPool.TryGetValue(handle, out var oldValue))
|
|
{
|
|
if (weakPool.TryUpdate(handle, value, oldValue))
|
|
//if (!Unsafe.IsNullRef(ref obj))
|
|
{
|
|
//obj = value;
|
|
return;
|
|
}
|
|
}
|
|
if (weakPoolOther.TryGetValue(handle, out oldValue))
|
|
{
|
|
if (weakPoolOther.TryUpdate(handle, value, oldValue))
|
|
//if (!Unsafe.IsNullRef(ref obj))
|
|
{
|
|
//obj = value;
|
|
return;
|
|
}
|
|
}
|
|
/*{
|
|
ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPool, handle);
|
|
if (!Unsafe.IsNullRef(ref obj))
|
|
{
|
|
obj = value;
|
|
return;
|
|
}
|
|
}
|
|
{
|
|
ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPoolOther, handle);
|
|
if (!Unsafe.IsNullRef(ref obj))
|
|
{
|
|
obj = value;
|
|
return;
|
|
}
|
|
}*/
|
|
}
|
|
break;
|
|
}
|
|
#else
|
|
switch (type)
|
|
{
|
|
case GCHandleType.Normal:
|
|
lock (persistentPoolLock)
|
|
{
|
|
ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(persistentPool, handle);
|
|
if (!Unsafe.IsNullRef(ref obj))
|
|
{
|
|
obj = value;
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case GCHandleType.Pinned:
|
|
lock (pinnedPoolLock)
|
|
{
|
|
ref GCHandle gcHandle = ref CollectionsMarshal.GetValueRefOrNullRef(pinnedPool, handle);
|
|
if (!Unsafe.IsNullRef(ref gcHandle))
|
|
{
|
|
gcHandle.Target = value;
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case GCHandleType.Weak:
|
|
case GCHandleType.WeakTrackResurrection:
|
|
lock (weakPoolLock)
|
|
{
|
|
TryCollectWeakHandles();
|
|
{
|
|
ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPool, handle);
|
|
if (!Unsafe.IsNullRef(ref obj))
|
|
{
|
|
obj = value;
|
|
return;
|
|
}
|
|
}
|
|
{
|
|
ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPoolOther, handle);
|
|
if (!Unsafe.IsNullRef(ref obj))
|
|
{
|
|
obj = value;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
throw new NativeInteropException($"Invalid ManagedHandle of type '{type}'");
|
|
}
|
|
|
|
internal static void FreeHandle(IntPtr handle)
|
|
{
|
|
GCHandleType type = GetHandleType(handle);
|
|
switch (type)
|
|
{
|
|
case GCHandleType.Normal:
|
|
#if !USE_CONCURRENT_DICT
|
|
lock (persistentPoolLock)
|
|
#endif
|
|
{
|
|
if (persistentPool.Remove(handle, out _))
|
|
{
|
|
//if (value?.GetType().Name.Contains("RenderContext") ?? false)
|
|
// value = value;
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case GCHandleType.Pinned:
|
|
#if !USE_CONCURRENT_DICT
|
|
lock (pinnedPoolLock)
|
|
#endif
|
|
{
|
|
if (pinnedPool.Remove(handle, out GCHandle gcHandle))
|
|
{
|
|
gcHandle.Free();
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case GCHandleType.Weak:
|
|
case GCHandleType.WeakTrackResurrection:
|
|
lock (weakPoolLock)
|
|
{
|
|
TryCollectWeakHandles();
|
|
if (weakPool.Remove(handle, out _))
|
|
return;
|
|
else if (weakPoolOther.Remove(handle, out _))
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
throw new NativeInteropException($"Invalid ManagedHandle of type '{type}'");
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#endif
|