diff --git a/public/tier1/bufferstring.h b/public/tier1/bufferstring.h index 135d10ec..ce9e29b2 100644 --- a/public/tier1/bufferstring.h +++ b/public/tier1/bufferstring.h @@ -12,42 +12,30 @@ class CFormatStringElement; class IFormatOutputStream; -template -class CBufferStringGrowable; - /* - Main idea of CBufferString is to provide the base class for the CBufferStringGrowable wich implements stack allocation + Main idea of CBufferString is to provide stack allocated string with the ability to convert to the heap allocation if allowed. - Example usage of CBufferStringGrowable class: + By default CBufferString provides 8 bytes of stack allocation and could be increased by + using CBufferStringN where custom stack SIZE could be used. + + Example usage of CBufferStringN class: * Basic buffer allocation: ``` - CBufferStringGrowable<256> buff; + CBufferStringN<256> buff; buff.Insert(0, "Hello World!"); printf("Result: %s\n", buff.Get()); ``` - additionaly the heap allocation of the buffer could be disabled, by providing ``AllowHeapAllocation`` template argument, - by disabling heap allocation, if the buffer capacity is not enough to perform the operation, the app would exit with an Assert; + additionaly the heap allocation of the buffer could be disabled. If the heap allocation is disabled and + if the buffer capacity is not enough to perform the growing operation, the app would exit with an Assert; - * Additional usage: - CBufferString::IsStackAllocated() - could be used to check if the buffer is stack allocated; - CBufferString::IsHeapAllocated() - could be used to check if the buffer is heap allocated; - CBufferString::Get() - would return a pointer to the data, or an empty string if it's not allocated. - - * Additionaly current length of the buffer could be read via CBufferString::GetTotalNumber() - and currently allocated amount of bytes could be read via CBufferString::GetAllocatedNumber() - - * Most, if not all the functions would ensure the buffer capacity and enlarge it when needed, - in case of stack allocated buffers, it would switch to heap allocation instead. + * Most, if not all the functions would ensure the buffer capacity and enlarge it when needed. + In case of stack allocated buffers, if the requested size exceeds stack size, it would switch to heap allocation instead. */ class CBufferString { -protected: - // You shouldn't be initializing this class, use CBufferStringGrowable instead. - CBufferString() {} - public: enum EAllocationOption_t { @@ -55,8 +43,7 @@ public: UNK2 = 0, UNK3 = (1 << 1), UNK4 = (1 << 8), - UNK5 = (1 << 9), - ALLOW_HEAP_ALLOCATION = (1 << 31) + UNK5 = (1 << 9) }; enum EAllocationFlags_t @@ -64,10 +51,91 @@ public: LENGTH_MASK = (1 << 30) - 1, FLAGS_MASK = ~LENGTH_MASK, - STACK_ALLOCATION_MARKER = (1 << 30), - HEAP_ALLOCATION_MARKER = (1 << 31) + // Flags in m_nLength + // Means it tried to grow larger than static size and heap allocation was disabled + OVERFLOWED_MARKER = (1 << 30), + // Means it owns the heap buffer and it needs to be cleaned up + FREE_HEAP_MARKER = (1 << 31), + + // Flags in m_nAllocatedSize + // Means it uses stack allocated buffer + STACK_ALLOCATED_MARKER = (1 << 30), + // Allows the buffer to grow beyond the static size on the heap + ALLOW_HEAP_ALLOCATION = (1 << 31) }; +public: + CBufferString( bool bAllowHeapAllocation = true ) : + m_nLength( 0 ), + m_nAllocatedSize( (bAllowHeapAllocation * ALLOW_HEAP_ALLOCATION) | STACK_ALLOCATED_MARKER | sizeof( m_szString ) ), + m_pString( nullptr ) + { } + + CBufferString( const char *pString, bool bAllowHeapAllocation = true ) : + CBufferString( bAllowHeapAllocation ) + { + Insert( 0, pString ); + } + +protected: + CBufferString( size_t nAllocatedSize, bool bAllowHeapAllocation = true ) : + m_nLength( 0 ), + m_nAllocatedSize( (bAllowHeapAllocation * ALLOW_HEAP_ALLOCATION) | STACK_ALLOCATED_MARKER | (nAllocatedSize + sizeof( m_szString )) ), + m_pString( nullptr ) + { + Assert( nAllocatedSize > 8 ); + } + +public: + CBufferString( const CBufferString &other ) : CBufferString() { *this = other; } + CBufferString &operator=( const CBufferString &src ) + { + Clear(); + Insert( 0, src.Get() ); + return *this; + } + + ~CBufferString() { Purge(); } + + void SetHeapAllocationState( bool state ) + { + if(state) + m_nAllocatedSize |= ALLOW_HEAP_ALLOCATION; + else + m_nAllocatedSize &= ~ALLOW_HEAP_ALLOCATION; + } + + int AllocatedNum() const { return m_nAllocatedSize & LENGTH_MASK; } + int Length() const { return m_nLength & LENGTH_MASK; } + + bool CanHeapAllocate() const { return (m_nAllocatedSize & ALLOW_HEAP_ALLOCATION) != 0; } + bool IsStackAllocated() const { return (m_nAllocatedSize & STACK_ALLOCATED_MARKER) != 0; } + bool ShouldFreeMemory() const { return (m_nLength & FREE_HEAP_MARKER) != 0; } + bool IsOverflowed() const { return (m_nLength & OVERFLOWED_MARKER) != 0; } + + bool IsInputStringUnsafe( const char *pData ) const + { + return ((void *)pData >= this && (void *)pData < &this[1]) || + (!IsAllocationEmpty() && pData >= Base() && pData < (Base() + AllocatedNum())); + } + + bool IsAllocationEmpty() const { return AllocatedNum() == 0; } + +protected: + char *Base() { return IsStackAllocated() ? m_szString : (!IsAllocationEmpty() ? m_pString : nullptr); } + const char *Base() const { return const_cast( this )->Base(); } + +public: + const char *Get() const { auto base = Base(); return base ? base : StringFuncs::EmptyString(); } + + void Clear() + { + if(!IsAllocationEmpty()) + Base()[0] = '\0'; + + m_nLength &= ~LENGTH_MASK; + } + public: DLL_CLASS_IMPORT const char *AppendConcat(int, const char * const *, const int *, bool bIgnoreAlignment = false); DLL_CLASS_IMPORT const char *AppendConcat(const char *, const char *, ...) FMTFUNCTION(3, 4); @@ -94,10 +162,10 @@ public: // Ensures the nCapacity condition is met and grows the local buffer if needed. // Returns pResultingBuffer pointer to the newly allocated data, as well as resulting capacity that was allocated in bytes. - DLL_CLASS_IMPORT int EnsureCapacity(int nCapacity, char **pResultingBuffer, bool bIgnoreAlignment = false, bool bForceGrow = true); - DLL_CLASS_IMPORT int EnsureAddedCapacity(int nCapacity, char **pResultingBuffer, bool bIgnoreAlignment = false, bool bForceGrow = true); + DLL_CLASS_IMPORT int EnsureCapacity(int nCapacity, char **pResultingBuffer, bool bIgnoreAlignment = false, bool bForceGrow = false); + DLL_CLASS_IMPORT int EnsureAddedCapacity(int nCapacity, char **pResultingBuffer, bool bIgnoreAlignment = false, bool bForceGrow = false); - DLL_CLASS_IMPORT char *EnsureLength(int nCapacity, bool bIgnoreAlignment = false, int *pNewCapacity = NULL); + DLL_CLASS_IMPORT char *EnsureLength(int nCapacity, bool bIgnoreAlignment = false, int *pNewCapacity = nullptr); DLL_CLASS_IMPORT char *EnsureOwnedAllocation(CBufferString::EAllocationOption_t eAlloc); DLL_CLASS_IMPORT const char *EnsureTrailingSlash(char cSeparator, bool bDontAppendIfEmpty = true); @@ -134,8 +202,8 @@ public: // Returns the resulting char buffer (Same as to what CBufferString->Get() returns). DLL_CLASS_IMPORT const char *Insert(int nIndex, const char *pBuf, int nCount = -1, bool bIgnoreAlignment = false); - DLL_CLASS_IMPORT char *GetInsertPtr(int nIndex, int nChars, bool bIgnoreAlignment = false, int *pNewCapacity = NULL); - DLL_CLASS_IMPORT char *GetReplacePtr(int nIndex, int nOldChars, int nNewChars, bool bIgnoreAlignment = false, int *pNewCapacity = NULL); + DLL_CLASS_IMPORT char *GetInsertPtr(int nIndex, int nChars, bool bIgnoreAlignment = false, int *pNewCapacity = nullptr); + DLL_CLASS_IMPORT char *GetReplacePtr(int nIndex, int nOldChars, int nNewChars, bool bIgnoreAlignment = false, int *pNewCapacity = nullptr); DLL_CLASS_IMPORT int GrowByChunks(int, int); @@ -156,7 +224,7 @@ public: // Copies data from pOther and then purges it DLL_CLASS_IMPORT void MoveFrom(CBufferString &pOther); - DLL_CLASS_IMPORT void Purge(int nLength); + DLL_CLASS_IMPORT void Purge(int nAllocatedBytesToPreserve = 0); DLL_CLASS_IMPORT char *Relinquish(CBufferString::EAllocationOption_t eAlloc); @@ -184,7 +252,7 @@ public: // Strips any current extension from path and ensures that extension is the new extension DLL_CLASS_IMPORT const char *SetExtension(const char *extension); - DLL_CLASS_IMPORT char *SetLength(int nLen, bool bIgnoreAlignment = false, int *pNewCapacity = NULL); + DLL_CLASS_IMPORT char *SetLength(int nLen, bool bIgnoreAlignment = false, int *pNewCapacity = nullptr); DLL_CLASS_IMPORT void SetPtr(char *pBuf, int nBufferChars, int, bool, bool); // Frees the buffer (if it was heap allocated) and writes "~DSTRCT" to the local buffer. @@ -213,119 +281,40 @@ public: DLL_CLASS_IMPORT int UnicodeCaseConvert(int, EStringConvertErrorPolicy eErrorPolicy); - // Casts to CBufferStringGrowable. Very dirty solution until someone figures out the sane one. - template> - T *ToGrowable() - { - return (T *)this; - } -}; - -template -class CBufferStringGrowable : public CBufferString -{ - friend class CBufferString; - -public: - CBufferStringGrowable() : m_nTotalCount(0), m_nAllocated(STACK_ALLOCATION_MARKER | (MAX_SIZE & LENGTH_MASK)) - { - memset(m_Memory.m_szString, 0, sizeof(m_Memory.m_szString)); - if (AllowHeapAllocation) - { - m_nAllocated |= ALLOW_HEAP_ALLOCATION; - } - } - - CBufferStringGrowable(const CBufferStringGrowable& other) : m_nTotalCount(0), m_nAllocated(STACK_ALLOCATION_MARKER | (MAX_SIZE & LENGTH_MASK)) - { - memset(m_Memory.m_szString, 0, sizeof(m_Memory.m_szString)); - if (AllowHeapAllocation) - { - m_nAllocated |= ALLOW_HEAP_ALLOCATION; - } - Insert(0, other.Get()); - } - - ~CBufferStringGrowable() - { - if (IsHeapAllocated() && m_Memory.m_pString) - { -#if PLATFORM_WINDOWS - g_pMemAlloc->Free((void*)m_Memory.m_pString); -#else - delete[] m_Memory.m_pString; -#endif - } - } - - inline CBufferStringGrowable& operator=(const CBufferStringGrowable& src) - { - Clear(); - Insert(0, src.Get()); - return *this; - } - - inline int GetAllocatedNumber() const - { - return m_nAllocated & LENGTH_MASK; - } - - inline int GetTotalNumber() const - { - return m_nTotalCount & LENGTH_MASK; - } - - inline bool IsStackAllocated() const - { - return (m_nAllocated & STACK_ALLOCATION_MARKER) != 0; - } - - inline bool IsHeapAllocated() const - { - return (m_nTotalCount & HEAP_ALLOCATION_MARKER) != 0; - } - - inline bool IsInputStringUnsafe(const char *pData) const - { - return ((void *)pData >= this && (void *)pData < &this[1]) || - (GetAllocatedNumber() != 0 && pData >= Get() && pData < (Get() + GetAllocatedNumber())); - } - - inline const char *Get() const - { - if (IsStackAllocated()) - { - return m_Memory.m_szString; - } - else if (GetAllocatedNumber() != 0) - { - return m_Memory.m_pString; - } - - return StringFuncs::EmptyString(); - } - - inline void Clear() - { - if (GetAllocatedNumber() != 0) - { - if (IsStackAllocated()) - m_Memory.m_szString[0] = '\0'; - else - m_Memory.m_pString[0] = '\0'; - } - m_nTotalCount &= ~LENGTH_MASK; - } - private: - int m_nTotalCount; - int m_nAllocated; + int m_nLength; + int m_nAllocatedSize; union { char *m_pString; - char m_szString[MAX_SIZE]; - } m_Memory; + char m_szString[8]; + }; }; +template +class CBufferStringN : public CBufferString +{ +public: + static const size_t DATA_SIZE = ALIGN_VALUE( SIZE - sizeof( char[8] ), 8 ); + + CBufferStringN( bool bAllowHeapAllocation = true ) : CBufferString( DATA_SIZE, bAllowHeapAllocation ) {} + CBufferStringN( const char *pString, bool bAllowHeapAllocation = true ) : CBufferStringN( bAllowHeapAllocation ) + { + Insert( 0, pString ); + } + + ~CBufferStringN() { PurgeN(); } + + // Should be preferred over CBufferString::Purge as it preserves stack space correctly + void PurgeN() { Purge( DATA_SIZE ); } + +private: + char m_FixedData[DATA_SIZE]; +}; + +// AMNOTE: CBufferStringN name is preferred to be used, altho CBufferStringGrowable is left as a small bcompat +template +using CBufferStringGrowable = CBufferStringN; + #endif /* BUFFERSTRING_H */ \ No newline at end of file diff --git a/public/tier1/keyvalues3.h b/public/tier1/keyvalues3.h index ffdb5a2c..f1af81f1 100644 --- a/public/tier1/keyvalues3.h +++ b/public/tier1/keyvalues3.h @@ -341,7 +341,7 @@ struct KV3MetaData_t m_Comments.Purge(); } - typedef CUtlMap, int, CDefLess> CommentsMap_t; + typedef CUtlMap> CommentsMap_t; int m_nLine; int m_nColumn; diff --git a/public/variant.h b/public/variant.h index c3f07ab3..f44dbd64 100644 --- a/public/variant.h +++ b/public/variant.h @@ -935,7 +935,7 @@ public: const char *ToString() { - static CBufferStringGrowable<200> szBuf; + static CBufferStringN<200> szBuf; AssignTo(szBuf); return szBuf.Get(); } diff --git a/tier1/keyvalues3.cpp b/tier1/keyvalues3.cpp index 029a5d9c..eceb985a 100644 --- a/tier1/keyvalues3.cpp +++ b/tier1/keyvalues3.cpp @@ -993,7 +993,7 @@ const char* KeyValues3::ToString( CBufferString& buff, uint flags ) const if ( ( flags & KV3_TO_STRING_DONT_CLEAR_BUFF ) != 0 ) flags &= ~KV3_TO_STRING_DONT_APPEND_STRINGS; else - buff.ToGrowable()->Clear(); + buff.Clear(); KV3Type_t type = GetType(); @@ -1001,7 +1001,7 @@ const char* KeyValues3::ToString( CBufferString& buff, uint flags ) const { case KV3_TYPE_NULL: { - return buff.ToGrowable()->Get(); + return buff.Get(); } case KV3_TYPE_BOOL: { @@ -1010,34 +1010,34 @@ const char* KeyValues3::ToString( CBufferString& buff, uint flags ) const if ( ( flags & KV3_TO_STRING_DONT_APPEND_STRINGS ) != 0 ) return str; - buff.Insert( buff.ToGrowable()->GetTotalNumber(), str ); - return buff.ToGrowable()->Get(); + buff.Insert( buff.Length(), str ); + return buff.Get(); } case KV3_TYPE_INT: { buff.AppendFormat( "%lld", m_Data.m_Int ); - return buff.ToGrowable()->Get(); + return buff.Get(); } case KV3_TYPE_UINT: { if ( GetSubType() == KV3_SUBTYPE_POINTER ) { if ( ( flags & KV3_TO_STRING_APPEND_ONLY_NUMERICS ) == 0 ) - buff.Insert( buff.ToGrowable()->GetTotalNumber(), "" ); + buff.Insert( buff.Length(), "" ); if ( ( flags & KV3_TO_STRING_RETURN_NON_NUMERICS ) == 0 ) return nullptr; - return buff.ToGrowable()->Get(); + return buff.Get(); } buff.AppendFormat( "%llu", m_Data.m_UInt ); - return buff.ToGrowable()->Get(); + return buff.Get(); } case KV3_TYPE_DOUBLE: { buff.AppendFormat( "%g", m_Data.m_Double ); - return buff.ToGrowable()->Get(); + return buff.Get(); } case KV3_TYPE_STRING: { @@ -1046,8 +1046,8 @@ const char* KeyValues3::ToString( CBufferString& buff, uint flags ) const if ( ( flags & KV3_TO_STRING_DONT_APPEND_STRINGS ) != 0 ) return str; - buff.Insert( buff.ToGrowable()->GetTotalNumber(), str ); - return buff.ToGrowable()->Get(); + buff.Insert( buff.Length(), str ); + return buff.Get(); } case KV3_TYPE_BINARY_BLOB: { @@ -1057,7 +1057,7 @@ const char* KeyValues3::ToString( CBufferString& buff, uint flags ) const if ( ( flags & KV3_TO_STRING_RETURN_NON_NUMERICS ) == 0 ) return nullptr; - return buff.ToGrowable()->Get(); + return buff.Get(); } case KV3_TYPE_ARRAY: { @@ -1070,7 +1070,7 @@ const char* KeyValues3::ToString( CBufferString& buff, uint flags ) const case KV3_TYPEEX_ARRAY: { bool unprintable = false; - CBufferStringGrowable<128> temp; + CBufferStringN<128> temp; CKeyValues3Array::Element_t* arr = m_Data.m_pArray->Base(); for ( int i = 0; i < elements; ++i ) @@ -1097,68 +1097,68 @@ const char* KeyValues3::ToString( CBufferString& buff, uint flags ) const if ( unprintable ) break; - if ( i != elements - 1 ) temp.Insert( temp.ToGrowable()->GetTotalNumber(), " " ); + if ( i != elements - 1 ) temp.Insert( temp.Length(), " " ); } if ( unprintable ) break; - buff.Insert( buff.ToGrowable()->GetTotalNumber(), temp.Get() ); - return buff.ToGrowable()->Get(); + buff.Insert( buff.Length(), temp.Get() ); + return buff.Get(); } case KV3_TYPEEX_ARRAY_FLOAT32: { for ( int i = 0; i < elements; ++i ) { buff.AppendFormat( "%g", m_Data.m_Array.m_f32[i] ); - if ( i != elements - 1 ) buff.Insert( buff.ToGrowable()->GetTotalNumber(), " " ); + if ( i != elements - 1 ) buff.Insert( buff.Length(), " " ); } - return buff.ToGrowable()->Get(); + return buff.Get(); } case KV3_TYPEEX_ARRAY_FLOAT64: { for ( int i = 0; i < elements; ++i ) { buff.AppendFormat( "%g", m_Data.m_Array.m_f64[i] ); - if ( i != elements - 1 ) buff.Insert( buff.ToGrowable()->GetTotalNumber(), " " ); + if ( i != elements - 1 ) buff.Insert( buff.Length(), " " ); } - return buff.ToGrowable()->Get(); + return buff.Get(); } case KV3_TYPEEX_ARRAY_INT16: { for ( int i = 0; i < elements; ++i ) { buff.AppendFormat( "%d", m_Data.m_Array.m_i16Short[i] ); - if ( i != elements - 1 ) buff.Insert( buff.ToGrowable()->GetTotalNumber(), " " ); + if ( i != elements - 1 ) buff.Insert( buff.Length(), " " ); } - return buff.ToGrowable()->Get(); + return buff.Get(); } case KV3_TYPEEX_ARRAY_INT32: { for ( int i = 0; i < elements; ++i ) { buff.AppendFormat( "%d", m_Data.m_Array.m_i32[i] ); - if ( i != elements - 1 ) buff.Insert( buff.ToGrowable()->GetTotalNumber(), " " ); + if ( i != elements - 1 ) buff.Insert( buff.Length(), " " ); } - return buff.ToGrowable()->Get(); + return buff.Get(); } case KV3_TYPEEX_ARRAY_UINT8_SHORT: { for ( int i = 0; i < elements; ++i ) { buff.AppendFormat( "%u", m_Data.m_Array.m_u8Short[i] ); - if ( i != elements - 1 ) buff.Insert( buff.ToGrowable()->GetTotalNumber(), " " ); + if ( i != elements - 1 ) buff.Insert( buff.Length(), " " ); } - return buff.ToGrowable()->Get(); + return buff.Get(); } case KV3_TYPEEX_ARRAY_INT16_SHORT: { for ( int i = 0; i < elements; ++i ) { buff.AppendFormat( "%d", m_Data.m_Array.m_i16Short[i] ); - if ( i != elements - 1 ) buff.Insert( buff.ToGrowable()->GetTotalNumber(), " " ); + if ( i != elements - 1 ) buff.Insert( buff.Length(), " " ); } - return buff.ToGrowable()->Get(); + return buff.Get(); } default: break; @@ -1171,7 +1171,7 @@ const char* KeyValues3::ToString( CBufferString& buff, uint flags ) const if ( ( flags & KV3_TO_STRING_RETURN_NON_NUMERICS ) == 0 ) return nullptr; - return buff.ToGrowable()->Get(); + return buff.Get(); } case KV3_TYPE_TABLE: { @@ -1181,7 +1181,7 @@ const char* KeyValues3::ToString( CBufferString& buff, uint flags ) const if ( ( flags & KV3_TO_STRING_RETURN_NON_NUMERICS ) == 0 ) return nullptr; - return buff.ToGrowable()->Get(); + return buff.Get(); } default: { @@ -1191,7 +1191,7 @@ const char* KeyValues3::ToString( CBufferString& buff, uint flags ) const if ( ( flags & KV3_TO_STRING_RETURN_NON_NUMERICS ) == 0 ) return nullptr; - return buff.ToGrowable()->Get(); + return buff.Get(); } } }