2
0

CmMemStack.h 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. #pragma once
  2. #include <stack>
  3. #include <assert.h>
  4. #include "CmStdHeaders.h"
  5. #include "CmThreadDefines.h"
  6. namespace CamelotFramework
  7. {
  8. /**
  9. * @brief Memory stack.
  10. *
  11. * @tparam BlockCapacity Minimum size of a block. Larger blocks mean less memory allocations, but also potentially
  12. * more wasted memory. If an allocation requests more bytes than BlockCapacity, first largest multiple is
  13. * used instead.
  14. */
  15. template <int BlockCapacity = 1024 * 1024>
  16. class MemStackInternal
  17. {
  18. private:
  19. class MemBlock
  20. {
  21. public:
  22. MemBlock(UINT32 size)
  23. :mData(nullptr), mFreePtr(0), mSize(size)
  24. { }
  25. ~MemBlock()
  26. { }
  27. UINT8* alloc(UINT8 amount)
  28. {
  29. UINT8* freePtr = &mData[mFreePtr];
  30. mFreePtr += amount;
  31. return freePtr;
  32. }
  33. void dealloc(UINT8* data, UINT8 amount)
  34. {
  35. mFreePtr -= amount;
  36. assert((&mData[mFreePtr]) == data && "Out of order stack deallocation detected. Deallocations need to happen in order opposite of allocations.");
  37. }
  38. UINT8* mData;
  39. UINT32 mFreePtr;
  40. UINT32 mSize;
  41. };
  42. public:
  43. MemStackInternal()
  44. { }
  45. ~MemStackInternal()
  46. {
  47. assert(mBlocks.size() == 0 && "Not all blocks were released before shutting down the stack allocator.");
  48. while(!mBlocks.empty())
  49. {
  50. MemBlock* curPtr = mBlocks.top();
  51. mBlocks.pop();
  52. deallocBlock(curPtr);
  53. }
  54. }
  55. UINT8* alloc(UINT32 amount)
  56. {
  57. amount += sizeof(UINT32);
  58. MemBlock* topBlock;
  59. if(mBlocks.size() == 0)
  60. topBlock = allocBlock(amount);
  61. else
  62. topBlock = mBlocks.top();
  63. MemBlock* memBlock = nullptr;
  64. UINT32 freeMem = topBlock->mSize - topBlock->mFreePtr;
  65. if(amount <= freeMem)
  66. memBlock = topBlock;
  67. else
  68. memBlock = allocBlock(amount);
  69. UINT8* data = memBlock->alloc(amount);
  70. UINT32* storedSize = reinterpret_cast<UINT32*>(data);
  71. *storedSize = amount;
  72. return data + sizeof(UINT32);
  73. }
  74. void dealloc(UINT8* data)
  75. {
  76. data -= sizeof(UINT32);
  77. UINT32* storedSize = reinterpret_cast<UINT32*>(data);
  78. MemBlock* topBlock = mBlocks.top();
  79. topBlock->dealloc(data, *storedSize);
  80. if(topBlock->mFreePtr == 0)
  81. {
  82. deallocBlock(topBlock);
  83. mBlocks.pop();
  84. }
  85. }
  86. private:
  87. std::stack<MemBlock*> mBlocks;
  88. MemBlock* allocBlock(UINT32 wantedSize)
  89. {
  90. UINT32 blockSize = BlockCapacity;
  91. if(wantedSize > blockSize)
  92. blockSize = wantedSize;
  93. UINT8* data = (UINT8*)reinterpret_cast<UINT8*>(cm_alloc(blockSize + sizeof(MemBlock)));
  94. MemBlock* newBlock = new (data) MemBlock(blockSize);
  95. data += sizeof(MemBlock);
  96. newBlock->mData = data;
  97. mBlocks.push(newBlock);
  98. return newBlock;
  99. }
  100. void deallocBlock(MemBlock* block)
  101. {
  102. block->~MemBlock();
  103. cm_free(block);
  104. }
  105. };
  106. /**
  107. * @brief One of the fastest, but also very limiting type of allocator. All deallocations
  108. * must happen in opposite order from allocations.
  109. *
  110. * @note It's mostly useful when you need to allocate something temporarily on the heap,
  111. * usually something that gets allocated and freed within the same function.
  112. *
  113. * Each allocation comes with a pretty hefty 4 byte memory overhead, so don't use it for small allocations.
  114. *
  115. * This class is thread safe but you cannot allocate on one thread and deallocate on another. Threads will keep
  116. * separate stacks internally. Make sure to call beginThread/endThread for any thread this stack is used on.
  117. */
  118. class MemStack
  119. {
  120. public:
  121. /**
  122. * @brief Sets up the stack with the currently active thread. You need to call this
  123. * on any thread before doing any allocations or deallocations
  124. */
  125. static CM_UTILITY_EXPORT void beginThread();
  126. /**
  127. * @brief Cleans up the stack for the current thread. You may not perform any allocations or deallocations
  128. * after this is called, unless you call beginThread again.
  129. */
  130. static CM_UTILITY_EXPORT void endThread();
  131. static CM_UTILITY_EXPORT UINT8* alloc(UINT32 numBytes);
  132. static CM_UTILITY_EXPORT void deallocLast(UINT8* data);
  133. private:
  134. static CM_THREADLOCAL MemStackInternal<1024 * 1024>* ThreadMemStack;
  135. };
  136. CM_UTILITY_EXPORT inline UINT8* stackAlloc(UINT32 numBytes);
  137. template<class T>
  138. T* stackAlloc()
  139. {
  140. return (T*)MemStack::alloc(sizeof(T));
  141. }
  142. template<class T>
  143. T* stackAllocN(UINT32 count)
  144. {
  145. return (T*)MemStack::alloc(sizeof(T) * count);
  146. }
  147. template<class T>
  148. T* stackConstructN(UINT32 count)
  149. {
  150. T* data = stackAllocN<T>(count);
  151. for(unsigned int i = 0; i < count; i++)
  152. new ((void*)&data[i]) T;
  153. return data;
  154. }
  155. template<class T>
  156. void stackDestruct(T* data)
  157. {
  158. data->~T();
  159. MemStack::deallocLast((UINT8*)data);
  160. }
  161. template<class T>
  162. void stackDestructN(T* data, UINT32 count)
  163. {
  164. for(unsigned int i = 0; i < count; i++)
  165. data[i].~T();
  166. MemStack::deallocLast((UINT8*)data);
  167. }
  168. CM_UTILITY_EXPORT inline void stackDeallocLast(void* data);
  169. }