BsMemStack.h 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #pragma once
  4. #include <stack>
  5. #include <assert.h>
  6. #include "BsStdHeaders.h"
  7. #include "BsThreadDefines.h"
  8. namespace BansheeEngine
  9. {
  10. /** @addtogroup Memory
  11. * @{
  12. */
  13. /** @cond INTERNAL */
  14. /**
  15. * Describes a memory stack of a certain block capacity. See ::MemStack for more information.
  16. *
  17. * @tparam BlockCapacity Minimum size of a block. Larger blocks mean less memory allocations, but also potentially
  18. * more wasted memory. If an allocation requests more bytes than BlockCapacity, first largest
  19. * multiple is used instead.
  20. */
  21. template <int BlockCapacity = 1024 * 1024>
  22. class MemStackInternal
  23. {
  24. private:
  25. /** A single block of memory of BlockCapacity size. A pointer to the first free address is stored, and a remaining
  26. * size.
  27. */
  28. class MemBlock
  29. {
  30. public:
  31. MemBlock(UINT32 size)
  32. :mData(nullptr), mFreePtr(0), mSize(size),
  33. mNextBlock(nullptr), mPrevBlock(nullptr)
  34. { }
  35. ~MemBlock()
  36. { }
  37. /**
  38. * Returns the first free address and increments the free pointer. Caller needs to ensure the remaining block
  39. * size is adequate before calling.
  40. */
  41. UINT8* alloc(UINT32 amount)
  42. {
  43. UINT8* freePtr = &mData[mFreePtr];
  44. mFreePtr += amount;
  45. return freePtr;
  46. }
  47. /**
  48. * Deallocates the provided pointer. Deallocation must happen in opposite order from allocation otherwise
  49. * corruption will occur.
  50. *
  51. * @note Pointer to @p data isn't actually needed, but is provided for debug purposes in order to more
  52. * easily track out-of-order deallocations.
  53. */
  54. void dealloc(UINT8* data, UINT32 amount)
  55. {
  56. mFreePtr -= amount;
  57. assert((&mData[mFreePtr]) == data && "Out of order stack deallocation detected. Deallocations need to happen in order opposite of allocations.");
  58. }
  59. UINT8* mData;
  60. UINT32 mFreePtr;
  61. UINT32 mSize;
  62. MemBlock* mNextBlock;
  63. MemBlock* mPrevBlock;
  64. };
  65. public:
  66. MemStackInternal()
  67. :mFreeBlock(nullptr)
  68. {
  69. mFreeBlock = allocBlock(BlockCapacity);
  70. }
  71. ~MemStackInternal()
  72. {
  73. assert(mFreeBlock->mFreePtr == 0 && "Not all blocks were released before shutting down the stack allocator.");
  74. MemBlock* curBlock = mFreeBlock;
  75. while (curBlock != nullptr)
  76. {
  77. MemBlock* nextBlock = curBlock->mNextBlock;
  78. deallocBlock(curBlock);
  79. curBlock = nextBlock;
  80. }
  81. }
  82. /**
  83. * Allocates the given amount of memory on the stack.
  84. *
  85. * @param[in] amount The amount to allocate in bytes.
  86. *
  87. * @note
  88. * Allocates the memory in the currently active block if it is large enough, otherwise a new block is allocated.
  89. * If the allocation is larger than default block size a separate block will be allocated only for that allocation,
  90. * making it essentially a slower heap allocator.
  91. * @note
  92. * Each allocation comes with a 4 byte overhead.
  93. */
  94. UINT8* alloc(UINT32 amount)
  95. {
  96. amount += sizeof(UINT32);
  97. UINT32 freeMem = mFreeBlock->mSize - mFreeBlock->mFreePtr;
  98. if(amount > freeMem)
  99. allocBlock(amount);
  100. UINT8* data = mFreeBlock->alloc(amount);
  101. UINT32* storedSize = reinterpret_cast<UINT32*>(data);
  102. *storedSize = amount;
  103. return data + sizeof(UINT32);
  104. }
  105. /** Deallocates the given memory. Data must be deallocated in opposite order then when it was allocated. */
  106. void dealloc(UINT8* data)
  107. {
  108. data -= sizeof(UINT32);
  109. UINT32* storedSize = reinterpret_cast<UINT32*>(data);
  110. mFreeBlock->dealloc(data, *storedSize);
  111. if (mFreeBlock->mFreePtr == 0)
  112. {
  113. MemBlock* emptyBlock = mFreeBlock;
  114. if (emptyBlock->mPrevBlock != nullptr)
  115. mFreeBlock = emptyBlock->mPrevBlock;
  116. // Merge with next block
  117. if (emptyBlock->mNextBlock != nullptr)
  118. {
  119. UINT32 totalSize = emptyBlock->mSize + emptyBlock->mNextBlock->mSize;
  120. if (emptyBlock->mPrevBlock != nullptr)
  121. emptyBlock->mPrevBlock->mNextBlock = nullptr;
  122. else
  123. mFreeBlock = nullptr;
  124. deallocBlock(emptyBlock->mNextBlock);
  125. deallocBlock(emptyBlock);
  126. allocBlock(totalSize);
  127. }
  128. }
  129. }
  130. private:
  131. MemBlock* mFreeBlock;
  132. /**
  133. * Allocates a new block of memory using a heap allocator. Block will never be smaller than BlockCapacity no matter
  134. * the @p wantedSize.
  135. */
  136. MemBlock* allocBlock(UINT32 wantedSize)
  137. {
  138. UINT32 blockSize = BlockCapacity;
  139. if(wantedSize > blockSize)
  140. blockSize = wantedSize;
  141. MemBlock* newBlock = nullptr;
  142. MemBlock* curBlock = mFreeBlock;
  143. while (curBlock != nullptr)
  144. {
  145. MemBlock* nextBlock = curBlock->mNextBlock;
  146. if (nextBlock != nullptr && nextBlock->mSize >= blockSize)
  147. {
  148. newBlock = nextBlock;
  149. break;
  150. }
  151. curBlock = nextBlock;
  152. }
  153. if (newBlock == nullptr)
  154. {
  155. UINT8* data = (UINT8*)reinterpret_cast<UINT8*>(bs_alloc(blockSize + sizeof(MemBlock)));
  156. newBlock = new (data)MemBlock(blockSize);
  157. data += sizeof(MemBlock);
  158. newBlock->mData = data;
  159. newBlock->mPrevBlock = mFreeBlock;
  160. if (mFreeBlock != nullptr)
  161. {
  162. if(mFreeBlock->mNextBlock != nullptr)
  163. mFreeBlock->mNextBlock->mPrevBlock = newBlock;
  164. newBlock->mNextBlock = mFreeBlock->mNextBlock;
  165. mFreeBlock->mNextBlock = newBlock;
  166. }
  167. }
  168. mFreeBlock = newBlock;
  169. return newBlock;
  170. }
  171. /** Deallocates a block of memory. */
  172. void deallocBlock(MemBlock* block)
  173. {
  174. block->~MemBlock();
  175. bs_free(block);
  176. }
  177. };
  178. /**
  179. * One of the fastest, but also very limiting type of allocator. All deallocations must happen in opposite order from
  180. * allocations.
  181. *
  182. * @note
  183. * It's mostly useful when you need to allocate something temporarily on the heap, usually something that gets
  184. * allocated and freed within the same method.
  185. * @note
  186. * Each allocation comes with a pretty hefty 4 byte memory overhead, so don't use it for small allocations.
  187. * @note
  188. * Thread safe. But you cannot allocate on one thread and deallocate on another. Threads will keep
  189. * separate stacks internally. Make sure to call beginThread()/endThread() for any thread this stack is used on.
  190. */
  191. class MemStack
  192. {
  193. public:
  194. /**
  195. * Sets up the stack with the currently active thread. You need to call this on any thread before doing any
  196. * allocations or deallocations.
  197. */
  198. static BS_UTILITY_EXPORT void beginThread();
  199. /**
  200. * Cleans up the stack for the current thread. You may not perform any allocations or deallocations after this is
  201. * called, unless you call beginThread again.
  202. */
  203. static BS_UTILITY_EXPORT void endThread();
  204. /** @copydoc MemoryStackInternal::alloc() */
  205. static BS_UTILITY_EXPORT UINT8* alloc(UINT32 numBytes);
  206. /** @copydoc MemoryStackInternal::dealloc() */
  207. static BS_UTILITY_EXPORT void deallocLast(UINT8* data);
  208. private:
  209. static BS_THREADLOCAL MemStackInternal<1024 * 1024>* ThreadMemStack;
  210. };
  211. /** @endcond */
  212. /** @copydoc MemoryStackInternal::alloc() */
  213. BS_UTILITY_EXPORT inline void* bs_stack_alloc(UINT32 numBytes);
  214. /**
  215. * Allocates enough memory to hold the specified type, on the stack, but does not initialize the object.
  216. *
  217. * @see MemoryStackInternal::alloc()
  218. */
  219. template<class T>
  220. T* bs_stack_alloc()
  221. {
  222. return (T*)MemStack::alloc(sizeof(T));
  223. }
  224. /**
  225. * Allocates enough memory to hold N objects of the specified type, on the stack, but does not initialize the objects.
  226. *
  227. * @see MemoryStackInternal::alloc()
  228. */
  229. template<class T>
  230. T* bs_stack_alloc(UINT32 count)
  231. {
  232. return (T*)MemStack::alloc(sizeof(T) * count);
  233. }
  234. /**
  235. * Allocates enough memory to hold the specified type, on the stack, and constructs the object.
  236. *
  237. * @see MemoryStackInternal::alloc()
  238. */
  239. template<class T>
  240. T* bs_stack_new(UINT32 count = 0)
  241. {
  242. T* data = bs_stack_alloc<T>(count);
  243. for(unsigned int i = 0; i < count; i++)
  244. new ((void*)&data[i]) T;
  245. return data;
  246. }
  247. /**
  248. * Allocates enough memory to hold the specified type, on the stack, and constructs the object.
  249. *
  250. * @see MemoryStackInternal::alloc()
  251. */
  252. template<class T, class... Args>
  253. T* bs_stack_new(Args &&...args, UINT32 count = 0)
  254. {
  255. T* data = bs_stack_alloc<T>(count);
  256. for(unsigned int i = 0; i < count; i++)
  257. new ((void*)&data[i]) T(std::forward<Args>(args)...);
  258. return data;
  259. }
  260. /**
  261. * Destructs and deallocates last allocated entry currently located on stack.
  262. *
  263. * @see MemoryStackInternal::dealloc()
  264. */
  265. template<class T>
  266. void bs_stack_delete(T* data)
  267. {
  268. data->~T();
  269. MemStack::deallocLast((UINT8*)data);
  270. }
  271. /**
  272. * Destructs an array of objects and deallocates last allocated entry currently located on stack.
  273. *
  274. * @see MemoryStackInternal::dealloc()
  275. */
  276. template<class T>
  277. void bs_stack_delete(T* data, UINT32 count)
  278. {
  279. for(unsigned int i = 0; i < count; i++)
  280. data[i].~T();
  281. MemStack::deallocLast((UINT8*)data);
  282. }
  283. /** @copydoc MemoryStackInternal::dealloc() */
  284. BS_UTILITY_EXPORT inline void bs_stack_free(void* data);
  285. /** @cond INTERNAL */
  286. /**
  287. * Allows use of a stack allocator by using normal new/delete/free/dealloc operators.
  288. *
  289. * @see MemStack
  290. */
  291. class StackAlloc
  292. { };
  293. /**
  294. * Specialized memory allocator implementations that allows use of a stack allocator in normal new/delete/free/dealloc
  295. * operators.
  296. *
  297. * @see MemStack
  298. */
  299. template<>
  300. class MemoryAllocator<StackAlloc> : public MemoryAllocatorBase
  301. {
  302. public:
  303. static void* allocate(size_t bytes)
  304. {
  305. return bs_stack_alloc((UINT32)bytes);
  306. }
  307. static void free(void* ptr)
  308. {
  309. bs_stack_free(ptr);
  310. }
  311. };
  312. /** @endcond */
  313. /** @} */
  314. }