BsPoolAlloc.h 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2017 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #pragma once
  4. #include "Prerequisites/BsPrerequisitesUtil.h"
  5. namespace bs
  6. {
  7. /** @addtogroup Internal-Utility
  8. * @{
  9. */
  10. /** @addtogroup Memory-Internal
  11. * @{
  12. */
  13. /**
  14. * A memory allocator that allocates elements of the same size. Allows for fairly quick allocations and deallocations.
  15. *
  16. * @tparam ElemSize Size of a single element in the pool. This will be the exact allocation size. 4 byte minimum.
  17. * @tparam ElemsPerBlock Determines how much space to reserve for elements. This determines the initial size of the
  18. * pool, and the additional size the pool will be expanded by every time the number of elements
  19. * goes over the available storage limit.
  20. * @tparam Alignment Memory alignment of each allocated element. Note that alignments that are larger than
  21. * element size, or aren't a multiplier of element size will introduce additionally padding
  22. * for each element, and therefore require more internal memory.
  23. */
  24. template <int ElemSize, int ElemsPerBlock = 512, int Alignment = 4>
  25. class PoolAlloc
  26. {
  27. private:
  28. /** A single block able to hold ElemsPerBlock elements. */
  29. class MemBlock
  30. {
  31. public:
  32. MemBlock(UINT8* blockData)
  33. :blockData(blockData), freePtr(0), freeElems(ElemsPerBlock), nextBlock(nullptr)
  34. {
  35. UINT32 offset = 0;
  36. for(UINT32 i = 0; i < ElemsPerBlock; i++)
  37. {
  38. UINT32* entryPtr = (UINT32*)&blockData[offset];
  39. offset += ActualElemSize;
  40. *entryPtr = offset;
  41. }
  42. }
  43. ~MemBlock()
  44. {
  45. assert(freeElems == ElemsPerBlock && "Not all elements were deallocated from a block.");
  46. }
  47. /**
  48. * Returns the first free address and increments the free pointer. Caller needs to ensure the remaining block
  49. * size is adequate before calling.
  50. */
  51. UINT8* alloc()
  52. {
  53. UINT8* freeEntry = &blockData[freePtr];
  54. freePtr = *(UINT32*)freeEntry;
  55. --freeElems;
  56. return freeEntry;
  57. }
  58. /** Deallocates the provided pointer. */
  59. void dealloc(void* data)
  60. {
  61. UINT32* entryPtr = (UINT32*)data;
  62. *entryPtr = freePtr;
  63. ++freeElems;
  64. freePtr = (UINT32)(((UINT8*)data) - blockData);
  65. }
  66. UINT8* blockData;
  67. UINT32 freePtr;
  68. UINT32 freeElems;
  69. MemBlock* nextBlock;
  70. };
  71. public:
  72. PoolAlloc()
  73. : mFreeBlock(nullptr), mTotalNumElems(0), mNumBlocks(0)
  74. {
  75. static_assert(ElemSize >= 4, "Pool allocator minimum allowed element size is 4 bytes.");
  76. static_assert(ElemsPerBlock > 0, "Number of elements per block must be at least 1.");
  77. static_assert(ElemsPerBlock * ActualElemSize <= UINT_MAX, "Pool allocator block size too large.");
  78. }
  79. ~PoolAlloc()
  80. {
  81. MemBlock* curBlock = mFreeBlock;
  82. while (curBlock != nullptr)
  83. {
  84. MemBlock* nextBlock = curBlock->nextBlock;
  85. deallocBlock(curBlock);
  86. curBlock = nextBlock;
  87. }
  88. }
  89. /** Allocates enough memory for a single element in the pool. */
  90. UINT8* alloc()
  91. {
  92. if(mFreeBlock == nullptr || mFreeBlock->freeElems == 0)
  93. allocBlock();
  94. mTotalNumElems++;
  95. return mFreeBlock->alloc();
  96. }
  97. /** Deallocates an element from the pool. */
  98. void free(void* data)
  99. {
  100. MemBlock* curBlock = mFreeBlock;
  101. while(curBlock)
  102. {
  103. constexpr UINT32 blockDataSize = ActualElemSize * ElemsPerBlock;
  104. if(data >= curBlock->blockData && data < (curBlock->blockData + blockDataSize))
  105. {
  106. curBlock->dealloc(data);
  107. mTotalNumElems--;
  108. if(curBlock->freeElems == 0 && curBlock->nextBlock)
  109. {
  110. // Free the block, but only if there is some extra free space in other blocks
  111. const UINT32 totalSpace = (mNumBlocks - 1) * ElemsPerBlock;
  112. const UINT32 freeSpace = totalSpace - mTotalNumElems;
  113. if(freeSpace > ElemsPerBlock / 2)
  114. {
  115. mFreeBlock = curBlock->nextBlock;
  116. deallocBlock(curBlock);
  117. }
  118. }
  119. return;
  120. }
  121. curBlock = curBlock->nextBlock;
  122. }
  123. assert(false);
  124. }
  125. /** Allocates and constructs a single pool element. */
  126. template<class T, class... Args>
  127. T* construct(Args &&...args)
  128. {
  129. T* data = (T*)alloc();
  130. new ((void*)data) T(std::forward<Args>(args)...);
  131. return data;
  132. }
  133. /** Destructs and deallocates a single pool element. */
  134. template<class T>
  135. void destruct(T* data)
  136. {
  137. data->~T();
  138. free(data);
  139. }
  140. private:
  141. /** Allocates a new block of memory using a heap allocator. */
  142. MemBlock* allocBlock()
  143. {
  144. MemBlock* newBlock = nullptr;
  145. MemBlock* curBlock = mFreeBlock;
  146. while (curBlock != nullptr)
  147. {
  148. MemBlock* nextBlock = curBlock->nextBlock;
  149. if (nextBlock != nullptr && nextBlock->freeElems > 0)
  150. {
  151. // Found an existing block with free space
  152. newBlock = nextBlock;
  153. curBlock->nextBlock = newBlock->nextBlock;
  154. newBlock->nextBlock = mFreeBlock;
  155. break;
  156. }
  157. curBlock = nextBlock;
  158. }
  159. if (newBlock == nullptr)
  160. {
  161. constexpr UINT32 blockDataSize = ActualElemSize * ElemsPerBlock;
  162. size_t paddedBlockDataSize = blockDataSize + (Alignment - 1); // Padding for potential alignment correction
  163. UINT8* data = (UINT8*)bs_alloc(sizeof(MemBlock) + (UINT32)paddedBlockDataSize);
  164. void* blockData = data + sizeof(MemBlock);
  165. blockData = std::align(Alignment, blockDataSize, blockData, paddedBlockDataSize);
  166. newBlock = new (data) MemBlock((UINT8*)blockData);
  167. mNumBlocks++;
  168. newBlock->nextBlock = mFreeBlock;
  169. }
  170. mFreeBlock = newBlock;
  171. return newBlock;
  172. }
  173. /** Deallocates a block of memory. */
  174. void deallocBlock(MemBlock* block)
  175. {
  176. block->~MemBlock();
  177. bs_free(block);
  178. mNumBlocks--;
  179. }
  180. static constexpr int ActualElemSize = ((ElemSize + Alignment - 1) / Alignment) * Alignment;
  181. MemBlock* mFreeBlock;
  182. UINT32 mTotalNumElems;
  183. UINT32 mNumBlocks;
  184. };
  185. /** @} */
  186. /** @} */
  187. }