/* =========================================================================== Copyright (C) 2015 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. OpenMoHAA source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // mem_blockalloc.h: Fast block memory manager #pragma once #include "Linklist.h" #include "q_shared.h" #include #include #include void *MEM_Alloc(int size); void MEM_Free(void *ptr); #ifdef _DEBUG_MEM # define _DEBUG_MEMBLOCK 1 #endif static constexpr size_t DefaultBlock = 256; enum class alloc_source_e { SourceBlock = 174, SourceMalloc }; template class MEM_BlockAlloc_enum; template struct block_selectType_t; template<> struct block_selectType_t<8> { using type = uint8_t; }; template<> struct block_selectType_t<16> { using type = uint16_t; }; template<> struct block_selectType_t<32> { using type = uint32_t; }; template<> struct block_selectType_t<64> { using type = uint64_t; }; template class block_s { private: static constexpr size_t bitsNeeded = blocksize <= 0x80 ? 8 : blocksize <= 0x8000 ? 16 : blocksize <= 0x80000000 ? 32 : 64; public: block_s(); #if !_DEBUG_MEMBLOCK bool usedDataAvailable() const; bool freeDataAvailable() const; #endif public: using offset_t = typename block_selectType_t::type; struct info_t { offset_t index; alloc_source_e source; static constexpr uint16_t typeSize = sizeof(aclass); alignas(alignof(aclass)) char data[sizeof(aclass)]; }; public: #if !_DEBUG_MEMBLOCK info_t data[blocksize]; offset_t prev_data[blocksize]; offset_t next_data[blocksize]; offset_t free_data; offset_t used_data; bool has_free_data : 1; bool has_used_data : 1; #else offset_t data[sizeof(aclass)]; #endif block_s *prev_block; block_s *next_block; public: static constexpr size_t headersize = offsetof(info_t, data); static constexpr size_t dataoffset = 0; static constexpr size_t datasize = sizeof(info_t); }; template block_s::block_s() #if !_DEBUG_MEMBLOCK { info_t *header; offset_t curr; for (curr = 0; curr < blocksize - 1; ++curr) { offset_t next = curr + 1; header = &data[curr]; header->source = alloc_source_e::SourceBlock; header->index = curr; prev_data[next] = curr; next_data[curr] = next; } header = &data[curr]; header->source = alloc_source_e::SourceBlock; header->index = blocksize - 1; prev_data[0] = blocksize - 1; next_data[blocksize - 1] = 0; free_data = 0; prev_block = next_block = nullptr; has_free_data = true; has_used_data = false; } #else : prev_block(nullptr) , next_block(nullptr) {} #endif #if !_DEBUG_MEMBLOCK template bool block_s::usedDataAvailable() const { return has_used_data; } template bool block_s::freeDataAvailable() const { return has_free_data; } #endif template class MEM_BlockAlloc { static_assert(blocksize >= 2, "Minimum 2x class preallocation required!!"); public: MEM_BlockAlloc(); ~MEM_BlockAlloc(); void *Alloc(); void Free(void *ptr) noexcept; void FreeAll() noexcept; size_t Count(); size_t BlockCount(); size_t BlockMemory(); private: friend class MEM_BlockAlloc_enum; using block_t = block_s; using block_offset_t = typename block_t::offset_t; #if !_DEBUG_MEMBLOCK block_t *m_FreeBlock; block_t *m_StartUsedBlock; block_t *m_StartFullBlock; #else block_t *m_Block; #endif size_t m_BlockCount; private: void *TakeFree(block_t *block, uintptr_t free_data); size_t Count(const block_t *block); }; template class MEM_BlockAlloc_enum { public: MEM_BlockAlloc_enum(MEM_BlockAlloc& owner); aclass *NextElement(); aclass *CurrentElement(); enum blockType_e { used, full }; private: using block_t = block_s; using offset_t = typename block_t::offset_t; MEM_BlockAlloc *m_Owner; block_t *m_CurrentBlock; #if !_DEBUG_MEMBLOCK offset_t m_CurrentData; blockType_e m_CurrentBlockType; #endif }; template MEM_BlockAlloc::MEM_BlockAlloc() #if !_DEBUG_MEMBLOCK : m_StartUsedBlock() , m_StartFullBlock() { m_FreeBlock = nullptr; m_BlockCount = 0; } #else : m_Block() { m_BlockCount = 0; } #endif template MEM_BlockAlloc::~MEM_BlockAlloc() { // due to the randomized order of initialization and destruction // MEM_BlockAlloc shouldn't automatically free memory // because con_set and other stuff could handle destruction // after memory was freed //FreeAll(); } template void *MEM_BlockAlloc::Alloc() { #if _DEBUG_MEMBLOCK block_t *block = new (MEM_Alloc(sizeof(block_t))) block_t(); LL_SafeAddFirst(m_Block, block, next_block, prev_block); m_BlockCount++; return (void *)block->data; #else block_t *used_block; block_offset_t free_data; block_offset_t next_data; if (m_StartUsedBlock) { used_block = m_StartUsedBlock; free_data = used_block->free_data; next_data = used_block->next_data[free_data]; if (next_data == free_data) { // Move the block to the next block list as there is no space // available m_StartUsedBlock = used_block->next_block; LL_SafeRemoveRoot(m_StartUsedBlock, used_block, next_block, prev_block); LL_SafeAddFirst(m_StartFullBlock, used_block, next_block, prev_block); used_block->has_free_data = false; return TakeFree(used_block, free_data); } } else { if (m_FreeBlock) { // start from the free block used_block = m_FreeBlock; m_FreeBlock = nullptr; free_data = used_block->free_data; next_data = used_block->next_data[free_data]; } else { m_BlockCount++; // allocate and construct a new block used_block = new (MEM_Alloc(sizeof(block_t))) block_t(); free_data = 0; next_data = 1; } LL_SafeAddFirst(m_StartUsedBlock, used_block, next_block, prev_block); } const block_offset_t prev_data = used_block->prev_data[free_data]; used_block->next_data[prev_data] = next_data; used_block->prev_data[next_data] = prev_data; used_block->free_data = next_data; used_block->has_free_data = true; if (!used_block->usedDataAvailable()) { used_block->used_data = free_data; used_block->has_used_data = true; used_block->next_data[free_data] = free_data; used_block->prev_data[free_data] = free_data; return used_block->data[free_data].data; } return TakeFree(used_block, free_data); #endif } template void *MEM_BlockAlloc::TakeFree(block_t *block, uintptr_t free_data) { const block_offset_t used_data = block->used_data; const block_offset_t prev_data = block->prev_data[used_data]; block->next_data[prev_data] = (block_offset_t)free_data; block->prev_data[used_data] = (block_offset_t)free_data; block->next_data[free_data] = used_data; block->prev_data[free_data] = prev_data; return block->data[free_data].data; } template void MEM_BlockAlloc::Free(void *ptr) noexcept { #if _DEBUG_MEMBLOCK block_t *block = (block_t*)ptr - offsetof(block_t, data); LL_SafeRemoveRoot(m_Block, block, next_block, prev_block); m_BlockCount--; MEM_Free(block); #else // get the header of the pointer typename block_t::info_t *header = reinterpret_cast(static_cast(ptr) - block_t::headersize); const block_offset_t used_data = header->index; // get the block from the header block_t *const block = (block_t *)((uint8_t *)header - used_data * block_t::datasize - block_t::dataoffset); const block_offset_t next_data = block->next_data[used_data]; if (next_data == used_data) { LL_SafeRemoveRoot(m_StartUsedBlock, block, next_block, prev_block); if (m_FreeBlock) { // deallocate the free block because of another deallocation --m_BlockCount; MEM_Free(m_FreeBlock); m_FreeBlock = nullptr; } m_FreeBlock = block; block->has_used_data = false; const block_offset_t free_data = block->free_data; const block_offset_t prev_data = block->prev_data[free_data]; block->next_data[prev_data] = used_data; block->prev_data[free_data] = used_data; block->next_data[used_data] = free_data; block->prev_data[used_data] = prev_data; } else { const block_offset_t prev_data = block->prev_data[used_data]; block->next_data[prev_data] = next_data; block->prev_data[next_data] = prev_data; block->used_data = next_data; block->has_used_data = true; if (block->freeDataAvailable()) { const block_offset_t free_data = block->free_data; const block_offset_t prev_data = block->prev_data[free_data]; block->next_data[prev_data] = used_data; block->prev_data[free_data] = used_data; block->next_data[used_data] = free_data; block->prev_data[used_data] = prev_data; return; } if (m_StartFullBlock == block) { m_StartFullBlock = block->next_block; } LL_SafeRemoveRoot(m_StartFullBlock, block, next_block, prev_block); LL_SafeAddFirst(m_StartUsedBlock, block, next_block, prev_block); block->free_data = used_data; block->has_free_data = true; block->prev_data[used_data] = used_data; block->next_data[used_data] = used_data; } #endif } template void MEM_BlockAlloc::FreeAll() noexcept { block_t *block; #if _DEBUG_MEMBLOCK block_t *next = m_Block; for (block = next; block; block = next) { next = block->next_block; m_BlockCount--; a *ptr = (a *)block->data; ptr->~a(); MEM_Free(block); } m_Block = NULL; #else while ((block = m_StartFullBlock) != nullptr) { if (block->usedDataAvailable()) { a *ptr = (a *)block->data[block->used_data].data; ptr->~a(); Free(ptr); block = m_StartFullBlock; } } while ((block = m_StartUsedBlock) != nullptr) { if (block->usedDataAvailable()) { a *ptr = (a *)block->data[block->used_data].data; ptr->~a(); Free(ptr); } } if (m_FreeBlock) { m_BlockCount--; MEM_Free(m_FreeBlock); m_FreeBlock = nullptr; } #endif } template size_t MEM_BlockAlloc::Count(const block_t *list) { int count = 0; #if _DEBUG_MEMBLOCK for (const block_t *block = list; block; block = block->next_block) { count++; } return count; #else for (const block_t *block = list; block; block = block->next_block) { if (!block->usedDataAvailable()) { continue; } const block_offset_t used_data = block->used_data; block_offset_t current_used_data = used_data; do { count++; current_used_data = block->next_data[current_used_data]; } while (current_used_data != used_data); } return count; #endif } template size_t MEM_BlockAlloc::Count() { #if _DEBUG_MEMBLOCK return Count(m_Block); #else return Count(m_StartFullBlock) + Count(m_StartUsedBlock); #endif } template size_t MEM_BlockAlloc::BlockCount() { return m_BlockCount; } template size_t MEM_BlockAlloc::BlockMemory() { return sizeof(block_s); } template MEM_BlockAlloc_enum::MEM_BlockAlloc_enum(MEM_BlockAlloc& owner) { m_Owner = &owner; m_CurrentBlock = nullptr; #if !_DEBUG_MEMBLOCK m_CurrentBlockType = MEM_BlockAlloc_enum::used; #endif } template a *MEM_BlockAlloc_enum::NextElement() { #if _DEBUG_MEMBLOCK if (!m_CurrentBlock) { m_CurrentBlock = m_Owner->m_Block; } else { m_CurrentBlock = m_CurrentBlock->next_block; } return (a *)m_CurrentBlock; #else // search for a valid block type while (!m_CurrentBlock) { switch (m_CurrentBlockType) { case blockType_e::used: m_CurrentBlock = m_Owner->m_StartUsedBlock; break; case blockType_e::full: m_CurrentBlock = m_Owner->m_StartFullBlock; break; default: return nullptr; } reinterpret_cast(m_CurrentBlockType)++; _label: for (; m_CurrentBlock; m_CurrentBlock = m_CurrentBlock->next_block) { if (m_CurrentBlock->usedDataAvailable()) { m_CurrentData = m_CurrentBlock->used_data; return reinterpret_cast(m_CurrentBlock->data[m_CurrentData].data); } } } m_CurrentData = m_CurrentBlock->next_data[m_CurrentData]; if (m_CurrentData == m_CurrentBlock->used_data) { // found an object m_CurrentBlock = m_CurrentBlock->next_block; goto _label; } return reinterpret_cast(m_CurrentBlock->data[m_CurrentData].data); #endif } template a *MEM_BlockAlloc_enum::CurrentElement() { return m_CurrentBlock; } template void *operator new(size_t, MEM_BlockAlloc& allocator) { return allocator.Alloc(); } template void operator delete(void *ptr, MEM_BlockAlloc& allocator) noexcept { return allocator.Free(ptr); }