//====== Copyright � 1996-2005, Valve Corporation, All rights reserved. =======// // // Purpose: // // $NoKeywords: $ // // A growable memory class. //=============================================================================// #ifndef UTLMEMORY_H #define UTLMEMORY_H #ifdef _WIN32 #pragma once #endif #include "tier0/memstd.h" #include "tier0/memalloc.h" #include "mathlib/mathlib.h" //#include "tier0/memdbgon.h" #pragma warning (disable:4100) #pragma warning (disable:4514) //----------------------------------------------------------------------------- #ifdef UTLMEMORY_TRACK #define UTLMEMORY_TRACK_ALLOC() MemAlloc_RegisterAllocation( "||Sum of all UtlMemory||", 0, m_nAllocationCount * sizeof(T), m_nAllocationCount * sizeof(T), 0 ) #define UTLMEMORY_TRACK_FREE() if ( !m_pMemory ) ; else MemAlloc_RegisterDeallocation( "||Sum of all UtlMemory||", 0, m_nAllocationCount * sizeof(T), m_nAllocationCount * sizeof(T), 0 ) #else #define UTLMEMORY_TRACK_ALLOC() ((void)0) #define UTLMEMORY_TRACK_FREE() ((void)0) #endif //----------------------------------------------------------------------------- // The CUtlMemory class: // A growable memory class which doubles in size by default. //----------------------------------------------------------------------------- template< class T, class I = int64_t > class CUtlMemory { template< class A, class B> friend class CUtlVector; template< class A, size_t B> friend class CUtlVectorFixedGrowableCompat; public: // constructor, destructor CUtlMemory(int64_t nGrowSize = 0, int64_t nInitSize = 0); CUtlMemory(T* pMemory, int64_t numElements); CUtlMemory(const T* pMemory, int64_t numElements); ~CUtlMemory(); CUtlMemory(const CUtlMemory&) = delete; CUtlMemory& operator=(const CUtlMemory&) = delete; CUtlMemory(CUtlMemory&& moveFrom); CUtlMemory& operator=(CUtlMemory&& moveFrom); // Set the size by which the memory grows void Init(int64_t nGrowSize = 0, int64_t nInitSize = 0); class Iterator_t { public: Iterator_t(I i) : index(i) {} I index; bool operator==(const Iterator_t it) const { return index == it.index; } bool operator!=(const Iterator_t it) const { return index != it.index; } }; Iterator_t First() const { return Iterator_t(IsIdxValid(0) ? 0 : InvalidIndex()); } Iterator_t Next(const Iterator_t& it) const { return Iterator_t(IsIdxValid(it.index + 1) ? it.index + 1 : InvalidIndex()); } I GetIndex(const Iterator_t& it) const { return it.index; } bool IsIdxAfter(I i, const Iterator_t& it) const { return i > it.index; } bool IsValidIterator(const Iterator_t& it) const { return IsIdxValid(it.index); } Iterator_t InvalidIterator() const { return Iterator_t(InvalidIndex()); } // element access T& operator[](I i); const T& operator[](I i) const; T& Element(I i); const T& Element(I i) const; // Can we use this index? bool IsIdxValid(I i) const; // Specify the invalid ('null') index that we'll only return on failure static const I INVALID_INDEX = (I)-1; // For use with COMPILE_TIME_ASSERT static I InvalidIndex() { return INVALID_INDEX; } // Gets the base address (can change when adding elements!) T* Base(); const T* Base() const; // Attaches the buffer to external memory.... void SetExternalBuffer(T* pMemory, int64_t numElements); void SetExternalBuffer(const T* pMemory, int64_t numElements); void AssumeMemory(T* pMemory, int64_t nSize); T* Detach(); void* DetachMemory(); // Fast swap void Swap(CUtlMemory< T, I >& mem); // Switches the buffer from an external memory buffer to a reallocatable buffer // Will copy the current contents of the external buffer to the reallocatable buffer void ConvertToGrowableMemory(int64_t nGrowSize); // Size int64_t NumAllocated() const; int64_t Count() const; // Grows the memory, so that at least allocated + num elements are allocated void Grow(int64_t num = 1); // Makes sure we've got at least this much memory void EnsureCapacity(int64_t num); // Memory deallocation void Purge(); // Purge all but the given number of elements void Purge(int64_t numElements); // is the memory externally allocated? bool IsExternallyAllocated() const; // is the memory read only? bool IsReadOnly() const; // Set the size by which the memory grows void SetGrowSize(int64_t size); protected: void ValidateGrowSize() { #ifdef _X360 if (m_nGrowSize && m_nGrowSize != EXTERNAL_BUFFER_MARKER) { // Max grow size at 128 bytes on XBOX const int64_t MAX_GROW = 128; if (m_nGrowSize * sizeof(T) > MAX_GROW) { m_nGrowSize = max(1, MAX_GROW / sizeof(T)); } } #endif } enum { EXTERNAL_BUFFER_MARKER = -1, EXTERNAL_CONST_BUFFER_MARKER = -2, }; T* m_pMemory; int64_t m_nAllocationCount; int64_t m_nGrowSize; }; //----------------------------------------------------------------------------- // The CUtlMemory class: // A growable memory class which doubles in size by default. //----------------------------------------------------------------------------- template< class T, size_t SIZE, class I = int64_t > class CUtlMemoryFixedGrowable : public CUtlMemory< T, I > { typedef CUtlMemory< T, I > BaseClass; public: CUtlMemoryFixedGrowable(int64_t nGrowSize = 0, int64_t nInitSize = SIZE) : BaseClass(m_pFixedMemory, SIZE) { Assert(nInitSize == 0 || nInitSize == SIZE); m_nMallocGrowSize = nGrowSize; } void Grow(int64_t nCount = 1) { if (this->IsExternallyAllocated()) { this->ConvertToGrowableMemory(m_nMallocGrowSize); } BaseClass::Grow(nCount); } void EnsureCapacity(int64_t num) { if (CUtlMemory::m_nAllocationCount >= num) return; if (this->IsExternallyAllocated()) { // Can't grow a buffer whose memory was externally allocated this->ConvertToGrowableMemory(m_nMallocGrowSize); } BaseClass::EnsureCapacity(num); } private: int64_t m_nMallocGrowSize; T m_pFixedMemory[SIZE]; }; //----------------------------------------------------------------------------- // The CUtlMemoryFixed class: // A fixed memory class //----------------------------------------------------------------------------- template< typename T, size_t SIZE, int64_t nAlignment = 0 > class CUtlMemoryFixed { public: // constructor, destructor CUtlMemoryFixed(int64_t nGrowSize = 0, int64_t nInitSize = 0) { Assert(nInitSize == 0 || nInitSize == SIZE); } CUtlMemoryFixed(T* pMemory, int64_t numElements) { Assert(0); } // Can we use this index? bool IsIdxValid(int64_t i) const { return (i >= 0) && (i < SIZE); } // Specify the invalid ('null') index that we'll only return on failure static const int64_t INVALID_INDEX = -1; // For use with COMPILE_TIME_ASSERT static int64_t InvalidIndex() { return INVALID_INDEX; } // Gets the base address T* Base() { if (nAlignment == 0) return (T*)(&m_Memory[0]); else return (T*)AlignValue(&m_Memory[0], nAlignment); } const T* Base() const { if (nAlignment == 0) return (T*)(&m_Memory[0]); else return (T*)AlignValue(&m_Memory[0], nAlignment); } // element access T& operator[](int64_t i) { Assert(IsIdxValid(i)); return Base()[i]; } const T& operator[](int64_t i) const { Assert(IsIdxValid(i)); return Base()[i]; } T& Element(int64_t i) { Assert(IsIdxValid(i)); return Base()[i]; } const T& Element(int64_t i) const { Assert(IsIdxValid(i)); return Base()[i]; } // Attaches the buffer to external memory.... void SetExternalBuffer(T* pMemory, int64_t numElements) { Assert(0); } // Size int64_t NumAllocated() const { return SIZE; } int64_t Count() const { return SIZE; } // Grows the memory, so that at least allocated + num elements are allocated void Grow(int64_t num = 1) { Assert(0); } // Makes sure we've got at least this much memory void EnsureCapacity(int64_t num) { Assert(num <= SIZE); } // Memory deallocation void Purge() {} // Purge all but the given number of elements (NOT IMPLEMENTED IN CUtlMemoryFixed) void Purge(int64_t numElements) { Assert(0); } // is the memory externally allocated? bool IsExternallyAllocated() const { return false; } // Set the size by which the memory grows void SetGrowSize(int64_t size) {} class Iterator_t { public: Iterator_t(int64_t i) : index(i) {} int64_t index; bool operator==(const Iterator_t it) const { return index == it.index; } bool operator!=(const Iterator_t it) const { return index != it.index; } }; Iterator_t First() const { return Iterator_t(IsIdxValid(0) ? 0 : InvalidIndex()); } Iterator_t Next(const Iterator_t& it) const { return Iterator_t(IsIdxValid(it.index + 1) ? it.index + 1 : InvalidIndex()); } int64_t GetIndex(const Iterator_t& it) const { return it.index; } bool IsIdxAfter(int64_t i, const Iterator_t& it) const { return i > it.index; } bool IsValidIterator(const Iterator_t& it) const { return IsIdxValid(it.index); } Iterator_t InvalidIterator() const { return Iterator_t(InvalidIndex()); } private: char m_Memory[SIZE * sizeof(T) + nAlignment]; }; #ifdef _LINUX #define REMEMBER_ALLOC_SIZE_FOR_VALGRIND 1 #endif //----------------------------------------------------------------------------- // The CUtlMemoryConservative class: // A dynamic memory class that tries to minimize overhead (itself small, no custom grow factor) //----------------------------------------------------------------------------- template< typename T > class CUtlMemoryConservative { public: // constructor, destructor CUtlMemoryConservative(int64_t nGrowSize = 0, int64_t nInitSize = 0) : m_pMemory(NULL) { #ifdef REMEMBER_ALLOC_SIZE_FOR_VALGRIND m_nCurAllocSize = 0; #endif } CUtlMemoryConservative(T* pMemory, int64_t numElements) { Assert(0); } ~CUtlMemoryConservative() { if (m_pMemory) free(m_pMemory); } // Can we use this index? bool IsIdxValid(int64_t i) const { return (IsDebug()) ? (i >= 0 && i < NumAllocated()) : (i >= 0); } static int64_t InvalidIndex() { return -1; } // Gets the base address T* Base() { return m_pMemory; } const T* Base() const { return m_pMemory; } // element access T& operator[](int64_t i) { Assert(IsIdxValid(i)); return Base()[i]; } const T& operator[](int64_t i) const { Assert(IsIdxValid(i)); return Base()[i]; } T& Element(int64_t i) { Assert(IsIdxValid(i)); return Base()[i]; } const T& Element(int64_t i) const { Assert(IsIdxValid(i)); return Base()[i]; } // Attaches the buffer to external memory.... void SetExternalBuffer(T* pMemory, int64_t numElements) { Assert(0); } // Size FORCEINLINE void RememberAllocSize(size_t sz) { #ifdef REMEMBER_ALLOC_SIZE_FOR_VALGRIND m_nCurAllocSize = sz; #endif } size_t AllocSize(void) const { #ifdef REMEMBER_ALLOC_SIZE_FOR_VALGRIND return m_nCurAllocSize; #else //return (m_pMemory) ? MemAllocSingleton()->GetSize(m_pMemory) : 0; // FIXME: reverse vftable and get 'GetSize' return 0; #endif } int64_t NumAllocated() const { return AllocSize() / sizeof(T); } int64_t Count() const { return NumAllocated(); } FORCEINLINE void ReAlloc(size_t sz) { m_pMemory = (T*)realloc(m_pMemory, sz); RememberAllocSize(sz); } // Grows the memory, so that at least allocated + num elements are allocated void Grow(int64_t num = 1) { int64_t nCurN = NumAllocated(); ReAlloc((nCurN + num) * sizeof(T)); } // Makes sure we've got at least this much memory void EnsureCapacity(int64_t num) { size_t nSize = sizeof(T) * MAX(num, Count()); ReAlloc(nSize); } // Memory deallocation void Purge() { free(m_pMemory); RememberAllocSize(0); m_pMemory = NULL; } // Purge all but the given number of elements void Purge(int64_t numElements) { ReAlloc(numElements * sizeof(T)); } // is the memory externally allocated? bool IsExternallyAllocated() const { return false; } // Set the size by which the memory grows void SetGrowSize(int64_t size) {} class Iterator_t { public: Iterator_t(int64_t i, int64_t _limit) : index(i), limit(_limit) {} int64_t index; int64_t limit; bool operator==(const Iterator_t it) const { return index == it.index; } bool operator!=(const Iterator_t it) const { return index != it.index; } }; Iterator_t First() const { int64_t limit = NumAllocated(); return Iterator_t(limit ? 0 : InvalidIndex(), limit); } Iterator_t Next(const Iterator_t& it) const { return Iterator_t((it.index + 1 < it.limit) ? it.index + 1 : InvalidIndex(), it.limit); } int64_t GetIndex(const Iterator_t& it) const { return it.index; } bool IsIdxAfter(int64_t i, const Iterator_t& it) const { return i > it.index; } bool IsValidIterator(const Iterator_t& it) const { return IsIdxValid(it.index) && (it.index < it.limit); } Iterator_t InvalidIterator() const { return Iterator_t(InvalidIndex(), 0); } private: T* m_pMemory; #ifdef REMEMBER_ALLOC_SIZE_FOR_VALGRIND size_t m_nCurAllocSize; #endif }; //----------------------------------------------------------------------------- // constructor, destructor //----------------------------------------------------------------------------- template< class T, class I > CUtlMemory::CUtlMemory(int64_t nGrowSize, int64_t nInitAllocationCount) : m_pMemory(0), m_nAllocationCount(nInitAllocationCount), m_nGrowSize(nGrowSize) { ValidateGrowSize(); Assert(nGrowSize >= 0); if (m_nAllocationCount) { UTLMEMORY_TRACK_ALLOC(); MEM_ALLOC_CREDIT_CLASS(); m_pMemory = (T*)malloc(m_nAllocationCount * sizeof(T)); } } template< class T, class I > CUtlMemory::CUtlMemory(T* pMemory, int64_t numElements) : m_pMemory(pMemory), m_nAllocationCount(numElements) { // Special marker indicating externally supplied modifyable memory m_nGrowSize = EXTERNAL_BUFFER_MARKER; } template< class T, class I > CUtlMemory::CUtlMemory(const T* pMemory, int64_t numElements) : m_pMemory((T*)pMemory), m_nAllocationCount(numElements) { // Special marker indicating externally supplied modifyable memory m_nGrowSize = EXTERNAL_CONST_BUFFER_MARKER; } template< class T, class I > CUtlMemory::~CUtlMemory() { Purge(); #ifdef _DEBUG m_pMemory = reinterpret_cast(0xFEFEBAAD); m_nAllocationCount = 0x7BADF00D; #endif } template< class T, class I > CUtlMemory::CUtlMemory(CUtlMemory&& moveFrom) : m_pMemory(moveFrom.m_pMemory) , m_nAllocationCount(moveFrom.m_nAllocationCount) , m_nGrowSize(moveFrom.m_nGrowSize) { moveFrom.m_pMemory = nullptr; moveFrom.m_nAllocationCount = 0; moveFrom.m_nGrowSize = 0; } template< class T, class I > CUtlMemory& CUtlMemory::operator=(CUtlMemory&& moveFrom) { // Copy member variables to locals before purge to handle self-assignment T* pMemory = moveFrom.m_pMemory; int64_t nAllocationCount = moveFrom.m_nAllocationCount; int64_t nGrowSize = moveFrom.m_nGrowSize; moveFrom.m_pMemory = nullptr; moveFrom.m_nAllocationCount = 0; moveFrom.m_nGrowSize = 0; // If this is a self-assignment, Purge() is a no-op here Purge(); m_pMemory = pMemory; m_nAllocationCount = nAllocationCount; m_nGrowSize = nGrowSize; return *this; } template< class T, class I > void CUtlMemory::Init(int64_t nGrowSize /*= 0*/, int64_t nInitSize /*= 0*/) { Purge(); m_nGrowSize = nGrowSize; m_nAllocationCount = nInitSize; ValidateGrowSize(); Assert(nGrowSize >= 0); if (m_nAllocationCount) { UTLMEMORY_TRACK_ALLOC(); MEM_ALLOC_CREDIT_CLASS(); m_pMemory = (T*)malloc(m_nAllocationCount * sizeof(T)); } } //----------------------------------------------------------------------------- // Fast swap //----------------------------------------------------------------------------- template< class T, class I > void CUtlMemory::Swap(CUtlMemory& mem) { V_swap(m_nGrowSize, mem.m_nGrowSize); V_swap(m_pMemory, mem.m_pMemory); V_swap(m_nAllocationCount, mem.m_nAllocationCount); } //----------------------------------------------------------------------------- // Switches the buffer from an external memory buffer to a reallocatable buffer //----------------------------------------------------------------------------- template< class T, class I > void CUtlMemory::ConvertToGrowableMemory(int64_t nGrowSize) { if (!IsExternallyAllocated()) return; m_nGrowSize = nGrowSize; if (m_nAllocationCount) { UTLMEMORY_TRACK_ALLOC(); MEM_ALLOC_CREDIT_CLASS(); int64_t nNumBytes = m_nAllocationCount * sizeof(T); T* pMemory = (T*)malloc(nNumBytes); memcpy(pMemory, m_pMemory, nNumBytes); m_pMemory = pMemory; } else { m_pMemory = NULL; } } //----------------------------------------------------------------------------- // Attaches the buffer to external memory.... //----------------------------------------------------------------------------- template< class T, class I > void CUtlMemory::SetExternalBuffer(T* pMemory, int64_t numElements) { // Blow away any existing allocated memory Purge(); m_pMemory = pMemory; m_nAllocationCount = numElements; // Indicate that we don't own the memory m_nGrowSize = EXTERNAL_BUFFER_MARKER; } template< class T, class I > void CUtlMemory::SetExternalBuffer(const T* pMemory, int64_t numElements) { // Blow away any existing allocated memory Purge(); m_pMemory = const_cast(pMemory); m_nAllocationCount = numElements; // Indicate that we don't own the memory m_nGrowSize = EXTERNAL_CONST_BUFFER_MARKER; } template< class T, class I > void CUtlMemory::AssumeMemory(T* pMemory, int64_t numElements) { // Blow away any existing allocated memory Purge(); // Simply take the pointer but don't mark us as external m_pMemory = pMemory; m_nAllocationCount = numElements; } template< class T, class I > void* CUtlMemory::DetachMemory() { if (IsExternallyAllocated()) return NULL; void* pMemory = m_pMemory; m_pMemory = 0; m_nAllocationCount = 0; return pMemory; } template< class T, class I > inline T* CUtlMemory::Detach() { return (T*)DetachMemory(); } //----------------------------------------------------------------------------- // element access //----------------------------------------------------------------------------- template< class T, class I > inline T& CUtlMemory::operator[](I i) { Assert(!IsReadOnly()); Assert(IsIdxValid(i)); return m_pMemory[i]; } template< class T, class I > inline const T& CUtlMemory::operator[](I i) const { Assert(IsIdxValid(i)); return m_pMemory[i]; } template< class T, class I > inline T& CUtlMemory::Element(I i) { Assert(!IsReadOnly()); Assert(IsIdxValid(i)); return m_pMemory[i]; } template< class T, class I > inline const T& CUtlMemory::Element(I i) const { Assert(IsIdxValid(i)); return m_pMemory[i]; } //----------------------------------------------------------------------------- // is the memory externally allocated? //----------------------------------------------------------------------------- template< class T, class I > bool CUtlMemory::IsExternallyAllocated() const { return (m_nGrowSize < 0); } //----------------------------------------------------------------------------- // is the memory read only? //----------------------------------------------------------------------------- template< class T, class I > bool CUtlMemory::IsReadOnly() const { return (m_nGrowSize == EXTERNAL_CONST_BUFFER_MARKER); } template< class T, class I > void CUtlMemory::SetGrowSize(int64_t nSize) { Assert(!IsExternallyAllocated()); Assert(nSize >= 0); m_nGrowSize = nSize; ValidateGrowSize(); } //----------------------------------------------------------------------------- // Gets the base address (can change when adding elements!) //----------------------------------------------------------------------------- template< class T, class I > inline T* CUtlMemory::Base() { Assert(!IsReadOnly()); return m_pMemory; } template< class T, class I > inline const T* CUtlMemory::Base() const { return m_pMemory; } //----------------------------------------------------------------------------- // Size //----------------------------------------------------------------------------- template< class T, class I > inline int64_t CUtlMemory::NumAllocated() const { return m_nAllocationCount; } template< class T, class I > inline int64_t CUtlMemory::Count() const { return m_nAllocationCount; } //----------------------------------------------------------------------------- // Is element index valid? //----------------------------------------------------------------------------- template< class T, class I > inline bool CUtlMemory::IsIdxValid(I i) const { // GCC warns if I is an unsigned type and we do a ">= 0" against it (since the comparison is always 0). // We get the warning even if we cast inside the expression. It only goes away if we assign to another variable. long x = i; return (x >= 0) && (x < m_nAllocationCount); } //----------------------------------------------------------------------------- // Grows the memory //----------------------------------------------------------------------------- inline int64_t UtlMemory_CalcNewAllocationCount(int64_t nAllocationCount, int64_t nGrowSize, int64_t nNewSize, int64_t nBytesItem) { if (nGrowSize) { nAllocationCount = ((1 + ((nNewSize - 1) / nGrowSize)) * nGrowSize); } else { if (!nAllocationCount) { // Compute an allocation which is at least as big as a cache line... nAllocationCount = (31 + nBytesItem) / nBytesItem; // If the requested amount is larger then compute an allocation which // is exactly the right size. Otherwise we can end up with wasted memory // when CUtlVector::EnsureCount(n) is called. if (nAllocationCount < nNewSize) nAllocationCount = nNewSize; } while (nAllocationCount < nNewSize) { #ifndef _X360 nAllocationCount *= 2; #else int64_t nNewAllocationCount = (nAllocationCount * 9) / 8; // 12.5 % if (nNewAllocationCount > nAllocationCount) nAllocationCount = nNewAllocationCount; else nAllocationCount *= 2; #endif } } return nAllocationCount; } template< class T, class I > void CUtlMemory::Grow(int64_t num) { Assert(num > 0); if (IsExternallyAllocated()) { // Can't grow a buffer whose memory was externally allocated Assert(0); return; } // Make sure we have at least numallocated + num allocations. // Use the grow rules specified for this memory (in m_nGrowSize) int64_t nAllocationRequested = m_nAllocationCount + num; UTLMEMORY_TRACK_FREE(); int64_t nNewAllocationCount = UtlMemory_CalcNewAllocationCount(m_nAllocationCount, m_nGrowSize, nAllocationRequested, sizeof(T)); // if m_nAllocationRequested wraps index type I, recalculate if ((int64_t)(I)nNewAllocationCount < nAllocationRequested) { if ((int64_t)(I)nNewAllocationCount == 0 && (int64_t)(I)(nNewAllocationCount - 1) >= nAllocationRequested) { --nNewAllocationCount; // deal w/ the common case of m_nAllocationCount == MAX_USHORT + 1 } else { if ((int64_t)(I)nAllocationRequested != nAllocationRequested) { // we've been asked to grow memory to a size s.t. the index type can't address the requested amount of memory Assert(0); return; } while ((int64_t)(I)nNewAllocationCount < nAllocationRequested) { nNewAllocationCount = (nNewAllocationCount + nAllocationRequested) / 2; } } } m_nAllocationCount = nNewAllocationCount; UTLMEMORY_TRACK_ALLOC(); if (m_pMemory) { MEM_ALLOC_CREDIT_CLASS(); m_pMemory = (T*)realloc(m_pMemory, m_nAllocationCount * sizeof(T)); Assert(m_pMemory); } else { MEM_ALLOC_CREDIT_CLASS(); m_pMemory = (T*)malloc(m_nAllocationCount * sizeof(T)); Assert(m_pMemory); } } //----------------------------------------------------------------------------- // Makes sure we've got at least this much memory //----------------------------------------------------------------------------- template< class T, class I > inline void CUtlMemory::EnsureCapacity(int64_t num) { if (m_nAllocationCount >= num) return; if (IsExternallyAllocated()) { // Can't grow a buffer whose memory was externally allocated Assert(0); return; } UTLMEMORY_TRACK_FREE(); m_nAllocationCount = num; UTLMEMORY_TRACK_ALLOC(); if (m_pMemory) { MEM_ALLOC_CREDIT_CLASS(); m_pMemory = (T*)realloc(m_pMemory, m_nAllocationCount * sizeof(T)); } else { MEM_ALLOC_CREDIT_CLASS(); m_pMemory = (T*)malloc(m_nAllocationCount * sizeof(T)); } } //----------------------------------------------------------------------------- // Memory deallocation //----------------------------------------------------------------------------- template< class T, class I > void CUtlMemory::Purge() { if (!IsExternallyAllocated()) { if (m_pMemory) { UTLMEMORY_TRACK_FREE(); free((void*)m_pMemory); m_pMemory = 0; } m_nAllocationCount = 0; } } template< class T, class I > void CUtlMemory::Purge(int64_t numElements) { Assert(numElements >= 0); if (numElements > m_nAllocationCount) { // Ensure this isn't a grow request in disguise. Assert(numElements <= m_nAllocationCount); return; } // If we have zero elements, simply do a purge: if (numElements == 0) { Purge(); return; } if (IsExternallyAllocated()) { // Can't shrink a buffer whose memory was externally allocated, fail silently like purge return; } // If the number of elements is the same as the allocation count, we are done. if (numElements == m_nAllocationCount) { return; } if (!m_pMemory) { // Allocation count is non zero, but memory is null. Assert(m_pMemory); return; } UTLMEMORY_TRACK_FREE(); m_nAllocationCount = numElements; UTLMEMORY_TRACK_ALLOC(); // Allocation count > 0, shrink it down. MEM_ALLOC_CREDIT_CLASS(); m_pMemory = (T*)realloc(m_pMemory, m_nAllocationCount * sizeof(T)); } //----------------------------------------------------------------------------- // The CUtlMemory class: // A growable memory class which doubles in size by default. //----------------------------------------------------------------------------- template< class T, int64_t nAlignment > class CUtlMemoryAligned : public CUtlMemory { public: // constructor, destructor CUtlMemoryAligned(int64_t nGrowSize = 0, int64_t nInitSize = 0); CUtlMemoryAligned(T* pMemory, int64_t numElements); CUtlMemoryAligned(const T* pMemory, int64_t numElements); ~CUtlMemoryAligned(); // Attaches the buffer to external memory.... void SetExternalBuffer(T* pMemory, int64_t numElements); void SetExternalBuffer(const T* pMemory, int64_t numElements); // Grows the memory, so that at least allocated + num elements are allocated void Grow(int64_t num = 1); // Makes sure we've got at least this much memory void EnsureCapacity(int64_t num); // Memory deallocation void Purge(); // Purge all but the given number of elements (NOT IMPLEMENTED IN CUtlMemoryAligned) void Purge(int64_t numElements) { Assert(0); } private: void* Align(const void* pAddr); }; //----------------------------------------------------------------------------- // Aligns a pointer //----------------------------------------------------------------------------- template< class T, int64_t nAlignment > void* CUtlMemoryAligned::Align(const void* pAddr) { size_t nAlignmentMask = nAlignment - 1; return (void*)(((size_t)pAddr + nAlignmentMask) & (~nAlignmentMask)); } //----------------------------------------------------------------------------- // constructor, destructor //----------------------------------------------------------------------------- template< class T, int64_t nAlignment > CUtlMemoryAligned::CUtlMemoryAligned(int64_t nGrowSize, int64_t nInitAllocationCount) { CUtlMemory::m_pMemory = 0; CUtlMemory::m_nAllocationCount = nInitAllocationCount; CUtlMemory::m_nGrowSize = nGrowSize; this->ValidateGrowSize(); // Alignment must be a power of two COMPILE_TIME_ASSERT((nAlignment & (nAlignment - 1)) == 0); Assert((nGrowSize >= 0) && (nGrowSize != CUtlMemory::EXTERNAL_BUFFER_MARKER)); if (CUtlMemory::m_nAllocationCount) { UTLMEMORY_TRACK_ALLOC(); MEM_ALLOC_CREDIT_CLASS(); CUtlMemory::m_pMemory = (T*)_aligned_malloc(nInitAllocationCount * sizeof(T), nAlignment); } } template< class T, int64_t nAlignment > CUtlMemoryAligned::CUtlMemoryAligned(T* pMemory, int64_t numElements) { // Special marker indicating externally supplied memory CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_BUFFER_MARKER; CUtlMemory::m_pMemory = (T*)Align(pMemory); CUtlMemory::m_nAllocationCount = ((int64_t)(pMemory + numElements) - (int64_t)CUtlMemory::m_pMemory) / sizeof(T); } template< class T, int64_t nAlignment > CUtlMemoryAligned::CUtlMemoryAligned(const T* pMemory, int64_t numElements) { // Special marker indicating externally supplied memory CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_CONST_BUFFER_MARKER; CUtlMemory::m_pMemory = (T*)Align(pMemory); CUtlMemory::m_nAllocationCount = ((int64_t)(pMemory + numElements) - (int64_t)CUtlMemory::m_pMemory) / sizeof(T); } template< class T, int64_t nAlignment > CUtlMemoryAligned::~CUtlMemoryAligned() { Purge(); } //----------------------------------------------------------------------------- // Attaches the buffer to external memory.... //----------------------------------------------------------------------------- template< class T, int64_t nAlignment > void CUtlMemoryAligned::SetExternalBuffer(T* pMemory, int64_t numElements) { // Blow away any existing allocated memory Purge(); CUtlMemory::m_pMemory = (T*)Align(pMemory); CUtlMemory::m_nAllocationCount = ((int64_t)(pMemory + numElements) - (int64_t)CUtlMemory::m_pMemory) / sizeof(T); // Indicate that we don't own the memory CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_BUFFER_MARKER; } template< class T, int64_t nAlignment > void CUtlMemoryAligned::SetExternalBuffer(const T* pMemory, int64_t numElements) { // Blow away any existing allocated memory Purge(); CUtlMemory::m_pMemory = (T*)Align(pMemory); CUtlMemory::m_nAllocationCount = ((int64_t)(pMemory + numElements) - (int64_t)CUtlMemory::m_pMemory) / sizeof(T); // Indicate that we don't own the memory CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_CONST_BUFFER_MARKER; } //----------------------------------------------------------------------------- // Grows the memory //----------------------------------------------------------------------------- template< class T, int64_t nAlignment > void CUtlMemoryAligned::Grow(int64_t num) { Assert(num > 0); if (this->IsExternallyAllocated()) { // Can't grow a buffer whose memory was externally allocated Assert(0); return; } UTLMEMORY_TRACK_FREE(); // Make sure we have at least numallocated + num allocations. // Use the grow rules specified for this memory (in m_nGrowSize) int64_t nAllocationRequested = CUtlMemory::m_nAllocationCount + num; CUtlMemory::m_nAllocationCount = UtlMemory_CalcNewAllocationCount(CUtlMemory::m_nAllocationCount, CUtlMemory::m_nGrowSize, nAllocationRequested, sizeof(T)); UTLMEMORY_TRACK_ALLOC(); if (CUtlMemory::m_pMemory) { MEM_ALLOC_CREDIT_CLASS(); CUtlMemory::m_pMemory = (T*)MemAlloc_ReallocAligned(CUtlMemory::m_pMemory, CUtlMemory::m_nAllocationCount * sizeof(T), nAlignment); Assert(CUtlMemory::m_pMemory); } else { MEM_ALLOC_CREDIT_CLASS(); CUtlMemory::m_pMemory = (T*)MemAlloc_AllocAligned(CUtlMemory::m_nAllocationCount * sizeof(T), nAlignment); Assert(CUtlMemory::m_pMemory); } } //----------------------------------------------------------------------------- // Makes sure we've got at least this much memory //----------------------------------------------------------------------------- template< class T, int64_t nAlignment > inline void CUtlMemoryAligned::EnsureCapacity(int64_t num) { if (CUtlMemory::m_nAllocationCount >= num) return; if (this->IsExternallyAllocated()) { // Can't grow a buffer whose memory was externally allocated Assert(0); return; } UTLMEMORY_TRACK_FREE(); CUtlMemory::m_nAllocationCount = num; UTLMEMORY_TRACK_ALLOC(); if (CUtlMemory::m_pMemory) { MEM_ALLOC_CREDIT_CLASS(); CUtlMemory::m_pMemory = (T*)MemAlloc_ReallocAligned(CUtlMemory::m_pMemory, CUtlMemory::m_nAllocationCount * sizeof(T), nAlignment); } else { MEM_ALLOC_CREDIT_CLASS(); CUtlMemory::m_pMemory = (T*)MemAlloc_AllocAligned(CUtlMemory::m_nAllocationCount * sizeof(T), nAlignment); } } //----------------------------------------------------------------------------- // Memory deallocation //----------------------------------------------------------------------------- template< class T, int64_t nAlignment > void CUtlMemoryAligned::Purge() { if (!this->IsExternallyAllocated()) { if (CUtlMemory::m_pMemory) { UTLMEMORY_TRACK_FREE(); MemAlloc_FreeAligned(CUtlMemory::m_pMemory); CUtlMemory::m_pMemory = 0; } CUtlMemory::m_nAllocationCount = 0; } } //#include "tier0/memdbgoff.h" #endif // UTLMEMORY_H