Skip to content

Commit

Permalink
Slight reworks around custom memory management
Browse files Browse the repository at this point in the history
  • Loading branch information
bibigone committed Mar 3, 2024
1 parent c4294ca commit 54a3ac3
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 37 deletions.
4 changes: 2 additions & 2 deletions K4AdotNet.Tests.Unit/Sensor/ImageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ public void TestCustomMemoryManagement()
{
// Set our test custom allocator
var testAllocator = new TestMemoryAllocator();
Sdk.SetCustomMemoryAllocator(testAllocator);
Sdk.CustomMemoryAllocator = testAllocator;
// Check initial state
Assert.AreEqual(0, testAllocator.AllocateCount);
Assert.AreEqual(0, testAllocator.FreeCount);
Expand Down Expand Up @@ -526,7 +526,7 @@ public void TestCustomMemoryManagement()
Assert.AreEqual(testContextA, testAllocator.LastFreeContextValue);

// Clear custom allocator
Sdk.SetCustomMemoryAllocator(null);
Sdk.CustomMemoryAllocator = null;

// Now creation of test image does not result in calls to our testAllocator instance
var testImageC = new Image(ImageFormat.ColorYUY2, testWidth, testHeight);
Expand Down
40 changes: 40 additions & 0 deletions K4AdotNet/HGlobalMemoryAllocator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Runtime.InteropServices;

namespace K4AdotNet
{
/// <summary>
/// Implementation of <see cref="ICustomMemoryAllocator"/> interface
/// via <see cref="Marshal.AllocHGlobal(int)"/> and <see cref="Marshal.FreeHGlobal(IntPtr)"/> methods.
/// </summary>
public sealed class HGlobalMemoryAllocator : ICustomMemoryAllocator
{
/// <summary>Singleton.</summary>
public static readonly ICustomMemoryAllocator Instance = new HGlobalMemoryAllocator();

/// <summary>For internal usage.</summary>
internal static readonly Sensor.NativeApi.MemoryDestroyCallback MemoryDestroyCallback = new(ReleaseUnmanagedBuffer);

private HGlobalMemoryAllocator()
{ }

/// <summary>Allocates a buffer of size at least <paramref name="size"/> bytes.</summary>
/// <param name="size">Minimum size in bytes needed for the buffer. Cannot be negative.</param>
/// <param name="context">Not used. Always <see cref="IntPtr.Zero"/>.</param>
/// <returns>A pointer to the newly allocated memory. This memory must be released using the <see cref="Free(IntPtr, IntPtr)"/> method.</returns>
public IntPtr Allocate(int size, out IntPtr context)
{
context = IntPtr.Zero;
return Marshal.AllocHGlobal(size);
}

/// <summary>Frees memory previously allocated by <see cref="Allocate(int, out IntPtr)"/> method.</summary>
/// <param name="buffer">The handle returned by the original matching call to <see cref="Allocate(int, out IntPtr)"/> method.</param>
/// <param name="context">Not used.</param>
public void Free(IntPtr buffer, IntPtr context)
=> ReleaseUnmanagedBuffer(buffer, context);

private static void ReleaseUnmanagedBuffer(IntPtr buffer, IntPtr context)
=> Marshal.FreeHGlobal(buffer);
}
}
2 changes: 1 addition & 1 deletion K4AdotNet/ICustomMemoryAllocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace K4AdotNet
/// <summary>
/// Base interface for custom memory manager that can be used to allocate and free memory used by images.
/// </summary>
/// <seealso cref="Sdk.SetCustomMemoryAllocator(ICustomMemoryAllocator?)"/>
/// <seealso cref="Sdk.CustomMemoryAllocator"/>
public interface ICustomMemoryAllocator
{
/// <summary>Function for a memory allocation. Will be called by internals of Azure Kinect SDK.</summary>
Expand Down
81 changes: 56 additions & 25 deletions K4AdotNet/Sdk.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
Expand Down Expand Up @@ -158,45 +157,77 @@ public static void ConfigureBodyTrackingLogging(TraceLevel level, bool logToStdo

#region Custom memory allocation

private static volatile ICustomMemoryAllocator? customMemoryAllocator;
private static readonly object customMemoryAllocatorSync = new object();
// to keep callbacks alive
private static readonly ConcurrentBag<Sensor.NativeApi.MemoryAllocateCallback> allocateCallbacks = new();
private static readonly ConcurrentBag<Sensor.NativeApi.MemoryDestroyCallback> destroyCallbacks = new();
private static readonly LinkedList<Sensor.NativeApi.MemoryAllocateCallback> allocateCallbacks = new();
private static readonly LinkedList<Sensor.NativeApi.MemoryDestroyCallback> destroyCallbacks = new();

/// <summary>
/// Sets or clears custom memory allocator to be use by internals of Azure Kinect SDK.
/// </summary>
/// <param name="allocator">
/// Instance of <see cref="ICustomMemoryAllocator"/> to allocate and free memory in internals of Azure Kinect SDK
/// or <see langword="null"/> to "clear" custom memory allocator (that is, to switch to standard built-in SDK's allocator).
/// </param>
/// <exception cref="InvalidOperationException">Setting or clearing of custom memory allocator was failed. See log for details.</exception>
/// <remarks>
/// All instances of <paramref name="allocator"/> will be keeping alive forever because they can be used
/// <remarks><para>
/// Assigning <see langword="null"/> means the clearing of custom memory allocator (that is, to switch to standard built-in SDK's allocator).
/// </para><para>
/// All instances of <see cref="ICustomMemoryAllocator"/> will be keeping alive forever because they can be used
/// to free memory even after setting custom allocator to <see langword="null"/>.
/// </remarks>
/// </para></remarks>
#if ORBBECSDK_K4A_WRAPPER
[Obsolete("Not supported by OrbbecSDK-K4A-Wrapper")]
#endif
public static void SetCustomMemoryAllocator(ICustomMemoryAllocator? allocator)
public static ICustomMemoryAllocator? CustomMemoryAllocator
{
if (allocator is null)
get => customMemoryAllocator;

set
{
var res = Sensor.NativeApi.SetAllocator(null, null);
if (res != NativeCallResults.Result.Succeeded)
throw new InvalidOperationException("Cannot clear custom memory allocator");
return;
}
lock (customMemoryAllocatorSync)
{
if (customMemoryAllocator == value)
return;

var allocateCallback = new Sensor.NativeApi.MemoryAllocateCallback(allocator.Allocate);
var destroyCallback = new Sensor.NativeApi.MemoryDestroyCallback(allocator.Free);
if (value is null)
{
var res = Sensor.NativeApi.SetAllocator(null, null);
if (res != NativeCallResults.Result.Succeeded)
throw new InvalidOperationException("Cannot clear custom memory allocator");
customMemoryAllocator = null;
return;
}

var allocateCallback = new Sensor.NativeApi.MemoryAllocateCallback(value.Allocate);
var destroyCallback = new Sensor.NativeApi.MemoryDestroyCallback(value.Free);

// to keep callbacks alive
allocateCallbacks.AddFirst(allocateCallback);
destroyCallbacks.AddFirst(destroyCallback);

var rs = Sensor.NativeApi.SetAllocator(allocateCallback, destroyCallback);
if (rs != NativeCallResults.Result.Succeeded)
{
allocateCallbacks.RemoveFirst();
destroyCallbacks.RemoveFirst();
throw new InvalidOperationException("Cannot set custom memory allocator");
}

// to keep callbacks alive
allocateCallbacks.Add(allocateCallback);
destroyCallbacks.Add(destroyCallback);
customMemoryAllocator = value;
}
}
}

var rs = Sensor.NativeApi.SetAllocator(allocateCallback, destroyCallback);
if (rs != NativeCallResults.Result.Succeeded)
throw new InvalidOperationException("Cannot set custom memory allocator");
/// <summary>Gets information about current memory allocator in use. For internal needs.</summary>
/// <param name="memoryAllocator">Instance of memory allocator.</param>
/// <param name="memoryDestroyCallback">Reference to native callback to destroy memory.</param>
internal static void GetCustomMemoryAllocator(
out ICustomMemoryAllocator? memoryAllocator,
out Sensor.NativeApi.MemoryDestroyCallback? memoryDestroyCallback)
{
lock (customMemoryAllocatorSync)
{
memoryAllocator = customMemoryAllocator;
memoryDestroyCallback = destroyCallbacks.FirstOrDefault();
}
}

#endregion
Expand Down
20 changes: 11 additions & 9 deletions K4AdotNet/Sensor/Image.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,22 @@ public Image(ImageFormat format, int widthPixels, int heightPixels, int strideBy
if (strideBytes > 0 && sizeBytes < format.ImageSizeBytes(strideBytes, heightPixels))
throw new ArgumentOutOfRangeException(nameof(sizeBytes));

var buffer = Marshal.AllocHGlobal(sizeBytes);
// Gets current memory allocator
Sdk.GetCustomMemoryAllocator(out var memoryAllocator, out var memoryDestroyCallback);
// If not set, use HGlobal
if (memoryAllocator is null || memoryDestroyCallback is null)
{
memoryAllocator = HGlobalMemoryAllocator.Instance;
memoryDestroyCallback = HGlobalMemoryAllocator.MemoryDestroyCallback;
}

var buffer = memoryAllocator.Allocate(sizeBytes, out var memoryContext);
if (buffer == IntPtr.Zero)
throw new OutOfMemoryException($"Cannot allocate buffer of {sizeBytes} bytes.");

var res = NativeApi.ImageCreateFromBuffer(format, widthPixels, heightPixels, strideBytes,
buffer, Helpers.Int32ToUIntPtr(sizeBytes),
unmanagedBufferReleaseCallback, IntPtr.Zero,
memoryDestroyCallback, memoryContext,
out var handle);
if (res != NativeCallResults.Result.Succeeded || !handle.IsValid)
throw new ArgumentException($"Cannot create image with format {format}, size {widthPixels}x{heightPixels} pixels, stride {strideBytes} bytes from buffer of size {sizeBytes} bytes.");
Expand Down Expand Up @@ -642,13 +651,6 @@ public override string ToString()

#region Memory management

// This field is required to keep callback delegate in memory
private static readonly NativeApi.MemoryDestroyCallback unmanagedBufferReleaseCallback
= new(ReleaseUnmanagedBuffer);

private static void ReleaseUnmanagedBuffer(IntPtr buffer, IntPtr context)
=> Marshal.FreeHGlobal(buffer);

// This field is required to keep callback delegate in memory
private static readonly NativeApi.MemoryDestroyCallback pinnedArrayReleaseCallback
= new(ReleasePinnedArray);
Expand Down

0 comments on commit 54a3ac3

Please sign in to comment.