//----------------------------------------------------------------------------- // Copyright (c) 2023 tgemit contributors. // See AUTHORS file and git repository for contributor information. // // SPDX-License-Identifier: MIT //----------------------------------------------------------------------------- #ifndef _DATACHUNKER_H_ #define _DATACHUNKER_H_ #ifndef _PLATFORM_H_ # include "platform/platform.h" #endif #ifndef _PLATFORMASSERT_H_ # include "platform/platformAssert.h" #endif #ifndef _FRAMEALLOCATOR_H_ #include "core/frameAllocator.h" #endif #include #include /// Implements a chunked data allocator. /// /// This memory allocator allocates data in chunks of bytes, /// the default size being ChunkSize. /// Bytes are sourced from the current head chunk until expended, /// in which case a new chunk of bytes will be allocated from /// the system memory allocator. /// template class BaseDataChunker { public: enum { ChunkSize = 16384 }; typedef T AlignmentType; struct alignas(uintptr_t) DataBlock : public AlignedBufferAllocator { DataBlock* mNext = NULL; inline DataBlock* getEnd() { return this + 1; } }; protected: dsize_t mChunkSize; DataBlock* mChunkHead; public: BaseDataChunker(U32 chunkSize = BaseDataChunker::ChunkSize) : mChunkSize(chunkSize), mChunkHead(NULL) { } virtual ~BaseDataChunker() { freeBlocks(false); } DataBlock* allocChunk(dsize_t chunkSize) { DataBlock* newChunk = (DataBlock*)dMalloc(sizeof(DataBlock) + chunkSize); constructInPlace(newChunk); newChunk->initWithBytes((T*)newChunk->getEnd(), chunkSize); newChunk->mNext = mChunkHead; mChunkHead = newChunk; return newChunk; } void* alloc(dsize_t numBytes) { void* theAlloc = mChunkHead ? mChunkHead->allocBytes(numBytes) : NULL; if (theAlloc == NULL) { dsize_t actualSize = std::max(mChunkSize, numBytes); allocChunk(actualSize); theAlloc = mChunkHead->allocBytes(numBytes); AssertFatal(theAlloc != NULL, "Something really odd going on here"); } return theAlloc; } void freeBlocks(bool keepOne = false) { DataBlock* itr = mChunkHead; while (itr) { DataBlock* nextItr = itr->mNext; if (nextItr == NULL && keepOne) { itr->setPosition(0); break; } dFree(itr); itr = nextItr; } mChunkHead = itr; } U32 countUsedBlocks() { U32 count = 0; for (DataBlock* itr = mChunkHead; itr; itr = itr->mNext) { count++; } return count; } dsize_t countUsedBytes() { dsize_t count = 0; for (DataBlock* itr = mChunkHead; itr; itr = itr->mNext) { count += itr->getPositionBytes(); } return count; } void setChunkSize(dsize_t size) { AssertFatal(mChunkHead == NULL, "Tried setting AFTER init"); mChunkSize = size; } bool isManagedByChunker(void* ptr) const { U8* chkPtr = (U8*)ptr; for (DataBlock* itr = mChunkHead; itr; itr = itr->mNext) { const U8* blockStart = (U8*)itr->getAlignedBuffer(); const U8* blockEnd = (U8*)itr->getAlignedBufferEnd(); if (chkPtr >= blockStart && chkPtr < blockEnd) return true; } return false; } }; class DataChunker : public BaseDataChunker { public: DataChunker() : BaseDataChunker(BaseDataChunker::ChunkSize) { ; } explicit DataChunker(dsize_t size) : BaseDataChunker(size) { ; } }; /// Implements a derivative of BaseDataChunker designed for /// allocating structs of type T without initialization. template class Chunker : private BaseDataChunker { public: Chunker(dsize_t size = BaseDataChunker::ChunkSize) : BaseDataChunker(std::max(sizeof(T), size)) { } T* alloc() { return (T*)BaseDataChunker::alloc(sizeof(T)); } void clear() { BaseDataChunker::freeBlocks(); } }; /// Implements a derivative of BaseDataChunker designed for /// allocating structs of various types Y without initialization. /// @note: this is horribly suboptimal for types not multiples of uintptr_t in size. class MultiTypedChunker : private BaseDataChunker { public: typedef uintptr_t AlignmentType; MultiTypedChunker(dsize_t size = BaseDataChunker::ChunkSize) : BaseDataChunker(std::max(sizeof(uintptr_t), size)) { } template Y* alloc() { return (Y*)BaseDataChunker::alloc(sizeof(Y)); } void clear() { BaseDataChunker::freeBlocks(true); } }; /// Implements a simple linked list for ClassChunker and FreeListChunker. template struct ChunkerFreeClassList { ChunkerFreeClassList* mNextList; ChunkerFreeClassList() : mNextList(NULL) { } void reset() { mNextList = NULL; } bool isEmpty() const { return mNextList == NULL; } T* pop() { ChunkerFreeClassList* oldNext = mNextList; mNextList = mNextList ? mNextList->mNextList : NULL; return (T*)oldNext; } void push(ChunkerFreeClassList* other) { other->mNextList = mNextList; mNextList = other; } }; /// Implements a derivative of BaseDataChunker designed for /// allocating structs or classes of type T with initialization. template class ClassChunker : private BaseDataChunker { protected: ChunkerFreeClassList mFreeListHead; public: ClassChunker(dsize_t size = BaseDataChunker::ChunkSize) { } T* alloc() { if (mFreeListHead.isEmpty()) { return constructInPlace((T*)BaseDataChunker::alloc(sizeof(T))); } else { return constructInPlace(mFreeListHead.pop()); } } void free(T* item) { destructInPlace(item); mFreeListHead.push(reinterpret_cast*>(item)); } void freeBlocks(bool keepOne = false) { BaseDataChunker::freeBlocks(keepOne); mFreeListHead.reset(); } inline bool isManagedByChunker(void* ptr) const { return BaseDataChunker::isManagedByChunker(ptr); } inline ChunkerFreeClassList& getFreeListHead() { return mFreeListHead; } }; /// Implements a chunker which uses the data of another BaseDataChunker /// as underlying storage. template class FreeListChunker { protected: BaseDataChunker* mChunker; bool mOwnsChunker; ChunkerFreeClassList mFreeListHead; public: FreeListChunker(BaseDataChunker* otherChunker) : mChunker(otherChunker), mOwnsChunker(false) { } FreeListChunker(dsize_t size = BaseDataChunker::ChunkSize) { mChunker = new BaseDataChunker(size); mOwnsChunker = true; } BaseDataChunker* getChunker() { return mChunker; } T* alloc() { if (mFreeListHead.isEmpty()) { return constructInPlace((T*)mChunker->alloc(sizeof(T))); } else { return constructInPlace(mFreeListHead.pop()); } } void free(T* item) { destructInPlace(item); mFreeListHead.push(reinterpret_cast*>(item)); } void freeBlocks(bool keepOne = false) { mChunker->freeBlocks(keepOne); } }; template struct DWordDataBlob { U32 data[(byteSize + 3)/ 4]; }; /// Implements a three-tiered chunker /// K1..3 should be ordered from low to high template class ThreeTieredChunker { public: struct Handle { U32 tier; void* ptr; Handle() : tier(0), ptr(NULL) { ; } Handle(const Handle& other) : tier(other.tier), ptr(other.ptr) { ; } Handle(U32 in_tier, void* in_ptr) : tier(in_tier), ptr(in_ptr) { ; } Handle& operator=(const Handle& other) { tier = other.tier; ptr = other.ptr; return *this; } }; protected: ClassChunker mT1; ClassChunker mT2; ClassChunker mT3; public: Handle alloc(U32 byteSize) { Handle outH; if (byteSize > sizeof(K3)) { const U32 wordSize = (byteSize + 3) / 4; outH = Handle(0, (void*)(new U32[wordSize])); } else { if (byteSize <= sizeof(K1)) { outH = Handle(1, (void*)mT1.alloc()); } else if (byteSize <= sizeof(K2)) { outH = Handle(2, (void*)mT2.alloc()); } else if (byteSize <= sizeof(K3)) { outH = Handle(3, (void*)mT3.alloc()); } else { outH = Handle(0, NULL); } } return outH; } void free(Handle& item) { if (item.ptr == NULL) return; switch (item.tier) { case 0: delete[] ((U32*)item.ptr); break; case 1: mT1.free((K1*)item.ptr); break; case 2: mT2.free((K2*)item.ptr); break; case 3: mT3.free((K3*)item.ptr); break; default: break; } item.ptr = NULL; } void freeBlocks(bool keepOne = false) { mT1.freeBlocks(keepOne); mT2.freeBlocks(keepOne); mT3.freeBlocks(keepOne); } inline ClassChunker& getT1Chunker() { return mT1; } inline ClassChunker& getT2Chunker() { return mT2; } inline ClassChunker& getT3Chunker() { return mT3; } }; #endif