BsStaticAlloc.h 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. #pragma once
  2. namespace BansheeEngine
  3. {
  4. /**
  5. * @brief Static allocator that attempts to perform zero heap allocations by always keeping an active
  6. * stack-allocated buffer. If the size of allocated data goes over the set limit dynamic allocations
  7. * will occur however.
  8. *
  9. * @note This kind of allocator is only able to free all of its memory at once. Freeing individual elements
  10. * will not free the memory until a call to ::clear.
  11. *
  12. * @tparam BlockSize Size of the initially allocated static block, and minimum size of any dynamically allocated memory.
  13. * @tparam MaxDynamicMemory Maximum amount of unused memory allowed in the buffer after a call to ::clear. Keeping active dynamic
  14. * buffers can help prevent further memory allocations at the cost of memory. This is not relevant
  15. * if you stay within the bounds of the statically allocated memory.
  16. */
  17. template<int BlockSize = 512, int MaxDynamicMemory = 512>
  18. class StaticAlloc
  19. {
  20. private:
  21. /**
  22. * @brief A single block of memory within a static allocator.
  23. */
  24. class MemBlock
  25. {
  26. public:
  27. MemBlock(UINT8* data, UINT32 size)
  28. :mData(data), mFreePtr(0), mSize(size),
  29. mPrevBlock(nullptr), mNextBlock(nullptr)
  30. { }
  31. /**
  32. * @brief Allocates a piece of memory within the block. Caller must ensure
  33. * the block has enough empty space.
  34. */
  35. UINT8* alloc(UINT32 amount)
  36. {
  37. UINT8* freePtr = &mData[mFreePtr];
  38. mFreePtr += amount;
  39. return freePtr;
  40. }
  41. /**
  42. * @brief Releases all allocations within a block but doesn't actually free the memory.
  43. */
  44. void clear()
  45. {
  46. mFreePtr = 0;
  47. }
  48. UINT8* mData;
  49. UINT32 mFreePtr;
  50. UINT32 mSize;
  51. MemBlock* mPrevBlock;
  52. MemBlock* mNextBlock;
  53. };
  54. public:
  55. StaticAlloc()
  56. :mStaticBlock(mStaticData, BlockSize), mFreeBlock(&mStaticBlock),
  57. mTotalAllocBytes(0)
  58. {
  59. }
  60. ~StaticAlloc()
  61. {
  62. assert(mFreeBlock == &mStaticBlock && mStaticBlock.mFreePtr == 0);
  63. freeBlocks(mFreeBlock);
  64. }
  65. /**
  66. * @brief Allocates a new piece of memory of the specified size.
  67. *
  68. * @param amount Amount of memory to allocate, in bytes.
  69. */
  70. UINT8* alloc(UINT32 amount)
  71. {
  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. /**
  89. * @brief Deallocates a previously allocated piece of memory.
  90. */
  91. void free(void* data)
  92. {
  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. void clear()
  103. {
  104. assert(mTotalAllocBytes == 0);
  105. MemBlock* dynamicBlock = mStaticBlock.mNextBlock;
  106. INT32 totalDynamicMemAmount = 0;
  107. UINT32 numDynamicBlocks = 0;
  108. while (dynamicBlock != nullptr)
  109. {
  110. totalDynamicMemAmount += dynamicBlock->mFreePtr;
  111. dynamicBlock->clear();
  112. dynamicBlock = dynamicBlock->mNextBlock;
  113. numDynamicBlocks++;
  114. }
  115. mFreeBlock = &mStaticBlock;
  116. mStaticBlock.clear();
  117. if (numDynamicBlocks > 1)
  118. {
  119. freeBlocks(&mStaticBlock);
  120. allocBlock(std::min(totalDynamicMemAmount, MaxDynamicMemory));
  121. mFreeBlock = &mStaticBlock;
  122. }
  123. else if (numDynamicBlocks == 1 && MaxDynamicMemory == 0)
  124. {
  125. freeBlocks(&mStaticBlock);
  126. }
  127. }
  128. private:
  129. UINT8 mStaticData[BlockSize];
  130. MemBlock mStaticBlock;
  131. MemBlock* mFreeBlock;
  132. UINT32 mTotalAllocBytes;
  133. /**
  134. * @brief Allocates a dynamic block of memory of the wanted size. The exact allocation size
  135. * might be slightly higher in order to store block meta data.
  136. */
  137. MemBlock* allocBlock(UINT32 wantedSize)
  138. {
  139. UINT32 blockSize = BlockSize;
  140. if (wantedSize > blockSize)
  141. blockSize = wantedSize;
  142. MemBlock* dynamicBlock = mFreeBlock->mNextBlock;
  143. MemBlock* newBlock = nullptr;
  144. while (dynamicBlock != nullptr)
  145. {
  146. if (dynamicBlock->mSize >= blockSize)
  147. {
  148. newBlock = dynamicBlock;
  149. break;
  150. }
  151. dynamicBlock = dynamicBlock->mNextBlock;
  152. }
  153. if (newBlock == nullptr)
  154. {
  155. UINT8* data = (UINT8*)reinterpret_cast<UINT8*>(bs_alloc(blockSize + sizeof(MemBlock)));
  156. newBlock = new (data)MemBlock(data + sizeof(MemBlock), blockSize);
  157. newBlock->mPrevBlock = mFreeBlock;
  158. mFreeBlock->mNextBlock = newBlock;
  159. }
  160. mFreeBlock = newBlock;
  161. return newBlock;
  162. }
  163. /**
  164. * @brief Releases memory for any dynamic blocks following the
  165. * provided block (if there are any).
  166. */
  167. void freeBlocks(MemBlock* start)
  168. {
  169. MemBlock* dynamicBlock = start->mNextBlock;
  170. while (dynamicBlock != nullptr)
  171. {
  172. MemBlock* nextBlock = dynamicBlock->mNextBlock;
  173. dynamicBlock->~MemBlock();
  174. bs_free(dynamicBlock);
  175. dynamicBlock = nextBlock;
  176. }
  177. start->mNextBlock = nullptr;
  178. }
  179. };
  180. }