#pragma once #include "BsPrerequisitesUtil.h" namespace BansheeEngine { /** * @brief Frame allocator. Performs very fast allocations but can only free all of its memory at once. * Perfect for allocations that last just a single frame. * * @note Not thread safe with an exception. "alloc" and "clear" methods need to be called from the same thread. * "dealloc" is thread safe and can be called from any thread. */ class BS_UTILITY_EXPORT FrameAlloc { private: /** * @brief A single block of memory within a frame allocator. */ class MemBlock { public: MemBlock(UINT32 size); ~MemBlock(); /** * @brief Allocates a piece of memory within the block. Caller must ensure * the block has enough empty space. */ UINT8* alloc(UINT32 amount); /** * @brief Releases all allocations within a block but doesn't actually free the memory. */ void clear(); UINT8* mData; UINT32 mFreePtr; UINT32 mSize; }; public: FrameAlloc(UINT32 blockSize = 1024 * 1024); ~FrameAlloc(); /** * @brief Allocates a new block of memory of the specified size. * * @param amount Amount of memory to allocate, in bytes. * * @note Not thread safe. */ UINT8* alloc(UINT32 amount); /** * @brief Allocates and constructs a new object. * * @note Not thread safe. */ template T* alloc(Args &&...args) { return new ((T*)alloc(sizeof(T))) T(std::forward(args)...); } /** * @brief Deallocates a previously allocated block of memory. * * @note No deallocation is actually done here. This method is only used for debug purposes * so it is easier to track down memory leaks and corruption. * * Thread safe. */ void dealloc(UINT8* data); /** * @brief Deallocates and destructs a previously allocated object. * * @note No deallocation is actually done here. This method is only used to call the destructor * and for debug purposes so it is easier to track down memory leaks and corruption. * * Thread safe. */ template void dealloc(T* obj) { if (obj != nullptr) obj->~T(); dealloc((UINT8*)obj); } /** * @brief Starts a new frame. Next call to ::clear will only clear memory * allocated past this point. */ void markFrame(); /** * @brief Deallocates all allocated memory since the last call to ::markFrame * (or all the memory if there was no call to ::markFrame). * * @note Not thread safe. */ void clear(); /** * @brief Changes the frame allocator owner thread. After the owner * thread has changed only allocations from that thread can be made. */ void setOwnerThread(BS_THREAD_ID_TYPE thread); private: UINT32 mBlockSize; Vector mBlocks; MemBlock* mFreeBlock; UINT32 mNextBlockIdx; std::atomic mTotalAllocBytes; void* mLastFrame; #if BS_DEBUG_MODE BS_THREAD_ID_TYPE mOwnerThread; #endif /** * @brief 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); /** * @brief Frees a memory block. */ void deallocBlock(MemBlock* block); }; /** * @brief Allocator for the standard library that internally uses a * frame allocator. */ template class StdFrameAlloc { public: // Type definitions typedef T value_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; /** * @brief Rebind allocator to type U */ template struct rebind { typedef StdFrameAlloc other; }; StdFrameAlloc() throw() :mFrameAlloc(nullptr) { } StdFrameAlloc(FrameAlloc* frameAlloc) throw() :mFrameAlloc(frameAlloc) { } StdFrameAlloc(const StdFrameAlloc& alloc) throw() :mFrameAlloc(alloc.mFrameAlloc) { } template StdFrameAlloc(const StdFrameAlloc& alloc) throw() :mFrameAlloc(alloc.mFrameAlloc) { } ~StdFrameAlloc() throw() { } /** * @brief Return address of value. */ pointer address(reference value) const { return &value; } /** * @brief Return address of value. */ const_pointer address(const_reference value) const { return &value; } /** * @brief Return maximum number of elements that can be allocated. */ size_type max_size() const throw() { return std::numeric_limits::max() / sizeof(T); } /** * @brief Allocate but don't initialize number elements of type T. */ pointer allocate(size_type num, const void* = 0) { pointer ret = (pointer)(mFrameAlloc->alloc((UINT32)num*sizeof(T))); return ret; } /** * @brief Initialize elements of allocated storage p with value "value". */ void construct(pointer p, const T& value) { new((void*)p)T(value); } /** * @brief Destroy elements of initialized storage p. */ void destroy(pointer p) { p->~T(); } /** * @brief Deallocate storage p of deleted elements. */ void deallocate(pointer p, size_type num) { mFrameAlloc->dealloc((UINT8*)p); } FrameAlloc* mFrameAlloc; }; /** * @brief Return that all specializations of this allocator are interchangeable. */ template bool operator== (const StdFrameAlloc&, const StdFrameAlloc&) throw() { return true; } /** * @brief Return that all specializations of this allocator are interchangeable. */ template bool operator!= (const StdFrameAlloc&, const StdFrameAlloc&) throw() { return false; } }