BsStaticAlloc.h 6.8 KB

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