Merge branch 'weak_handle_improvements' of https://github.com/GoaLitiuM/FlaxEngine into GoaLitiuM-weak_handle_improvements

This commit is contained in:
Wojtek Figat
2023-04-29 12:18:28 +02:00
9 changed files with 147 additions and 88 deletions

View File

@@ -305,13 +305,11 @@ namespace FlaxEngine.Interop
public void Free()
{
if (handle != IntPtr.Zero)
{
ManagedHandlePool.FreeHandle(handle);
handle = IntPtr.Zero;
}
if (handle == IntPtr.Zero)
return;
ManagedHandlePool.TryCollectWeakHandles();
ManagedHandlePool.FreeHandle(handle);
handle = IntPtr.Zero;
}
public object Target
@@ -352,6 +350,8 @@ namespace FlaxEngine.Interop
private static class ManagedHandlePool
{
private const int WeakPoolCollectionThreshold = 10000000;
private static ulong normalHandleAccumulator = 0;
private static ulong pinnedHandleAccumulator = 0;
private static ulong weakHandleAccumulator = 0;
@@ -360,31 +360,39 @@ namespace FlaxEngine.Interop
private static Dictionary<IntPtr, object> persistentPool = new Dictionary<nint, object>();
private static Dictionary<IntPtr, GCHandle> pinnedPool = new Dictionary<nint, GCHandle>();
private static Dictionary<IntPtr, object> weakPool1 = new Dictionary<nint, object>();
private static Dictionary<IntPtr, object> weakPool2 = new Dictionary<nint, object>();
private static Dictionary<IntPtr, object> weakPool = weakPool1;
private static Dictionary<IntPtr, object> weakPoolOther = weakPool2;
private static int nextCollection = GC.CollectionCount(0) + 1;
// Manage double-buffered pool for weak handles in order to avoid collecting in-flight handles
[ThreadStatic] private static Dictionary<IntPtr, object> weakPool;
[ThreadStatic] private static Dictionary<IntPtr, object> weakPoolOther;
[ThreadStatic] private static ulong nextWeakPoolCollection;
[ThreadStatic] private static int nextWeakPoolGCCollection;
/// <summary>
/// Tries to free all references to old weak handles so GC can collect them.
/// </summary>
internal static void TryCollectWeakHandles()
private static void TryCollectWeakHandles()
{
if (GC.CollectionCount(0) < nextCollection)
if (weakHandleAccumulator < nextWeakPoolCollection)
return;
lock (poolLock)
nextWeakPoolCollection = weakHandleAccumulator + 1000;
if (weakPool == null)
{
nextCollection = GC.CollectionCount(0) + 1;
var swap = weakPoolOther;
weakPoolOther = weakPool;
weakPool = swap;
weakPool.Clear();
weakPool = new Dictionary<nint, object>();
weakPoolOther = new Dictionary<nint, object>();
nextWeakPoolGCCollection = GC.CollectionCount(0);
return;
}
// Collect right after garbage collection or whenever the pool gets too large
var gc0CollectionCount = GC.CollectionCount(0);
if (gc0CollectionCount < nextWeakPoolGCCollection && weakPool.Count < WeakPoolCollectionThreshold)
return;
nextWeakPoolGCCollection = gc0CollectionCount + 1;
var swap = weakPoolOther;
weakPoolOther = weakPool;
weakPool = swap;
weakPool.Clear();
}
private static IntPtr NewHandle(GCHandleType type)
@@ -410,71 +418,107 @@ namespace FlaxEngine.Interop
internal static IntPtr AllocateHandle(object value, GCHandleType type)
{
TryCollectWeakHandles();
IntPtr handle = NewHandle(type);
lock (poolLock)
if (type == GCHandleType.Normal)
{
if (type == GCHandleType.Normal)
lock (poolLock)
persistentPool.Add(handle, value);
else if (type == GCHandleType.Pinned)
pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned));
else if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection)
weakPool.Add(handle, value);
}
else if (type == GCHandleType.Pinned)
{
lock (poolLock)
pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned));
}
else if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection)
weakPool.Add(handle, value);
return handle;
}
internal static object GetObject(IntPtr handle)
{
TryCollectWeakHandles();
object value;
GCHandleType type = GetHandleType(handle);
lock (poolLock)
if (type == GCHandleType.Normal)
{
if (type == GCHandleType.Normal && persistentPool.TryGetValue(handle, out value))
return value;
else if (type == GCHandleType.Pinned && pinnedPool.TryGetValue(handle, out GCHandle gchandle))
return gchandle.Target;
else if (weakPool.TryGetValue(handle, out value))
return value;
else if (weakPoolOther.TryGetValue(handle, out value))
return value;
lock (poolLock)
{
if (persistentPool.TryGetValue(handle, out value))
return value;
}
}
else if (type == GCHandleType.Pinned)
{
lock (poolLock)
{
if (pinnedPool.TryGetValue(handle, out GCHandle gchandle))
return gchandle.Target;
}
}
else if (weakPool.TryGetValue(handle, out value))
return value;
else if (weakPoolOther.TryGetValue(handle, out value))
return value;
throw new Exception("Invalid ManagedHandle");
}
internal static void SetObject(IntPtr handle, object value)
{
TryCollectWeakHandles();
GCHandleType type = GetHandleType(handle);
lock (poolLock)
if (type == GCHandleType.Normal)
{
if (type == GCHandleType.Normal && persistentPool.ContainsKey(handle))
persistentPool[handle] = value;
else if (type == GCHandleType.Pinned && pinnedPool.TryGetValue(handle, out GCHandle gchandle))
gchandle.Target = value;
else if (weakPool.ContainsKey(handle))
weakPool[handle] = value;
else if (weakPoolOther.ContainsKey(handle))
weakPoolOther[handle] = value;
lock (poolLock)
{
if (persistentPool.ContainsKey(handle))
persistentPool[handle] = value;
}
}
else if (type == GCHandleType.Pinned)
{
lock (poolLock)
{
if (pinnedPool.TryGetValue(handle, out GCHandle gchandle))
gchandle.Target = value;
}
}
else if (weakPool.ContainsKey(handle))
weakPool[handle] = value;
else if (weakPoolOther.ContainsKey(handle))
weakPoolOther[handle] = value;
throw new Exception("Invalid ManagedHandle");
}
internal static void FreeHandle(IntPtr handle)
{
TryCollectWeakHandles();
GCHandleType type = GetHandleType(handle);
lock (poolLock)
if (type == GCHandleType.Normal)
{
if (type == GCHandleType.Normal && persistentPool.Remove(handle))
return;
else if (type == GCHandleType.Pinned && pinnedPool.Remove(handle, out GCHandle gchandle))
lock (poolLock)
{
gchandle.Free();
return;
if (persistentPool.Remove(handle))
return;
}
else if (weakPool.Remove(handle))
return;
else if (weakPoolOther.Remove(handle))
return;
}
else if (type == GCHandleType.Pinned)
{
lock (poolLock)
{
if (pinnedPool.Remove(handle, out GCHandle gchandle))
{
gchandle.Free();
return;
}
}
}
else
return;
throw new Exception("Invalid ManagedHandle");
}
}

View File

@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
#pragma warning disable 1591
@@ -34,13 +35,16 @@ namespace FlaxEngine.Interop
public static class ManagedToNative
{
public static IntPtr ConvertToUnmanaged(object managed) => managed != null ? ManagedHandle.ToIntPtr(managed) : IntPtr.Zero;
public static IntPtr ConvertToUnmanaged(object managed) => managed != null ? ManagedHandle.ToIntPtr(managed, GCHandleType.Weak) : IntPtr.Zero;
public static void Free(IntPtr unmanaged)
{
// This is a weak handle, no need to free it
/*
if (unmanaged == IntPtr.Zero)
return;
ManagedHandle.FromIntPtr(unmanaged).Free();
*/
}
}
@@ -170,7 +174,7 @@ namespace FlaxEngine.Interop
{
if (managedArray == null)
return IntPtr.Zero;
handle = ManagedHandle.Alloc(managedArray);
handle = ManagedHandle.Alloc(managedArray, GCHandleType.Weak);
return ManagedHandle.ToIntPtr(handle);
}
@@ -179,7 +183,7 @@ namespace FlaxEngine.Interop
if (managedArray == null)
return;
managedArray.FreePooled();
handle.Free();
//handle.Free(); // No need to free weak handles
}
}
@@ -232,8 +236,11 @@ namespace FlaxEngine.Interop
public static class ManagedToNative
{
public static IntPtr ConvertToUnmanaged(Dictionary<T, U> managed) => DictionaryMarshaller<T, U>.ToNative(managed);
public static void Free(IntPtr unmanaged) => DictionaryMarshaller<T, U>.Free(unmanaged);
public static IntPtr ConvertToUnmanaged(Dictionary<T, U> managed) => DictionaryMarshaller<T, U>.ToNative(managed, GCHandleType.Weak);
public static void Free(IntPtr unmanaged)
{
//DictionaryMarshaller<T, U>.Free(unmanaged); // No need to free weak handles
}
}
public struct Bidirectional
@@ -265,7 +272,7 @@ namespace FlaxEngine.Interop
public static IntPtr ConvertToUnmanaged(Dictionary<T, U> managed) => managed != null ? ManagedHandle.ToIntPtr(managed) : IntPtr.Zero;
public static Dictionary<T, U> ToManaged(IntPtr unmanaged) => unmanaged != IntPtr.Zero ? Unsafe.As<Dictionary<T, U>>(ManagedHandle.FromIntPtr(unmanaged).Target) : null;
public static IntPtr ToNative(Dictionary<T, U> managed) => managed != null ? ManagedHandle.ToIntPtr(managed) : IntPtr.Zero;
public static IntPtr ToNative(Dictionary<T, U> managed, GCHandleType handleType = GCHandleType.Normal) => managed != null ? ManagedHandle.ToIntPtr(managed, handleType) : IntPtr.Zero;
public static void Free(IntPtr unmanaged)
{
@@ -334,8 +341,7 @@ namespace FlaxEngine.Interop
}
numElements = managed.Length;
ManagedArray managedArray = ManagedArray.AllocatePooledArray<TUnmanagedElement>(managed.Length);
var ptr = ManagedHandle.ToIntPtr(managedArray);
return (TUnmanagedElement*)ptr;
return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak);
}
public static ReadOnlySpan<T> GetManagedValuesSource(T[]? managed) => managed;
@@ -354,7 +360,7 @@ namespace FlaxEngine.Interop
return;
ManagedHandle handle = ManagedHandle.FromIntPtr(new IntPtr(unmanaged));
(Unsafe.As<ManagedArray>(handle.Target)).FreePooled();
handle.Free();
//handle.Free(); // No need to free weak handles
}
}
@@ -475,10 +481,13 @@ namespace FlaxEngine.Interop
{
public static unsafe IntPtr ConvertToUnmanaged(string managed)
{
return managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed);
return managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed, GCHandleType.Weak);
}
public static void Free(IntPtr unmanaged) => ManagedString.Free(unmanaged);
public static void Free(IntPtr unmanaged)
{
//ManagedString.Free(unmanaged); // No need to free weak handles
}
}
public struct Bidirectional

