|
@@ -15,19 +15,17 @@ namespace bs
|
|
|
/**
|
|
/**
|
|
|
* Static allocator that attempts to perform zero heap (dynamic) allocations by always keeping an active preallocated
|
|
* Static allocator that attempts to perform zero heap (dynamic) allocations by always keeping an active preallocated
|
|
|
* buffer. The allocator provides a fixed amount of preallocated memory, and if the size of the allocated data goes over
|
|
* buffer. The allocator provides a fixed amount of preallocated memory, and if the size of the allocated data goes over
|
|
|
- * that limit the allocator will fall back to dynamic heap allocations.
|
|
|
|
|
|
|
+ * that limit the allocator will fall back to dynamic heap allocations using the selected allocator.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @note Static allocations can only be freed if memory is deallocated in opposite order it is allocated.
|
|
|
|
|
+ * Otherwise static memory gets orphaned until a call to clear(). Dynamic memory allocations behave
|
|
|
|
|
+ * depending on the selected allocator.
|
|
|
*
|
|
*
|
|
|
- * @note This kind of allocator is only able to free all of its memory at once. Freeing individual elements
|
|
|
|
|
- * will not free the memory until a call to clear().
|
|
|
|
|
- *
|
|
|
|
|
* @tparam BlockSize Size of the initially allocated static block, and minimum size of any dynamically
|
|
* @tparam BlockSize Size of the initially allocated static block, and minimum size of any dynamically
|
|
|
* allocated memory.
|
|
* allocated memory.
|
|
|
- * @tparam MaxDynamicMemory Maximum amount of unused memory allowed in the buffer after a call to clear(). Keeping
|
|
|
|
|
- * active dynamic buffers can help prevent further memory allocations at the cost of
|
|
|
|
|
- * memory. This is not relevant if you stay within the bounds of the statically allocated
|
|
|
|
|
- * memory.
|
|
|
|
|
|
|
+ * @tparam DynamicAllocator Allocator to fall-back to when static buffer is full.
|
|
|
*/
|
|
*/
|
|
|
- template<int BlockSize = 512, int MaxDynamicMemory = 512>
|
|
|
|
|
|
|
+ template<int BlockSize = 512, class DynamicAllocator = TFrameAlloc<BlockSize>>
|
|
|
class StaticAlloc
|
|
class StaticAlloc
|
|
|
{
|
|
{
|
|
|
private:
|
|
private:
|
|
@@ -36,8 +34,7 @@ namespace bs
|
|
|
{
|
|
{
|
|
|
public:
|
|
public:
|
|
|
MemBlock(UINT8* data, UINT32 size)
|
|
MemBlock(UINT8* data, UINT32 size)
|
|
|
- :mData(data), mFreePtr(0), mSize(size),
|
|
|
|
|
- mPrevBlock(nullptr), mNextBlock(nullptr)
|
|
|
|
|
|
|
+ :mData(data), mFreePtr(0), mSize(size), mNextBlock(nullptr)
|
|
|
{ }
|
|
{ }
|
|
|
|
|
|
|
|
/** Allocates a piece of memory within the block. Caller must ensure the block has enough empty space. */
|
|
/** Allocates a piece of memory within the block. Caller must ensure the block has enough empty space. */
|
|
@@ -49,6 +46,16 @@ namespace bs
|
|
|
return freePtr;
|
|
return freePtr;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Frees a piece of memory within the block. If the memory isn't the last allocated memory, no deallocation
|
|
|
|
|
+ * happens and that memory is instead orphaned.
|
|
|
|
|
+ */
|
|
|
|
|
+ void free(UINT8* data, UINT32 allocSize)
|
|
|
|
|
+ {
|
|
|
|
|
+ if((data + allocSize) == (mData + mFreePtr))
|
|
|
|
|
+ mFreePtr -= allocSize;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/** Releases all allocations within a block but doesn't actually free the memory. */
|
|
/** Releases all allocations within a block but doesn't actually free the memory. */
|
|
|
void clear()
|
|
void clear()
|
|
|
{
|
|
{
|
|
@@ -58,24 +65,16 @@ namespace bs
|
|
|
UINT8* mData;
|
|
UINT8* mData;
|
|
|
UINT32 mFreePtr;
|
|
UINT32 mFreePtr;
|
|
|
UINT32 mSize;
|
|
UINT32 mSize;
|
|
|
- MemBlock* mPrevBlock;
|
|
|
|
|
MemBlock* mNextBlock;
|
|
MemBlock* mNextBlock;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
public:
|
|
public:
|
|
|
StaticAlloc()
|
|
StaticAlloc()
|
|
|
- :mStaticBlock(mStaticData, BlockSize), mFreeBlock(&mStaticBlock),
|
|
|
|
|
- mTotalAllocBytes(0)
|
|
|
|
|
- {
|
|
|
|
|
-
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ : mFreePtr(0), mTotalAllocBytes(0)
|
|
|
|
|
+ { }
|
|
|
|
|
|
|
|
~StaticAlloc()
|
|
~StaticAlloc()
|
|
|
- {
|
|
|
|
|
- assert(mFreeBlock == &mStaticBlock && mStaticBlock.mFreePtr == 0);
|
|
|
|
|
-
|
|
|
|
|
- freeBlocks(mFreeBlock);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ { }
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Allocates a new piece of memory of the specified size.
|
|
* Allocates a new piece of memory of the specified size.
|
|
@@ -91,11 +90,16 @@ namespace bs
|
|
|
amount += sizeof(UINT32);
|
|
amount += sizeof(UINT32);
|
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
- UINT32 freeMem = mFreeBlock->mSize - mFreeBlock->mFreePtr;
|
|
|
|
|
|
|
+ UINT32 freeMem = BlockSize - mFreePtr;
|
|
|
|
|
+
|
|
|
|
|
+ UINT8* data;
|
|
|
if (amount > freeMem)
|
|
if (amount > freeMem)
|
|
|
- allocBlock(amount);
|
|
|
|
|
-
|
|
|
|
|
- UINT8* data = mFreeBlock->alloc(amount);
|
|
|
|
|
|
|
+ data = mDynamicAlloc.alloc(amount);
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ data = &mStaticData[mFreePtr];
|
|
|
|
|
+ mFreePtr += amount;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
#if BS_DEBUG_MODE
|
|
#if BS_DEBUG_MODE
|
|
|
mTotalAllocBytes += amount;
|
|
mTotalAllocBytes += amount;
|
|
@@ -110,13 +114,33 @@ namespace bs
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** Deallocates a previously allocated piece of memory. */
|
|
/** Deallocates a previously allocated piece of memory. */
|
|
|
- void free(void* data)
|
|
|
|
|
|
|
+ void free(void* data, UINT32 allocSize)
|
|
|
{
|
|
{
|
|
|
if (data == nullptr)
|
|
if (data == nullptr)
|
|
|
return;
|
|
return;
|
|
|
|
|
|
|
|
- // Dealloc is only used for debug and can be removed if needed. All the actual deallocation
|
|
|
|
|
- // happens in clear()
|
|
|
|
|
|
|
+ UINT8* dataPtr = (UINT8*)data;
|
|
|
|
|
+#if BS_DEBUG_MODE
|
|
|
|
|
+ dataPtr -= sizeof(UINT32);
|
|
|
|
|
+
|
|
|
|
|
+ UINT32* storedSize = (UINT32*)(dataPtr);
|
|
|
|
|
+ mTotalAllocBytes -= *storedSize;
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+ if(data > mStaticData && data < (mStaticData + BlockSize))
|
|
|
|
|
+ {
|
|
|
|
|
+ if((((UINT8*)data) + allocSize) == (mStaticData + mFreePtr))
|
|
|
|
|
+ mFreePtr -= allocSize;
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ mDynamicAlloc.free(dataPtr);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Deallocates a previously allocated piece of memory. */
|
|
|
|
|
+ void free(void* data)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (data == nullptr)
|
|
|
|
|
+ return;
|
|
|
|
|
|
|
|
#if BS_DEBUG_MODE
|
|
#if BS_DEBUG_MODE
|
|
|
UINT8* dataPtr = (UINT8*)data;
|
|
UINT8* dataPtr = (UINT8*)data;
|
|
@@ -125,6 +149,8 @@ namespace bs
|
|
|
UINT32* storedSize = (UINT32*)(dataPtr);
|
|
UINT32* storedSize = (UINT32*)(dataPtr);
|
|
|
mTotalAllocBytes -= *storedSize;
|
|
mTotalAllocBytes -= *storedSize;
|
|
|
#endif
|
|
#endif
|
|
|
|
|
+ if(data < mStaticData || data >= (mStaticData + BlockSize))
|
|
|
|
|
+ mDynamicAlloc.free(dataPtr);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -161,7 +187,7 @@ namespace bs
|
|
|
{
|
|
{
|
|
|
data->~T();
|
|
data->~T();
|
|
|
|
|
|
|
|
- free(data);
|
|
|
|
|
|
|
+ free(data, sizeof(T));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** Destructs and deallocates an array of objects allocated with the static frame allocator. */
|
|
/** Destructs and deallocates an array of objects allocated with the static frame allocator. */
|
|
@@ -171,7 +197,7 @@ namespace bs
|
|
|
for(unsigned int i = 0; i < count; i++)
|
|
for(unsigned int i = 0; i < count; i++)
|
|
|
data[i].~T();
|
|
data[i].~T();
|
|
|
|
|
|
|
|
- free(data);
|
|
|
|
|
|
|
+ free(data, sizeof(T) * count);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** Frees the internal memory buffers. All external allocations must be freed before calling this. */
|
|
/** Frees the internal memory buffers. All external allocations must be freed before calling this. */
|
|
@@ -179,94 +205,106 @@ namespace bs
|
|
|
{
|
|
{
|
|
|
assert(mTotalAllocBytes == 0);
|
|
assert(mTotalAllocBytes == 0);
|
|
|
|
|
|
|
|
- MemBlock* dynamicBlock = mStaticBlock.mNextBlock;
|
|
|
|
|
- INT32 totalDynamicMemAmount = 0;
|
|
|
|
|
- UINT32 numDynamicBlocks = 0;
|
|
|
|
|
-
|
|
|
|
|
- while (dynamicBlock != nullptr)
|
|
|
|
|
- {
|
|
|
|
|
- totalDynamicMemAmount += dynamicBlock->mFreePtr;
|
|
|
|
|
- dynamicBlock->clear();
|
|
|
|
|
-
|
|
|
|
|
- dynamicBlock = dynamicBlock->mNextBlock;
|
|
|
|
|
- numDynamicBlocks++;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- mFreeBlock = &mStaticBlock;
|
|
|
|
|
- mStaticBlock.clear();
|
|
|
|
|
-
|
|
|
|
|
- if (numDynamicBlocks > 1)
|
|
|
|
|
- {
|
|
|
|
|
- freeBlocks(&mStaticBlock);
|
|
|
|
|
- allocBlock(std::min(totalDynamicMemAmount, MaxDynamicMemory));
|
|
|
|
|
- mFreeBlock = &mStaticBlock;
|
|
|
|
|
- }
|
|
|
|
|
- else if (numDynamicBlocks == 1 && MaxDynamicMemory == 0)
|
|
|
|
|
- {
|
|
|
|
|
- freeBlocks(&mStaticBlock);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ mFreePtr = 0;
|
|
|
|
|
+ mDynamicAlloc.clear();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
private:
|
|
|
UINT8 mStaticData[BlockSize];
|
|
UINT8 mStaticData[BlockSize];
|
|
|
- MemBlock mStaticBlock;
|
|
|
|
|
|
|
+ UINT32 mFreePtr;
|
|
|
|
|
+ DynamicAllocator mDynamicAlloc;
|
|
|
|
|
|
|
|
- MemBlock* mFreeBlock;
|
|
|
|
|
UINT32 mTotalAllocBytes;
|
|
UINT32 mTotalAllocBytes;
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * Allocates a dynamic block of memory of the wanted size. The exact allocation size might be slightly higher in
|
|
|
|
|
- * order to store block meta data.
|
|
|
|
|
- */
|
|
|
|
|
- MemBlock* allocBlock(UINT32 wantedSize)
|
|
|
|
|
- {
|
|
|
|
|
- UINT32 blockSize = BlockSize;
|
|
|
|
|
- if (wantedSize > blockSize)
|
|
|
|
|
- blockSize = wantedSize;
|
|
|
|
|
|
|
+ /** Allocator for the standard library that internally uses a static allocator. */
|
|
|
|
|
+ template <int BlockSize, class T>
|
|
|
|
|
+ class StdStaticAlloc
|
|
|
|
|
+ {
|
|
|
|
|
+ public:
|
|
|
|
|
+ typedef T value_type;
|
|
|
|
|
+ typedef value_type* pointer;
|
|
|
|
|
+ typedef const value_type* const_pointer;
|
|
|
|
|
+ typedef value_type& reference;
|
|
|
|
|
+ typedef const value_type& const_reference;
|
|
|
|
|
+ typedef std::size_t size_type;
|
|
|
|
|
+ typedef std::ptrdiff_t difference_type;
|
|
|
|
|
|
|
|
- MemBlock* dynamicBlock = mFreeBlock->mNextBlock;
|
|
|
|
|
- MemBlock* newBlock = nullptr;
|
|
|
|
|
- while (dynamicBlock != nullptr)
|
|
|
|
|
- {
|
|
|
|
|
- if (dynamicBlock->mSize >= blockSize)
|
|
|
|
|
- {
|
|
|
|
|
- newBlock = dynamicBlock;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ StdStaticAlloc() = default;
|
|
|
|
|
|
|
|
- dynamicBlock = dynamicBlock->mNextBlock;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ StdStaticAlloc(StaticAlloc<BlockSize, FreeAlloc>* alloc) noexcept
|
|
|
|
|
+ :mStaticAlloc(alloc)
|
|
|
|
|
+ { }
|
|
|
|
|
|
|
|
- if (newBlock == nullptr)
|
|
|
|
|
- {
|
|
|
|
|
- UINT8* data = (UINT8*)reinterpret_cast<UINT8*>(bs_alloc(blockSize + sizeof(MemBlock)));
|
|
|
|
|
- newBlock = new (data)MemBlock(data + sizeof(MemBlock), blockSize);
|
|
|
|
|
- newBlock->mPrevBlock = mFreeBlock;
|
|
|
|
|
- mFreeBlock->mNextBlock = newBlock;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ template<class U> StdStaticAlloc(const StdStaticAlloc<BlockSize, U>& alloc) noexcept
|
|
|
|
|
+ :mStaticAlloc(alloc.mStaticAlloc)
|
|
|
|
|
+ { }
|
|
|
|
|
|
|
|
- mFreeBlock = newBlock;
|
|
|
|
|
- return newBlock;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ template<class U> class rebind { public: typedef StdStaticAlloc<BlockSize, U> other; };
|
|
|
|
|
|
|
|
- /** Releases memory for any dynamic blocks following the provided block (if there are any). */
|
|
|
|
|
- void freeBlocks(MemBlock* start)
|
|
|
|
|
|
|
+ /** Allocate but don't initialize number elements of type T.*/
|
|
|
|
|
+ T* allocate(const size_t num) const
|
|
|
{
|
|
{
|
|
|
- MemBlock* dynamicBlock = start->mNextBlock;
|
|
|
|
|
- while (dynamicBlock != nullptr)
|
|
|
|
|
- {
|
|
|
|
|
- MemBlock* nextBlock = dynamicBlock->mNextBlock;
|
|
|
|
|
|
|
+ if (num == 0)
|
|
|
|
|
+ return nullptr;
|
|
|
|
|
|
|
|
- dynamicBlock->~MemBlock();
|
|
|
|
|
- bs_free(dynamicBlock);
|
|
|
|
|
|
|
+ if (num > static_cast<size_t>(-1) / sizeof(T))
|
|
|
|
|
+ return nullptr; // Error
|
|
|
|
|
|
|
|
- dynamicBlock = nextBlock;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ void* const pv = mStaticAlloc->alloc((UINT32)(num * sizeof(T)));
|
|
|
|
|
+ if (!pv)
|
|
|
|
|
+ return nullptr; // Error
|
|
|
|
|
|
|
|
- start->mNextBlock = nullptr;
|
|
|
|
|
|
|
+ return static_cast<T*>(pv);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /** Deallocate storage p of deleted elements. */
|
|
|
|
|
+ void deallocate(T* p, size_t num) const noexcept
|
|
|
|
|
+ {
|
|
|
|
|
+ mStaticAlloc->free((UINT8*)p, (UINT32)num);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ StaticAlloc<BlockSize, FreeAlloc>* mStaticAlloc = nullptr;
|
|
|
|
|
+
|
|
|
|
|
+ size_t max_size() const { return std::numeric_limits<UINT32>::max() / sizeof(T); }
|
|
|
|
|
+ void construct(pointer p, const_reference t) { new (p) T(t); }
|
|
|
|
|
+ void destroy(pointer p) { p->~T(); }
|
|
|
|
|
+ template<class U, class... Args>
|
|
|
|
|
+ void construct(U* p, Args&&... args) { new(p) U(std::forward<Args>(args)...); }
|
|
|
|
|
+
|
|
|
|
|
+ template <class T1, int N1, class T2, int N2>
|
|
|
|
|
+ friend bool operator== (const StdStaticAlloc<N1, T1>& a, const StdStaticAlloc<N2, T2>& b) throw();
|
|
|
|
|
+
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ /** Return that all specializations of this allocator are interchangeable. */
|
|
|
|
|
+ template <class T1, int N1, class T2, int N2>
|
|
|
|
|
+ bool operator== (const StdStaticAlloc<N1, T1>& a, const StdStaticAlloc<N2, T2>& b) throw()
|
|
|
|
|
+ {
|
|
|
|
|
+ return N1 == N2 && a.mStaticAlloc == b.mStaticAlloc;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Return that all specializations of this allocator are interchangeable. */
|
|
|
|
|
+ template <class T1, int N1, class T2, int N2>
|
|
|
|
|
+ bool operator!= (const StdStaticAlloc<N1, T1>& a, const StdStaticAlloc<N2, T2>& b) throw()
|
|
|
|
|
+ {
|
|
|
|
|
+ return !(a == b);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** @} */
|
|
|
/** @} */
|
|
/** @} */
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ /** @addtogroup Memory
|
|
|
|
|
+ * @{
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Equivalent to Vector, except it avoids any dynamic allocations until the number of elements exceeds @p Count.
|
|
|
|
|
+ * Requires allocator to be explicitly provided.
|
|
|
|
|
+ */
|
|
|
|
|
+ template <typename T, int Count>
|
|
|
|
|
+ using StaticVector = std::vector<T, StdStaticAlloc<sizeof(T) * Count, T>>;
|
|
|
|
|
+
|
|
|
/** @} */
|
|
/** @} */
|
|
|
}
|
|
}
|