BsStaticAlloc.h 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. #pragma once
  2. namespace BansheeEngine
  3. {
  4. /** @cond INTERNAL */
  5. /** @addtogroup Memory
  6. * @{
  7. */
  8. /**
  9. * Static allocator that attempts to perform zero heap allocations by always keeping an active stack-allocated buffer.
  10. * If the size of allocated data goes over the set limit dynamic allocations will occur however.
  11. *
  12. * @note This kind of allocator is only able to free all of its memory at once. Freeing individual elements
  13. * will not free the memory until a call to clear().
  14. *
  15. * @tparam BlockSize Size of the initially allocated static block, and minimum size of any dynamically allocated memory.
  16. * @tparam MaxDynamicMemory Maximum amount of unused memory allowed in the buffer after a call to clear(). Keeping active dynamic
  17. * buffers can help prevent further memory allocations at the cost of memory. This is not relevant
  18. * if you stay within the bounds of the statically allocated memory.
  19. */
  20. template<int BlockSize = 512, int MaxDynamicMemory = 512>
  21. class StaticAlloc
  22. {
  23. private:
  24. /** A single block of memory within a static allocator. */
  25. class MemBlock
  26. {
  27. public:
  28. MemBlock(UINT8* data, UINT32 size)
  29. :mData(data), mFreePtr(0), mSize(size),
  30. mPrevBlock(nullptr), mNextBlock(nullptr)
  31. { }
  32. /** Allocates a piece of memory within the block. Caller must ensure the block has enough empty space. */
  33. UINT8* alloc(UINT32 amount)
  34. {
  35. UINT8* freePtr = &mData[mFreePtr];
  36. mFreePtr += amount;
  37. return freePtr;
  38. }
  39. /** Releases all allocations within a block but doesn't actually free the memory. */
  40. void clear()
  41. {
  42. mFreePtr = 0;
  43. }
  44. UINT8* mData;
  45. UINT32 mFreePtr;
  46. UINT32 mSize;
  47. MemBlock* mPrevBlock;
  48. MemBlock* mNextBlock;
  49. };
  50. public:
  51. StaticAlloc()
  52. :mStaticBlock(mStaticData, BlockSize), mFreeBlock(&mStaticBlock),
  53. mTotalAllocBytes(0)
  54. {
  55. }
  56. ~StaticAlloc()
  57. {
  58. assert(mFreeBlock == &mStaticBlock && mStaticBlock.mFreePtr == 0);
  59. freeBlocks(mFreeBlock);
  60. }
  61. /**
  62. * Allocates a new piece of memory of the specified size.
  63. *
  64. * @param[in] amount Amount of memory to allocate, in bytes.
  65. */
  66. UINT8* alloc(UINT32 amount)
  67. {
  68. if (amount == 0)
  69. return nullptr;
  70. #if BS_DEBUG_MODE
  71. amount += sizeof(UINT32);
  72. #endif
  73. UINT32 freeMem = mFreeBlock->mSize - mFreeBlock->mFreePtr;
  74. if (amount > freeMem)
  75. allocBlock(amount);
  76. UINT8* data = mFreeBlock->alloc(amount);
  77. #if BS_DEBUG_MODE
  78. mTotalAllocBytes += amount;
  79. UINT32* storedSize = reinterpret_cast<UINT32*>(data);
  80. *storedSize = amount;
  81. return data + sizeof(UINT32);
  82. #else
  83. return data;
  84. #endif
  85. }
  86. /** Deallocates a previously allocated piece of memory. */
  87. void free(void* data)
  88. {
  89. if (data == nullptr)
  90. return;
  91. // Dealloc is only used for debug and can be removed if needed. All the actual deallocation
  92. // happens in clear()
  93. #if BS_DEBUG_MODE
  94. UINT8* dataPtr = (UINT8*)data;
  95. dataPtr -= sizeof(UINT32);
  96. UINT32* storedSize = (UINT32*)(dataPtr);
  97. mTotalAllocBytes -= *storedSize;
  98. #endif
  99. }
  100. /**
  101. * Allocates enough memory to hold the object(s) of specified type using the static allocator, and constructs them.
  102. */
  103. template<class T>
  104. T* construct(UINT32 count = 0)
  105. {
  106. T* data = (T*)alloc(sizeof(T) * count);
  107. for(unsigned int i = 0; i < count; i++)
  108. new ((void*)&data[i]) T;
  109. return data;
  110. }
  111. /**
  112. * Allocates enough memory to hold the object(s) of specified type using the static allocator, and constructs them.
  113. */
  114. template<class T, class... Args>
  115. T* construct(Args &&...args, UINT32 count = 0)
  116. {
  117. T* data = (T*)alloc(sizeof(T) * count);
  118. for(unsigned int i = 0; i < count; i++)
  119. new ((void*)&data[i]) T(std::forward<Args>(args)...);
  120. return data;
  121. }
  122. /** Destructs and deallocates an object allocated with the static allocator. */
  123. template<class T>
  124. void destruct(T* data)
  125. {
  126. data->~T();
  127. free(data);
  128. }
  129. /** Destructs and deallocates an array of objects allocated with the static frame allocator. */
  130. template<class T>
  131. void destruct(T* data, UINT32 count)
  132. {
  133. for(unsigned int i = 0; i < count; i++)
  134. data[i].~T();
  135. free(data);
  136. }
  137. /** Frees the internal memory buffers. All external allocations must be freed before calling this. */
  138. void clear()
  139. {
  140. assert(mTotalAllocBytes == 0);
  141. MemBlock* dynamicBlock = mStaticBlock.mNextBlock;
  142. INT32 totalDynamicMemAmount = 0;
  143. UINT32 numDynamicBlocks = 0;
  144. while (dynamicBlock != nullptr)
  145. {
  146. totalDynamicMemAmount += dynamicBlock->mFreePtr;
  147. dynamicBlock->clear();
  148. dynamicBlock = dynamicBlock->mNextBlock;
  149. numDynamicBlocks++;
  150. }
  151. mFreeBlock = &mStaticBlock;
  152. mStaticBlock.clear();
  153. if (numDynamicBlocks > 1)
  154. {
  155. freeBlocks(&mStaticBlock);
  156. allocBlock(std::min(totalDynamicMemAmount, MaxDynamicMemory));
  157. mFreeBlock = &mStaticBlock;
  158. }
  159. else if (numDynamicBlocks == 1 && MaxDynamicMemory == 0)
  160. {
  161. freeBlocks(&mStaticBlock);
  162. }
  163. }
  164. private:
  165. UINT8 mStaticData[BlockSize];
  166. MemBlock mStaticBlock;
  167. MemBlock* mFreeBlock;
  168. UINT32 mTotalAllocBytes;
  169. /**
  170. * Allocates a dynamic block of memory of the wanted size. The exact allocation size might be slightly higher in
  171. * order to store block meta data.
  172. */
  173. MemBlock* allocBlock(UINT32 wantedSize)
  174. {
  175. UINT32 blockSize = BlockSize;
  176. if (wantedSize > blockSize)
  177. blockSize = wantedSize;
  178. MemBlock* dynamicBlock = mFreeBlock->mNextBlock;
  179. MemBlock* newBlock = nullptr;
  180. while (dynamicBlock != nullptr)
  181. {
  182. if (dynamicBlock->mSize >= blockSize)
  183. {
  184. newBlock = dynamicBlock;
  185. break;
  186. }
  187. dynamicBlock = dynamicBlock->mNextBlock;
  188. }
  189. if (newBlock == nullptr)
  190. {
  191. UINT8* data = (UINT8*)reinterpret_cast<UINT8*>(bs_alloc(blockSize + sizeof(MemBlock)));
  192. newBlock = new (data)MemBlock(data + sizeof(MemBlock), blockSize);
  193. newBlock->mPrevBlock = mFreeBlock;
  194. mFreeBlock->mNextBlock = newBlock;
  195. }
  196. mFreeBlock = newBlock;
  197. return newBlock;
  198. }
  199. /** Releases memory for any dynamic blocks following the provided block (if there are any). */
  200. void freeBlocks(MemBlock* start)
  201. {
  202. MemBlock* dynamicBlock = start->mNextBlock;
  203. while (dynamicBlock != nullptr)
  204. {
  205. MemBlock* nextBlock = dynamicBlock->mNextBlock;
  206. dynamicBlock->~MemBlock();
  207. bs_free(dynamicBlock);
  208. dynamicBlock = nextBlock;
  209. }
  210. start->mNextBlock = nullptr;
  211. }
  212. };
  213. /** @} */
  214. /** @endcond */
  215. }