Created
June 6, 2026 20:12
-
-
Save prime31/4bbb4ee8ab1824c4906e3da415ad6b4d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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