View File

@@ -249,13 +249,13 @@ bool AndroidFileSystem::DeleteFile(const StringView& path)
uint64 AndroidFileSystem::GetFileSize(const StringView& path)
{
struct stat fileInfo;
fileInfo.st_size = -1;
fileInfo.st_size = 0;
const StringAsANSI<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) != -1)
{
if (S_ISDIR(fileInfo.st_mode))
{
fileInfo.st_size = -1;
fileInfo.st_size = 0;
}
}
else

View File

@@ -223,14 +223,14 @@ bool AppleFileSystem::DeleteFile(const StringView& path)
uint64 AppleFileSystem::GetFileSize(const StringView& path)
{
struct stat fileInfo;
fileInfo.st_size = -1;
fileInfo.st_size = 0;
const StringAsANSI<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) != -1)
{
// Check for directories
if (S_ISDIR(fileInfo.st_mode))
{
fileInfo.st_size = -1;
fileInfo.st_size = 0;
}
}
return fileInfo.st_size;

View File

@@ -346,14 +346,14 @@ bool LinuxFileSystem::DeleteFile(const StringView& path)
uint64 LinuxFileSystem::GetFileSize(const StringView& path)
{
struct stat fileInfo;
fileInfo.st_size = -1;
fileInfo.st_size = 0;
const StringAsANSI<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) != -1)
{
// Check for directories
if (S_ISDIR(fileInfo.st_mode))
{
fileInfo.st_size = -1;
fileInfo.st_size = 0;
}
}
return fileInfo.st_size;