CmMemStack.h 4.9 KB

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