diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs
index 495222373..d3c644072 100644
--- a/Source/Engine/Engine/NativeInterop.Managed.cs
+++ b/Source/Engine/Engine/NativeInterop.Managed.cs
@@ -20,6 +20,7 @@ namespace FlaxEngine.Interop
{
private ManagedHandle _pinnedArrayHandle;
private IntPtr _unmanagedData;
+ private int _unmanagedAllocationSize;
private Type _arrayType;
private Type _elementType;
private int _elementSize;
@@ -46,7 +47,7 @@ namespace FlaxEngine.Interop
/// The resources must be released by calling FreePooled() instead of Free()-method.
public static ManagedArray WrapPooledArray(Array arr, Type arrayType)
{
- ManagedArray managedArray = ManagedArrayPool.Get();
+ ManagedArray managedArray = ManagedArrayPool.Get(arr.Length * Marshal.SizeOf(arr.GetType().GetElementType()));
managedArray.WrapArray(arr, arrayType);
return managedArray;
}
@@ -57,25 +58,14 @@ namespace FlaxEngine.Interop
internal static ManagedArray AllocateNewArray(IntPtr ptr, int length, Type arrayType, Type elementType)
=> new ManagedArray(ptr, length, arrayType, elementType);
- ///
- /// Returns an instance of ManagedArray from shared pool.
- ///
- /// The resources must be released by calling FreePooled() instead of Free()-method.
- public static ManagedArray AllocatePooledArray(T* ptr, int length) where T : unmanaged
- {
- ManagedArray managedArray = ManagedArrayPool.Get();
- managedArray.Allocate(ptr, length);
- return managedArray;
- }
-
///
/// Returns an instance of ManagedArray from shared pool.
///
/// The resources must be released by calling FreePooled() instead of Free()-method.
public static ManagedArray AllocatePooledArray(int length) where T : unmanaged
{
- ManagedArray managedArray = ManagedArrayPool.Get();
- managedArray.Allocate((T*)NativeInterop.NativeAlloc(length, Unsafe.SizeOf()), length);
+ ManagedArray managedArray = ManagedArrayPool.Get(length * Unsafe.SizeOf());
+ managedArray.Allocate(length);
return managedArray;
}
@@ -83,17 +73,27 @@ namespace FlaxEngine.Interop
internal void WrapArray(Array arr, Type arrayType)
{
+ if (_unmanagedData != IntPtr.Zero)
+ NativeInterop.NativeFree(_unmanagedData.ToPointer());
_pinnedArrayHandle = ManagedHandle.Alloc(arr, GCHandleType.Pinned);
_unmanagedData = Marshal.UnsafeAddrOfPinnedArrayElement(arr, 0);
+ _unmanagedAllocationSize = 0;
_length = arr.Length;
_arrayType = arrayType;
_elementType = arr.GetType().GetElementType();
_elementSize = Marshal.SizeOf(_elementType);
}
- internal void Allocate(T* ptr, int length) where T : unmanaged
+ internal void Allocate(int length) where T : unmanaged
{
- _unmanagedData = new IntPtr(ptr);
+ // Try to reuse existing allocated buffer
+ if (length * Unsafe.SizeOf() > _unmanagedAllocationSize)
+ {
+ if (_unmanagedAllocationSize > 0)
+ NativeInterop.NativeFree(_unmanagedData.ToPointer());
+ _unmanagedData = (IntPtr)NativeInterop.NativeAlloc(length, Unsafe.SizeOf());
+ _unmanagedAllocationSize = Unsafe.SizeOf() * length;
+ }
_length = length;
_arrayType = typeof(T).MakeArrayType();
_elementType = typeof(T);
@@ -108,6 +108,7 @@ namespace FlaxEngine.Interop
{
Assert.IsTrue(arrayType.IsArray);
_unmanagedData = ptr;
+ _unmanagedAllocationSize = Marshal.SizeOf(elementType) * length;
_length = length;
_arrayType = arrayType;
_elementType = elementType;
@@ -132,12 +133,17 @@ namespace FlaxEngine.Interop
{
NativeInterop.NativeFree(_unmanagedData.ToPointer());
_unmanagedData = IntPtr.Zero;
+ _unmanagedAllocationSize = 0;
}
}
public void FreePooled()
{
- Free();
+ if (_pinnedArrayHandle.IsAllocated)
+ {
+ _pinnedArrayHandle.Free();
+ _unmanagedData = IntPtr.Zero;
+ }
ManagedArrayPool.Put(this);
}
@@ -194,39 +200,65 @@ namespace FlaxEngine.Interop
private static class ManagedArrayPool
{
[ThreadStatic]
- private static List> pool;
+ private static List<(bool inUse, ManagedArray array)> pool;
- internal static ManagedArray Get()
+ ///
+ /// Borrows an array from the pool.
+ ///
+ /// Minimum size in bytes for the borrowed array. With value of 0, the returned array allocation is always zero.
+ /// The returned array size may be smaller than the requested minimumSize.
+ internal static ManagedArray Get(int minimumSize = 0)
{
if (pool == null)
- pool = new List>();
- for (int i = 0; i < pool.Count; i++)
+ pool = new();
+
+ int smallest = -1;
+ int smallestSize = int.MaxValue;
+ var poolSpan = CollectionsMarshal.AsSpan(pool);
+ for (int i = 0; i < poolSpan.Length; i++)
{
- if (!pool[i].Item1)
+ 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)
{
- var tuple = pool[i];
- tuple.Item1 = true;
- pool[i] = tuple;
- return tuple.Item2;
+ 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 = (true, new ManagedArray());
+ var newTuple = (inUse: true, array: new ManagedArray());
pool.Add(newTuple);
- return newTuple.Item2;
+ return newTuple.array;
}
+ ///
+ /// Returns the borrowed ManagedArray back to pool.
+ ///
+ /// The array borrowed from the pool
internal static void Put(ManagedArray obj)
{
- for (int i = 0; i < pool.Count; i++)
+ foreach (ref var tuple in CollectionsMarshal.AsSpan(pool))
{
- if (pool[i].Item2 == obj)
- {
- var tuple = pool[i];
- tuple.Item1 = false;
- pool[i] = tuple;
- return;
- }
+ if (tuple.array != obj)
+ continue;
+
+ tuple.inUse = false;
+ return;
}
throw new Exception("Tried to free non-pooled ManagedArray as pooled ManagedArray");