//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // // $NoKeywords: $ // // A growable memory class. //===========================================================================// #ifndef UTLFIXEDMEMORY_H #define UTLFIXEDMEMORY_H #ifdef _WIN32 #pragma once #endif #include "tier0/dbg.h" #include "tier0/platform.h" #include "tier0/memalloc.h" //#include "tier0/memdbgon.h" #include "mathlib/mathlib.h" #pragma warning (disable:4100) #pragma warning (disable:4514) //----------------------------------------------------------------------------- #ifdef UTLFIXEDMEMORY_TRACK #define UTLFIXEDMEMORY_TRACK_ALLOC() MemAlloc_RegisterAllocation( "||Sum of all UtlFixedMemory||", 0, NumAllocated() * sizeof(T), NumAllocated() * sizeof(T), 0 ) #define UTLFIXEDMEMORY_TRACK_FREE() if ( !m_pMemory ) ; else MemAlloc_RegisterDeallocation( "||Sum of all UtlFixedMemory||", 0, NumAllocated() * sizeof(T), NumAllocated() * sizeof(T), 0 ) #else #define UTLFIXEDMEMORY_TRACK_ALLOC() ((void)0) #define UTLFIXEDMEMORY_TRACK_FREE() ((void)0) #endif //----------------------------------------------------------------------------- // The CUtlFixedMemory class: // A growable memory class that allocates non-sequential blocks, but is indexed sequentially //----------------------------------------------------------------------------- template< class T > class CUtlFixedMemory { public: // constructor, destructor CUtlFixedMemory(ssize_t nGrowSize = 0, ssize_t nInitSize = 0); ~CUtlFixedMemory(); // Set the size by which the memory grows void Init(ssize_t nGrowSize = 0, ssize_t nInitSize = 0); // here to match CUtlMemory, but only used by ResetDbgInfo, so it can just return NULL T* Base() { return NULL; } const T* Base() const { return NULL; } protected: struct BlockHeader_t; public: class Iterator_t { public: Iterator_t(BlockHeader_t* p, ssize_t i) : m_pBlockHeader(p), m_nIndex(i) {} BlockHeader_t* m_pBlockHeader; ssize_t m_nIndex; bool operator==(const Iterator_t it) const { return m_pBlockHeader == it.m_pBlockHeader && m_nIndex == it.m_nIndex; } bool operator!=(const Iterator_t it) const { return m_pBlockHeader != it.m_pBlockHeader || m_nIndex != it.m_nIndex; } }; Iterator_t First() const { return m_pBlocks ? Iterator_t(m_pBlocks, 0) : InvalidIterator(); } Iterator_t Next(const Iterator_t& it) const { Assert(IsValidIterator(it)); if (!IsValidIterator(it)) return InvalidIterator(); BlockHeader_t* RESTRICT pHeader = it.m_pBlockHeader; if (it.m_nIndex + 1 < pHeader->m_nBlockSize) return Iterator_t(pHeader, it.m_nIndex + 1); return pHeader->m_pNext ? Iterator_t(pHeader->m_pNext, 0) : InvalidIterator(); } ssize_t GetIndex(const Iterator_t& it) const { Assert(IsValidIterator(it)); if (!IsValidIterator(it)) return InvalidIndex(); return (ssize_t)(HeaderToBlock(it.m_pBlockHeader) + it.m_nIndex); } bool IsIdxAfter(ssize_t i, const Iterator_t& it) const { Assert(IsValidIterator(it)); if (!IsValidIterator(it)) return false; if (IsInBlock(i, it.m_pBlockHeader)) return i > GetIndex(it); for (BlockHeader_t* RESTRICT pbh = it.m_pBlockHeader->m_pNext; pbh; pbh = pbh->m_pNext) { if (IsInBlock(i, pbh)) return true; } return false; } bool IsValidIterator(const Iterator_t& it) const { return it.m_pBlockHeader && it.m_nIndex >= 0 && it.m_nIndex < it.m_pBlockHeader->m_nBlockSize; } Iterator_t InvalidIterator() const { return Iterator_t(NULL, -1); } // element access T& operator[](ssize_t i); const T& operator[](ssize_t i) const; T& Element(ssize_t i); const T& Element(ssize_t i) const; // Can we use this index? bool IsIdxValid(ssize_t i) const; // Specify the invalid ('null') index that we'll only return on failure static const ssize_t INVALID_INDEX = 0; // For use with COMPILE_TIME_ASSERT static ssize_t InvalidIndex() { return INVALID_INDEX; } // Size ssize_t NumAllocated() const; ssize_t Count() const { return NumAllocated(); } // Grows memory by max(num,growsize), and returns the allocation index/ptr void Grow(ssize_t num = 1); // Makes sure we've got at least this much memory void EnsureCapacity(ssize_t num); // Memory deallocation void Purge(); protected: // Fast swap - WARNING: Swap invalidates all ptr-based indices!!! void Swap(CUtlFixedMemory< T >& mem); bool IsInBlock(ssize_t i, BlockHeader_t* pBlockHeader) const { T* p = (T*)i; const T* p0 = HeaderToBlock(pBlockHeader); return p >= p0 && p < p0 + pBlockHeader->m_nBlockSize; } struct BlockHeader_t { BlockHeader_t* m_pNext; ssize_t m_nBlockSize; }; const T* HeaderToBlock(const BlockHeader_t* pHeader) const { return (T*)(pHeader + 1); } const BlockHeader_t* BlockToHeader(const T* pBlock) const { return (BlockHeader_t*)(pBlock)-1; } BlockHeader_t* m_pBlocks; ssize_t m_nAllocationCount; ssize_t m_nGrowSize; }; //----------------------------------------------------------------------------- // constructor, destructor //----------------------------------------------------------------------------- template< class T > CUtlFixedMemory::CUtlFixedMemory(ssize_t nGrowSize, ssize_t nInitAllocationCount) : m_pBlocks(0), m_nAllocationCount(0), m_nGrowSize(0) { Init(nGrowSize, nInitAllocationCount); } template< class T > CUtlFixedMemory::~CUtlFixedMemory() { Purge(); } //----------------------------------------------------------------------------- // Fast swap - WARNING: Swap invalidates all ptr-based indices!!! //----------------------------------------------------------------------------- template< class T > void CUtlFixedMemory::Swap(CUtlFixedMemory< T >& mem) { V_swap(m_pBlocks, mem.m_pBlocks); V_swap(m_nAllocationCount, mem.m_nAllocationCount); V_swap(m_nGrowSize, mem.m_nGrowSize); } //----------------------------------------------------------------------------- // Set the size by which the memory grows - round up to the next power of 2 //----------------------------------------------------------------------------- template< class T > void CUtlFixedMemory::Init(ssize_t nGrowSize /* = 0 */, ssize_t nInitSize /* = 0 */) { Purge(); m_nGrowSize = nGrowSize; Grow(nInitSize); } //----------------------------------------------------------------------------- // element access //----------------------------------------------------------------------------- template< class T > inline T& CUtlFixedMemory::operator[](ssize_t i) { Assert(IsIdxValid(i)); return *(T*)i; } template< class T > inline const T& CUtlFixedMemory::operator[](ssize_t i) const { Assert(IsIdxValid(i)); return *(T*)i; } template< class T > inline T& CUtlFixedMemory::Element(ssize_t i) { Assert(IsIdxValid(i)); return *(T*)i; } template< class T > inline const T& CUtlFixedMemory::Element(ssize_t i) const { Assert(IsIdxValid(i)); return *(T*)i; } //----------------------------------------------------------------------------- // Size //----------------------------------------------------------------------------- template< class T > inline ssize_t CUtlFixedMemory::NumAllocated() const { return m_nAllocationCount; } //----------------------------------------------------------------------------- // Is element index valid? //----------------------------------------------------------------------------- template< class T > inline bool CUtlFixedMemory::IsIdxValid(ssize_t i) const { #ifdef _DEBUG for (BlockHeader_t* pbh = m_pBlocks; pbh; pbh = pbh->m_pNext) { if (IsInBlock(i, pbh)) return true; } return false; #else return i != InvalidIndex(); #endif } template< class T > void CUtlFixedMemory::Grow(ssize_t num) { if (num <= 0) return; ssize_t nBlockSize = m_nGrowSize; if (nBlockSize == 0) { if (m_nAllocationCount) { nBlockSize = m_nAllocationCount; } else { // Compute an allocation which is at least as big as a cache line... nBlockSize = (31 + sizeof(T)) / sizeof(T); Assert(nBlockSize); } } if (nBlockSize < num) { ssize_t n = (num + nBlockSize - 1) / nBlockSize; Assert(n * nBlockSize >= num); Assert((n - 1) * nBlockSize < num); nBlockSize *= n; } m_nAllocationCount += nBlockSize; MEM_ALLOC_CREDIT_CLASS(); BlockHeader_t* RESTRICT pBlockHeader = (BlockHeader_t*)malloc(sizeof(BlockHeader_t) + nBlockSize * sizeof(T)); if (!pBlockHeader) { Error(eDLL_T::COMMON, EXIT_FAILURE, "CUtlFixedMemory overflow!\n"); } pBlockHeader->m_pNext = NULL; pBlockHeader->m_nBlockSize = nBlockSize; if (!m_pBlocks) { m_pBlocks = pBlockHeader; } else { #if 1 // IsIdxAfter assumes that newly allocated blocks are at the end BlockHeader_t* RESTRICT pbh = m_pBlocks; while (pbh->m_pNext) { pbh = pbh->m_pNext; } pbh->m_pNext = pBlockHeader; #else pBlockHeader = m_pBlocks; pBlockHeader->m_pNext = m_pBlocks; #endif } } //----------------------------------------------------------------------------- // Makes sure we've got at least this much memory //----------------------------------------------------------------------------- template< class T > inline void CUtlFixedMemory::EnsureCapacity(ssize_t num) { Grow(num - NumAllocated()); } //----------------------------------------------------------------------------- // Memory deallocation //----------------------------------------------------------------------------- template< class T > void CUtlFixedMemory::Purge() { if (!m_pBlocks) return; for (BlockHeader_t* pbh = m_pBlocks; pbh; ) { BlockHeader_t* pFree = pbh; pbh = pbh->m_pNext; MemAllocSingleton()->Free(pFree); } m_pBlocks = NULL; m_nAllocationCount = 0; } //#include "tier0/memdbgoff.h" #endif // UTLFIXEDMEMORY_H