//----------------------------------------------------------------------------- // Copyright (c) 2023 tgemit contributors. // See AUTHORS file and git repository for contributor information. // // SPDX-License-Identifier: MIT //----------------------------------------------------------------------------- #pragma once #define _DATACHUNKER_H_ #ifndef _PLATFORM_H_ # include "platform/platform.h" #endif #ifndef _PLATFORMASSERT_H_ # include "platform/platformAssert.h" #endif #include #include #include "core/frameAllocator.h" //#include "math/mMathFn.h" // tgemit - needed here for the moment /// 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 }; struct alignas(uintptr_t) DataBlock : public AlignedBufferAllocator { DataBlock* mNext; 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; } }; 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: 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() { 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); } }; /// 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) { BaseDataChunker::freeBlocks(keepOne); } };