/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 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 3 of the License, or (at your option) any later version. Doom 3 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 Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #ifndef __HEAP_H__ #define __HEAP_H__ /* =============================================================================== Memory Management This is a replacement for the compiler heap code (i.e. "C" malloc() and free() calls). On average 2.5-3.0 times faster than MSVC malloc()/free(). Worst case performance is 1.65 times faster and best case > 70 times. =============================================================================== */ typedef struct { int num; int minSize; int maxSize; int totalSize; } memoryStats_t; void Mem_Init( void ); void Mem_Shutdown( void ); void Mem_EnableLeakTest( const char *name ); void Mem_ClearFrameStats( void ); void Mem_GetFrameStats( memoryStats_t &allocs, memoryStats_t &frees ); void Mem_GetStats( memoryStats_t &stats ); void Mem_Dump_f( const class idCmdArgs &args ); void Mem_DumpCompressed_f( const class idCmdArgs &args ); void Mem_AllocDefragBlock( void ); #ifndef ID_DEBUG_MEMORY void * Mem_Alloc( const int size ); void * Mem_ClearedAlloc( const int size ); void Mem_Free( void *ptr ); char * Mem_CopyString( const char *in ); void * Mem_Alloc16( const int size ); void Mem_Free16( void *ptr ); #ifdef ID_REDIRECT_NEWDELETE __inline void *operator new( size_t s ) { return Mem_Alloc( s ); } __inline void operator delete( void *p ) { Mem_Free( p ); } __inline void *operator new[]( size_t s ) { return Mem_Alloc( s ); } __inline void operator delete[]( void *p ) { Mem_Free( p ); } #endif #else /* ID_DEBUG_MEMORY */ void * Mem_Alloc( const int size, const char *fileName, const int lineNumber ); void * Mem_ClearedAlloc( const int size, const char *fileName, const int lineNumber ); void Mem_Free( void *ptr, const char *fileName, const int lineNumber ); char * Mem_CopyString( const char *in, const char *fileName, const int lineNumber ); void * Mem_Alloc16( const int size, const char *fileName, const int lineNumber ); void Mem_Free16( void *ptr, const char *fileName, const int lineNumber ); #ifdef ID_REDIRECT_NEWDELETE __inline void *operator new( size_t s, int t1, int t2, char *fileName, int lineNumber ) { return Mem_Alloc( s, fileName, lineNumber ); } __inline void operator delete( void *p, int t1, int t2, char *fileName, int lineNumber ) { Mem_Free( p, fileName, lineNumber ); } __inline void *operator new[]( size_t s, int t1, int t2, char *fileName, int lineNumber ) { return Mem_Alloc( s, fileName, lineNumber ); } __inline void operator delete[]( void *p, int t1, int t2, char *fileName, int lineNumber ) { Mem_Free( p, fileName, lineNumber ); } __inline void *operator new( size_t s ) { return Mem_Alloc( s, "", 0 ); } __inline void operator delete( void *p ) { Mem_Free( p, "", 0 ); } __inline void *operator new[]( size_t s ) { return Mem_Alloc( s, "", 0 ); } __inline void operator delete[]( void *p ) { Mem_Free( p, "", 0 ); } #define ID_DEBUG_NEW new( 0, 0, __FILE__, __LINE__ ) #undef new #define new ID_DEBUG_NEW #endif #define Mem_Alloc( size ) Mem_Alloc( size, __FILE__, __LINE__ ) #define Mem_ClearedAlloc( size ) Mem_ClearedAlloc( size, __FILE__, __LINE__ ) #define Mem_Free( ptr ) Mem_Free( ptr, __FILE__, __LINE__ ) #define Mem_CopyString( s ) Mem_CopyString( s, __FILE__, __LINE__ ) #define Mem_Alloc16( size ) Mem_Alloc16( size, __FILE__, __LINE__ ) #define Mem_Free16( ptr ) Mem_Free16( ptr, __FILE__, __LINE__ ) #endif /* ID_DEBUG_MEMORY */ /* =============================================================================== Block based allocator for fixed size objects. All objects of the 'type' are properly constructed. However, the constructor is not called for re-used objects. =============================================================================== */ template class idBlockAlloc { public: idBlockAlloc( void ); ~idBlockAlloc( void ); void Shutdown( void ); type * Alloc( void ); void Free( type *element ); int GetTotalCount( void ) const { return total; } int GetAllocCount( void ) const { return active; } int GetFreeCount( void ) const { return total - active; } private: typedef struct element_s { struct element_s * next; type t; } element_t; typedef struct block_s { element_t elements[blockSize]; struct block_s * next; } block_t; block_t * blocks; element_t * free; int total; int active; }; template idBlockAlloc::idBlockAlloc( void ) { blocks = NULL; free = NULL; total = active = 0; } template idBlockAlloc::~idBlockAlloc( void ) { Shutdown(); } template type *idBlockAlloc::Alloc( void ) { if ( !free ) { block_t *block = new block_t; block->next = blocks; blocks = block; for ( int i = 0; i < blockSize; i++ ) { block->elements[i].next = free; free = &block->elements[i]; } total += blockSize; } active++; element_t *element = free; free = free->next; element->next = NULL; return &element->t; } template void idBlockAlloc::Free( type *t ) { element_t *element = (element_t *)( intptr_t(t) - sizeof(intptr_t) ); element->next = free; free = element; active--; } template void idBlockAlloc::Shutdown( void ) { while( blocks ) { block_t *block = blocks; blocks = blocks->next; delete block; } blocks = NULL; free = NULL; total = active = 0; } /* ============================================================================== Dynamic allocator, simple wrapper for normal allocations which can be interchanged with idDynamicBlockAlloc. No constructor is called for the 'type'. Allocated blocks are always 16 byte aligned. ============================================================================== */ template class idDynamicAlloc { public: idDynamicAlloc( void ); ~idDynamicAlloc( void ); void Init( void ); void Shutdown( void ); void SetFixedBlocks( int numBlocks ) {} void SetLockMemory( bool lock ) {} void FreeEmptyBaseBlocks( void ) {} type * Alloc( const int num ); type * Resize( type *ptr, const int num ); void Free( type *ptr ); const char * CheckMemory( const type *ptr ) const; int GetNumBaseBlocks( void ) const { return 0; } int GetBaseBlockMemory( void ) const { return 0; } int GetNumUsedBlocks( void ) const { return numUsedBlocks; } int GetUsedBlockMemory( void ) const { return usedBlockMemory; } int GetNumFreeBlocks( void ) const { return 0; } int GetFreeBlockMemory( void ) const { return 0; } int GetNumEmptyBaseBlocks( void ) const { return 0; } private: int numUsedBlocks; // number of used blocks int usedBlockMemory; // total memory in used blocks int numAllocs; int numResizes; int numFrees; void Clear( void ); }; template idDynamicAlloc::idDynamicAlloc( void ) { Clear(); } template idDynamicAlloc::~idDynamicAlloc( void ) { Shutdown(); } template void idDynamicAlloc::Init( void ) { } template void idDynamicAlloc::Shutdown( void ) { Clear(); } template type *idDynamicAlloc::Alloc( const int num ) { numAllocs++; if ( num <= 0 ) { return NULL; } numUsedBlocks++; usedBlockMemory += num * sizeof( type ); return Mem_Alloc16( num * sizeof( type ) ); } template type *idDynamicAlloc::Resize( type *ptr, const int num ) { numResizes++; if ( ptr == NULL ) { return Alloc( num ); } if ( num <= 0 ) { Free( ptr ); return NULL; } assert( 0 ); return ptr; } template void idDynamicAlloc::Free( type *ptr ) { numFrees++; if ( ptr == NULL ) { return; } Mem_Free16( ptr ); } template const char *idDynamicAlloc::CheckMemory( const type *ptr ) const { return NULL; } template void idDynamicAlloc::Clear( void ) { numUsedBlocks = 0; usedBlockMemory = 0; numAllocs = 0; numResizes = 0; numFrees = 0; } /* ============================================================================== Fast dynamic block allocator. No constructor is called for the 'type'. Allocated blocks are always 16 byte aligned. ============================================================================== */ #include "containers/BTree.h" //#define DYNAMIC_BLOCK_ALLOC_CHECK template class idDynamicBlock { public: type * GetMemory( void ) const { return (type *)( ( (byte *) this ) + sizeof( idDynamicBlock ) ); } int GetSize( void ) const { return abs( size ); } void SetSize( int s, bool isBaseBlock ) { size = isBaseBlock ? -s : s; } bool IsBaseBlock( void ) const { return ( size < 0 ); } #ifdef DYNAMIC_BLOCK_ALLOC_CHECK int id[3]; void * allocator; #endif int size; // size in bytes of the block idDynamicBlock * prev; // previous memory block idDynamicBlock * next; // next memory block idBTreeNode,int> *node; // node in the B-Tree with free blocks }; template class idDynamicBlockAlloc { public: idDynamicBlockAlloc( void ); ~idDynamicBlockAlloc( void ); void Init( void ); void Shutdown( void ); void SetFixedBlocks( int numBlocks ); void SetLockMemory( bool lock ); void FreeEmptyBaseBlocks( void ); type * Alloc( const int num ); type * Resize( type *ptr, const int num ); void Free( type *ptr ); const char * CheckMemory( const type *ptr ) const; int GetNumBaseBlocks( void ) const { return numBaseBlocks; } int GetBaseBlockMemory( void ) const { return baseBlockMemory; } int GetNumUsedBlocks( void ) const { return numUsedBlocks; } int GetUsedBlockMemory( void ) const { return usedBlockMemory; } int GetNumFreeBlocks( void ) const { return numFreeBlocks; } int GetFreeBlockMemory( void ) const { return freeBlockMemory; } int GetNumEmptyBaseBlocks( void ) const; private: idDynamicBlock * firstBlock; // first block in list in order of increasing address idDynamicBlock * lastBlock; // last block in list in order of increasing address idBTree,int,4>freeTree; // B-Tree with free memory blocks bool allowAllocs; // allow base block allocations bool lockMemory; // lock memory so it cannot get swapped out #ifdef DYNAMIC_BLOCK_ALLOC_CHECK int blockId[3]; #endif int numBaseBlocks; // number of base blocks int baseBlockMemory; // total memory in base blocks int numUsedBlocks; // number of used blocks int usedBlockMemory; // total memory in used blocks int numFreeBlocks; // number of free blocks int freeBlockMemory; // total memory in free blocks int numAllocs; int numResizes; int numFrees; void Clear( void ); idDynamicBlock * AllocInternal( const int num ); idDynamicBlock * ResizeInternal( idDynamicBlock *block, const int num ); void FreeInternal( idDynamicBlock *block ); void LinkFreeInternal( idDynamicBlock *block ); void UnlinkFreeInternal( idDynamicBlock *block ); void CheckMemory( void ) const; }; template idDynamicBlockAlloc::idDynamicBlockAlloc( void ) { Clear(); } template idDynamicBlockAlloc::~idDynamicBlockAlloc( void ) { Shutdown(); } template void idDynamicBlockAlloc::Init( void ) { freeTree.Init(); } template void idDynamicBlockAlloc::Shutdown( void ) { idDynamicBlock *block; for ( block = firstBlock; block != NULL; block = block->next ) { if ( block->node == NULL ) { FreeInternal( block ); } } for ( block = firstBlock; block != NULL; block = firstBlock ) { firstBlock = block->next; assert( block->IsBaseBlock() ); if ( lockMemory ) { idLib::sys->UnlockMemory( block, block->GetSize() + (int)sizeof( idDynamicBlock ) ); } Mem_Free16( block ); } freeTree.Shutdown(); Clear(); } template void idDynamicBlockAlloc::SetFixedBlocks( int numBlocks ) { idDynamicBlock *block; for ( int i = numBaseBlocks; i < numBlocks; i++ ) { block = ( idDynamicBlock * ) Mem_Alloc16( baseBlockSize ); if ( lockMemory ) { idLib::sys->LockMemory( block, baseBlockSize ); } #ifdef DYNAMIC_BLOCK_ALLOC_CHECK memcpy( block->id, blockId, sizeof( block->id ) ); block->allocator = (void*)this; #endif block->SetSize( baseBlockSize - (int)sizeof( idDynamicBlock ), true ); block->next = NULL; block->prev = lastBlock; if ( lastBlock ) { lastBlock->next = block; } else { firstBlock = block; } lastBlock = block; block->node = NULL; FreeInternal( block ); numBaseBlocks++; baseBlockMemory += baseBlockSize; } allowAllocs = false; } template void idDynamicBlockAlloc::SetLockMemory( bool lock ) { lockMemory = lock; } template void idDynamicBlockAlloc::FreeEmptyBaseBlocks( void ) { idDynamicBlock *block, *next; for ( block = firstBlock; block != NULL; block = next ) { next = block->next; if ( block->IsBaseBlock() && block->node != NULL && ( next == NULL || next->IsBaseBlock() ) ) { UnlinkFreeInternal( block ); if ( block->prev ) { block->prev->next = block->next; } else { firstBlock = block->next; } if ( block->next ) { block->next->prev = block->prev; } else { lastBlock = block->prev; } if ( lockMemory ) { idLib::sys->UnlockMemory( block, block->GetSize() + (int)sizeof( idDynamicBlock ) ); } numBaseBlocks--; baseBlockMemory -= block->GetSize() + (int)sizeof( idDynamicBlock ); Mem_Free16( block ); } } #ifdef DYNAMIC_BLOCK_ALLOC_CHECK CheckMemory(); #endif } template int idDynamicBlockAlloc::GetNumEmptyBaseBlocks( void ) const { int numEmptyBaseBlocks; idDynamicBlock *block; numEmptyBaseBlocks = 0; for ( block = firstBlock; block != NULL; block = block->next ) { if ( block->IsBaseBlock() && block->node != NULL && ( block->next == NULL || block->next->IsBaseBlock() ) ) { numEmptyBaseBlocks++; } } return numEmptyBaseBlocks; } template type *idDynamicBlockAlloc::Alloc( const int num ) { idDynamicBlock *block; numAllocs++; if ( num <= 0 ) { return NULL; } block = AllocInternal( num ); if ( block == NULL ) { return NULL; } block = ResizeInternal( block, num ); if ( block == NULL ) { return NULL; } #ifdef DYNAMIC_BLOCK_ALLOC_CHECK CheckMemory(); #endif numUsedBlocks++; usedBlockMemory += block->GetSize(); return block->GetMemory(); } template type *idDynamicBlockAlloc::Resize( type *ptr, const int num ) { numResizes++; if ( ptr == NULL ) { return Alloc( num ); } if ( num <= 0 ) { Free( ptr ); return NULL; } idDynamicBlock *block = ( idDynamicBlock * ) ( ( (byte *) ptr ) - (int)sizeof( idDynamicBlock ) ); usedBlockMemory -= block->GetSize(); block = ResizeInternal( block, num ); if ( block == NULL ) { return NULL; } #ifdef DYNAMIC_BLOCK_ALLOC_CHECK CheckMemory(); #endif usedBlockMemory += block->GetSize(); return block->GetMemory(); } template void idDynamicBlockAlloc::Free( type *ptr ) { numFrees++; if ( ptr == NULL ) { return; } idDynamicBlock *block = ( idDynamicBlock * ) ( ( (byte *) ptr ) - (int)sizeof( idDynamicBlock ) ); numUsedBlocks--; usedBlockMemory -= block->GetSize(); FreeInternal( block ); #ifdef DYNAMIC_BLOCK_ALLOC_CHECK CheckMemory(); #endif } template const char *idDynamicBlockAlloc::CheckMemory( const type *ptr ) const { idDynamicBlock *block; if ( ptr == NULL ) { return NULL; } block = ( idDynamicBlock * ) ( ( (byte *) ptr ) - (int)sizeof( idDynamicBlock ) ); if ( block->node != NULL ) { return "memory has been freed"; } #ifdef DYNAMIC_BLOCK_ALLOC_CHECK if ( block->id[0] != 0x11111111 || block->id[1] != 0x22222222 || block->id[2] != 0x33333333 ) { return "memory has invalid id"; } if ( block->allocator != (void*)this ) { return "memory was allocated with different allocator"; } #endif /* base blocks can be larger than baseBlockSize which can cause this code to fail idDynamicBlock *base; for ( base = firstBlock; base != NULL; base = base->next ) { if ( base->IsBaseBlock() ) { if ( ((int)block) >= ((int)base) && ((int)block) < ((int)base) + baseBlockSize ) { break; } } } if ( base == NULL ) { return "no base block found for memory"; } */ return NULL; } template void idDynamicBlockAlloc::Clear( void ) { firstBlock = lastBlock = NULL; allowAllocs = true; lockMemory = false; numBaseBlocks = 0; baseBlockMemory = 0; numUsedBlocks = 0; usedBlockMemory = 0; numFreeBlocks = 0; freeBlockMemory = 0; numAllocs = 0; numResizes = 0; numFrees = 0; #ifdef DYNAMIC_BLOCK_ALLOC_CHECK blockId[0] = 0x11111111; blockId[1] = 0x22222222; blockId[2] = 0x33333333; #endif } template idDynamicBlock *idDynamicBlockAlloc::AllocInternal( const int num ) { idDynamicBlock *block; int alignedBytes = ( num * sizeof( type ) + 15 ) & ~15; block = freeTree.FindSmallestLargerEqual( alignedBytes ); if ( block != NULL ) { UnlinkFreeInternal( block ); } else if ( allowAllocs ) { int allocSize = Max( baseBlockSize, alignedBytes + (int)sizeof( idDynamicBlock ) ); block = ( idDynamicBlock * ) Mem_Alloc16( allocSize ); if ( lockMemory ) { idLib::sys->LockMemory( block, baseBlockSize ); } #ifdef DYNAMIC_BLOCK_ALLOC_CHECK memcpy( block->id, blockId, sizeof( block->id ) ); block->allocator = (void*)this; #endif block->SetSize( allocSize - (int)sizeof( idDynamicBlock ), true ); block->next = NULL; block->prev = lastBlock; if ( lastBlock ) { lastBlock->next = block; } else { firstBlock = block; } lastBlock = block; block->node = NULL; numBaseBlocks++; baseBlockMemory += allocSize; } return block; } template idDynamicBlock *idDynamicBlockAlloc::ResizeInternal( idDynamicBlock *block, const int num ) { int alignedBytes = ( num * sizeof( type ) + 15 ) & ~15; #ifdef DYNAMIC_BLOCK_ALLOC_CHECK assert( block->id[0] == 0x11111111 && block->id[1] == 0x22222222 && block->id[2] == 0x33333333 && block->allocator == (void*)this ); #endif // if the new size is larger if ( alignedBytes > block->GetSize() ) { idDynamicBlock *nextBlock = block->next; // try to annexate the next block if it's free if ( nextBlock && !nextBlock->IsBaseBlock() && nextBlock->node != NULL && block->GetSize() + (int)sizeof( idDynamicBlock ) + nextBlock->GetSize() >= alignedBytes ) { UnlinkFreeInternal( nextBlock ); block->SetSize( block->GetSize() + (int)sizeof( idDynamicBlock ) + nextBlock->GetSize(), block->IsBaseBlock() ); block->next = nextBlock->next; if ( nextBlock->next ) { nextBlock->next->prev = block; } else { lastBlock = block; } } else { // allocate a new block and copy idDynamicBlock *oldBlock = block; block = AllocInternal( num ); if ( block == NULL ) { return NULL; } memcpy( block->GetMemory(), oldBlock->GetMemory(), oldBlock->GetSize() ); FreeInternal( oldBlock ); } } // if the unused space at the end of this block is large enough to hold a block with at least one element if ( block->GetSize() - alignedBytes - (int)sizeof( idDynamicBlock ) < Max( minBlockSize, (int)sizeof( type ) ) ) { return block; } idDynamicBlock *newBlock; newBlock = ( idDynamicBlock * ) ( ( (byte *) block ) + (int)sizeof( idDynamicBlock ) + alignedBytes ); #ifdef DYNAMIC_BLOCK_ALLOC_CHECK memcpy( newBlock->id, blockId, sizeof( newBlock->id ) ); newBlock->allocator = (void*)this; #endif newBlock->SetSize( block->GetSize() - alignedBytes - (int)sizeof( idDynamicBlock ), false ); newBlock->next = block->next; newBlock->prev = block; if ( newBlock->next ) { newBlock->next->prev = newBlock; } else { lastBlock = newBlock; } newBlock->node = NULL; block->next = newBlock; block->SetSize( alignedBytes, block->IsBaseBlock() ); FreeInternal( newBlock ); return block; } template void idDynamicBlockAlloc::FreeInternal( idDynamicBlock *block ) { assert( block->node == NULL ); #ifdef DYNAMIC_BLOCK_ALLOC_CHECK assert( block->id[0] == 0x11111111 && block->id[1] == 0x22222222 && block->id[2] == 0x33333333 && block->allocator == (void*)this ); #endif // try to merge with a next free block idDynamicBlock *nextBlock = block->next; if ( nextBlock && !nextBlock->IsBaseBlock() && nextBlock->node != NULL ) { UnlinkFreeInternal( nextBlock ); block->SetSize( block->GetSize() + (int)sizeof( idDynamicBlock ) + nextBlock->GetSize(), block->IsBaseBlock() ); block->next = nextBlock->next; if ( nextBlock->next ) { nextBlock->next->prev = block; } else { lastBlock = block; } } // try to merge with a previous free block idDynamicBlock *prevBlock = block->prev; if ( prevBlock && !block->IsBaseBlock() && prevBlock->node != NULL ) { UnlinkFreeInternal( prevBlock ); prevBlock->SetSize( prevBlock->GetSize() + (int)sizeof( idDynamicBlock ) + block->GetSize(), prevBlock->IsBaseBlock() ); prevBlock->next = block->next; if ( block->next ) { block->next->prev = prevBlock; } else { lastBlock = prevBlock; } LinkFreeInternal( prevBlock ); } else { LinkFreeInternal( block ); } } template ID_INLINE void idDynamicBlockAlloc::LinkFreeInternal( idDynamicBlock *block ) { block->node = freeTree.Add( block, block->GetSize() ); numFreeBlocks++; freeBlockMemory += block->GetSize(); } template ID_INLINE void idDynamicBlockAlloc::UnlinkFreeInternal( idDynamicBlock *block ) { freeTree.Remove( block->node ); block->node = NULL; numFreeBlocks--; freeBlockMemory -= block->GetSize(); } template void idDynamicBlockAlloc::CheckMemory( void ) const { idDynamicBlock *block; for ( block = firstBlock; block != NULL; block = block->next ) { // make sure the block is properly linked if ( block->prev == NULL ) { assert( firstBlock == block ); } else { assert( block->prev->next == block ); } if ( block->next == NULL ) { assert( lastBlock == block ); } else { assert( block->next->prev == block ); } } } #endif /* !__HEAP_H__ */