Skip to content

Instantly share code, notes, and snippets.

@prime31
Created June 6, 2026 20:12
Show Gist options
  • Select an option

  • Save prime31/4bbb4ee8ab1824c4906e3da415ad6b4d to your computer and use it in GitHub Desktop.

Select an option

Save prime31/4bbb4ee8ab1824c4906e3da415ad6b4d to your computer and use it in GitHub Desktop.
public unsafe class UnmanagedMemoryPoolManager
{
#if UNMANAGED_MEMORY_DEBUG
/// <summary>
/// Value added to the end of an <see cref="UnmanagedMemoryPool"/> block. Used to detect
/// out-of-bound memory writes.
/// </summary>
private const ulong SentinelValue = 0x8899AABBCCDDEEFF;
#endif
/// <summary>
/// Allocate a pool of memory. The pool is made up of a fixed number of equal-sized blocks.
/// Allocations from the pool return one of these blocks.
/// </summary>
/// <returns>The allocated pool</returns>
/// <param name="blockSize">Size of each block, in bytes</param>
/// <param name="numBlocks">The number of blocks in the pool</param>
public static UnmanagedMemoryPool AllocPool(int blockSize, int numBlocks)
{
#if UNMANAGED_MEMORY_DEBUG
// Add room for the sentinel
blockSize += sizeof(ulong);
#endif
var pool = new UnmanagedMemoryPool();
pool.Free = null;
pool.NumBlocks = numBlocks;
pool.BlockSize = blockSize;
// Allocate unmanaged memory large enough to fit all the blocks
pool.Alloc = (byte*)UnsafeUtility.Alloc(blockSize * numBlocks);
#if UNMANAGED_MEMORY_DEBUG
{
// Set the sentinel value at the end of each block
byte* pCur = pool.Alloc + blockSize - sizeof(ulong);
for (int i = 0; i < numBlocks; ++i)
{
*((ulong*)pCur) = SentinelValue;
pCur += blockSize;
}
}
#endif
// Reset the free list
FreeAll(&pool);
return pool;
}
/// <summary>
/// Allocate a block of memory from a pool
/// </summary>
/// <param name="pool">Pool to allocate from</param>
public static void* Alloc(UnmanagedMemoryPool* pool)
{
void* pRet = pool->Free;
// Make sure the sentinel is still intact
#if UNMANAGED_MEMORY_DEBUG
if (*((ulong*)(((byte*)pRet)+pool->BlockSize-sizeof(ulong))) != SentinelValue)
Assert(false, "UnmanagedMemoryExceptionType.SentinelOverwritten", pRet);
#endif
// Return the head of the free list and advance the free list pointer
pool->Free = *((byte**)pool->Free);
#if UNMANAGED_MEMORY_DEBUG
*((ulong*)(((byte*)pRet)+pool->BlockSize-sizeof(ulong))) = SentinelValue;
#endif
return pRet;
}
/// <summary>
/// Free a block from a pool
/// </summary>
/// <param name="pool">Pool the block is from</param>
/// <param name="ptr">Pointer to the block to free. If null, this is a no-op.</param>
public static void Free(UnmanagedMemoryPool* pool, void* ptr)
{
// Freeing a null pointer is a no-op, not an error
if (ptr != null)
{
// Pointer must be in the pool and on a block boundary
Debug.Insist.IsTrue(
ptr >= pool->Alloc
&& ptr < pool->Alloc + pool->BlockSize * pool->NumBlocks
&& (((uint)((byte*)ptr - pool->Alloc)) % pool->BlockSize) == 0,
"UnmanagedMemoryExceptionType.PointerDoesNotPointToBlockInPool"
);
// Make sure the sentinel is still intact for this block and the one before it
#if UNMANAGED_MEMORY_DEBUG
if (*((ulong*)(((byte*)ptr)+pool->BlockSize-sizeof(ulong))) != SentinelValue)
{
Assert(false, UnmanagedMemoryExceptionType.SentinelOverwritten, ptr );
}
if (ptr != pool->Alloc && *((ulong*)(((byte*)ptr)-sizeof(ulong))) != SentinelValue)
{
Assert(false, UnmanagedMemoryExceptionType.SentinelOverwritten, (((byte*)ptr)-sizeof(ulong)));
}
#endif
// Insert the block to free at the start of the free list
void** pHead = (void**)ptr;
*pHead = pool->Free;
pool->Free = pHead;
}
}
/// <summary>
/// Free all the blocks of a pool. This does not free the pool itself, but rather makes all of
/// its blocks available for allocation again.
/// </summary>
/// <param name="pool">Pool whose blocks should be freed</param>
public static void FreeAll(UnmanagedMemoryPool* pool)
{
// Point each block except the last one to the next block. Check their sentinels while we're at it.
void** pCur = (void**)pool->Alloc;
byte* pNext = pool->Alloc + pool->BlockSize;
#if UNMANAGED_MEMORY_DEBUG
byte* pSentinel = pool->Alloc + pool->BlockSize - sizeof(ulong);
#endif
for (int i = 0, count = pool->NumBlocks - 1; i < count; ++i)
{
#if UNMANAGED_MEMORY_DEBUG
if (*((ulong*)pSentinel) != SentinelValue)
{
Assert(false, UnmanagedMemoryExceptionType.SentinelOverwritten, pCur);
}
pSentinel += pool->BlockSize;
#endif
*pCur = pNext;
pCur = (void**)pNext;
pNext += pool->BlockSize;
}
// Check the last block's sentinel.
#if UNMANAGED_MEMORY_DEBUG
if (*((ulong*)pSentinel) != SentinelValue)
Assert(false, UnmanagedMemoryExceptionType.SentinelOverwritten, pCur);
#endif
// Point the last block to null
*pCur = default(void*);
// The first block is now the head of the free list
pool->Free = pool->Alloc;
}
/// <summary>
/// Free a pool and all of its blocks. Double-freeing a pool is a no-op.
/// </summary>
/// <param name="pool">Pool to free</param>
public static void FreePool(UnmanagedMemoryPool* pool)
{
// Free the unmanaged memory for all the blocks and set to null to allow double-Destroy()
UnsafeUtility.Free((IntPtr)pool->Alloc);
pool->Alloc = null;
pool->Free = null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment