//========= Copyright (c), Valve LLC, All rights reserved. ============ // // Purpose: // // $NoKeywords: $ //============================================================================= #include "stdafx.h" #include "gcreportprinter.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" namespace GCSDK { CGCReportPrinter::Variant_t::Variant_t() : m_nInt( 0 ), m_fFloat( 0 ) {} CGCReportPrinter::CGCReportPrinter() { } CGCReportPrinter::~CGCReportPrinter() { Clear(); } bool CGCReportPrinter::AddStringColumn( const char* pszColumn ) { //don't allow adding columns if data is already present if( m_Rows.Count() > 0 ) return false; int nIndex = m_Columns.AddToTail(); Column_t& col = m_Columns[ nIndex ]; col.m_sName = pszColumn; col.m_eType = eCol_String; col.m_eSummary = eSummary_None; col.m_nNumDecimals = 0; col.m_eIntDisplay = eIntDisplay_Normal; return true; } bool CGCReportPrinter::AddIntColumn( const char* pszColumn, ESummaryType eSummary, EIntDisplayType eIntDisplay /* = eIntDisplay_Normal */ ) { //don't allow adding columns if data is already present if( m_Rows.Count() > 0 ) return false; int nIndex = m_Columns.AddToTail(); Column_t& col = m_Columns[ nIndex ]; col.m_sName = pszColumn; col.m_eType = eCol_Int; col.m_eSummary = eSummary; col.m_nNumDecimals = 0; col.m_eIntDisplay = eIntDisplay; return true; } bool CGCReportPrinter::AddFloatColumn( const char* pszColumn, ESummaryType eSummary, uint32 unNumDecimal /* = 2 */ ) { //don't allow adding columns if data is already present if( m_Rows.Count() > 0 ) return false; int nIndex = m_Columns.AddToTail(); Column_t& col = m_Columns[ nIndex ]; col.m_sName = pszColumn; col.m_eType = eCol_Float; col.m_eSummary = eSummary; col.m_nNumDecimals = unNumDecimal; col.m_eIntDisplay = eIntDisplay_Normal; return true; } bool CGCReportPrinter::AddSteamIDColumn( const char* pszColumn ) { //don't allow adding columns if data is already present if( m_Rows.Count() > 0 ) return false; int nIndex = m_Columns.AddToTail(); Column_t& col = m_Columns[ nIndex ]; col.m_sName = pszColumn; col.m_eType = eCol_SteamID; col.m_eSummary = eSummary_None; col.m_nNumDecimals = 0; col.m_eIntDisplay = eIntDisplay_Normal; return true; } void CGCReportPrinter::ClearData() { m_Rows.PurgeAndDeleteElements(); } //called to reset the entire report void CGCReportPrinter::Clear() { ClearData(); m_Columns.Purge(); } //called to commit the values that have been added as a new row bool CGCReportPrinter::CommitRow() { //only let full rows be committed if( m_RowBuilder.Count() != m_Columns.Count() ) return false; if( m_Columns.IsEmpty() ) return false; m_Rows.AddToTail( new TRow( m_RowBuilder ) ); m_RowBuilder.RemoveAll(); return true; } //called to add the various data to the report, the order of this must match the columns that were added originally bool CGCReportPrinter::StrValue( const char* pszStr, const char* pszLink ) { //make sure we have a following column and that the type matches if( ( m_RowBuilder.Count() >= m_Columns.Count() ) || ( m_Columns[ m_RowBuilder.Count() ].m_eType != eCol_String ) ) return false; Variant_t& val = m_RowBuilder[ m_RowBuilder.AddToTail() ]; val.m_sStr = pszStr; val.m_sLink = pszLink; return true; } bool CGCReportPrinter::IntValue( int64 nValue, const char* pszLink ) { //make sure we have a following column and that the type matches if( ( m_RowBuilder.Count() >= m_Columns.Count() ) || ( m_Columns[ m_RowBuilder.Count() ].m_eType != eCol_Int ) ) return false; Variant_t& val = m_RowBuilder[ m_RowBuilder.AddToTail() ]; val.m_nInt = nValue; val.m_sLink = pszLink; return true; } bool CGCReportPrinter::FloatValue( double fValue, const char* pszLink ) { //make sure we have a following column and that the type matches if( ( m_RowBuilder.Count() >= m_Columns.Count() ) || ( m_Columns[ m_RowBuilder.Count() ].m_eType != eCol_Float ) ) return false; Variant_t& val = m_RowBuilder[ m_RowBuilder.AddToTail() ]; val.m_fFloat = fValue; val.m_sLink = pszLink; return true; } bool CGCReportPrinter::SteamIDValue( CSteamID id, const char* pszLink ) { //make sure we have a following column and that the type matches if( ( m_RowBuilder.Count() >= m_Columns.Count() ) || ( m_Columns[ m_RowBuilder.Count() ].m_eType != eCol_SteamID ) ) return false; Variant_t& val = m_RowBuilder[ m_RowBuilder.AddToTail() ]; val.m_SteamID = id; val.m_sLink = pszLink; return true; } //class that implements sorting our rows based upon a provided column with ascending or descending ordering class CReportRowSorter { public: CReportRowSorter( bool bDescending, uint32 nCol, CGCReportPrinter::EColumnType eType ) : m_bDescending( bDescending ), m_nCol( nCol ), m_eType( eType ) { } bool operator()( const CGCReportPrinter::TRow* pR1, const CGCReportPrinter::TRow* pR2 ) { //to implement ascending vs descending, we can just flip our inputs if( m_bDescending ) std::swap( pR1, pR2 ); const CGCReportPrinter::Variant_t& v1 = ( *pR1 )[ m_nCol ]; const CGCReportPrinter::Variant_t& v2 = ( *pR2 )[ m_nCol ]; switch( m_eType ) { case CGCReportPrinter::eCol_String: return stricmp( v1.m_sStr, v2.m_sStr ) < 0; case CGCReportPrinter::eCol_Int: return v1.m_nInt < v2.m_nInt; case CGCReportPrinter::eCol_Float: return v1.m_fFloat < v2.m_fFloat; case CGCReportPrinter::eCol_SteamID: return v1.m_SteamID < v2.m_SteamID; } return false; } bool m_bDescending; uint32 m_nCol; CGCReportPrinter::EColumnType m_eType; }; //sorts the report based upon the specified column name void CGCReportPrinter::SortReport( const char* pszColumn, bool bDescending ) { //find our column FOR_EACH_VEC( m_Columns, nCol ) { if( stricmp( m_Columns[ nCol ].m_sName, pszColumn ) == 0 ) { CReportRowSorter sorter( bDescending, nCol, m_Columns[ nCol ].m_eType ); std::sort( m_Rows.begin(), m_Rows.end(), sorter ); break; } } } void CGCReportPrinter::SortReport( uint32 nColIndex, bool bDescending ) { if( nColIndex < ( uint32 )m_Columns.Count() ) { CReportRowSorter sorter( bDescending, nColIndex, m_Columns[ nColIndex ].m_eType ); std::sort( m_Rows.begin(), m_Rows.end(), sorter ); } } //utility to count the number of digits on the provided integer static uint CountDigits( int64 nInt ) { //the zero special case, since it would otherwise fall out of the loop too early if( nInt == 0 ) return 1; int nDigits = 0; if( nInt < 0 ) { //for the minus sign nDigits++; } while( nInt != 0 ) { nInt /= 10; nDigits++; } return nDigits; } static uint CountIntWidth( int64 nValue, CGCReportPrinter::EIntDisplayType eIntDisplay ) { uint unDigits; switch ( eIntDisplay ) { case CGCReportPrinter::eIntDisplay_Memory_MB: // "1234.56 MB" unDigits = CountDigits( nValue / k_nMegabyte ) + 1 + 2 + 3; break; case CGCReportPrinter::eIntDisplay_Normal: default: // 12345678 unDigits = CountDigits( nValue ); break; } return unDigits; } static const char * GetIntValueDisplay( int64 nValue, CGCReportPrinter::EIntDisplayType eIntDisplay ) { static CFmtStr1024 s_fmtResult; switch ( eIntDisplay ) { case CGCReportPrinter::eIntDisplay_Memory_MB: // "1234.56 MB" s_fmtResult.sprintf( "%lld.%02u MB", ( nValue / k_nMegabyte ), (uint32)( 100.0f * ( ( abs( nValue ) % k_nMegabyte ) / (float)k_nMegabyte ) ) ); break; case CGCReportPrinter::eIntDisplay_Normal: default: // 12345678 s_fmtResult.sprintf( "%lld", nValue ); break; } return s_fmtResult; } //called to print out the provided report void CGCReportPrinter::PrintReport( CGCEmitGroup& eg, uint32 nTop ) { //we need to determine our totals and maximum row widths for our columns first CUtlVector< uint32 > vColWidths; vColWidths.EnsureCapacity( m_Columns.Count() ); CUtlVector< Variant_t > vSummary; vSummary.EnsureCapacity( m_Columns.Count() ); CFmtStr1024 vMsg; CFmtStr1024 vSeparator; FOR_EACH_VEC( m_Columns, nCol ) { const Column_t& col = m_Columns[ nCol ]; uint32 nColWidth = V_strlen( col.m_sName ); Variant_t summary; //run through all the values to find the row widths and the summary values FOR_EACH_VEC( m_Rows, nRow ) { //bail after the first N elements if( ( nTop > 0 ) && ( ( uint32 )nRow >= nTop ) ) break; const Variant_t& v = ( *m_Rows[ nRow ] )[ nCol ]; switch( col.m_eType ) { case eCol_String: nColWidth = MAX( nColWidth, strlen( v.m_sStr ) ); break; case eCol_SteamID: nColWidth = MAX( nColWidth, strlen( v.m_SteamID.Render() ) ); break; case eCol_Float: nColWidth = MAX( nColWidth, CountDigits( ( int64 )v.m_fFloat ) + 1 + col.m_nNumDecimals ); switch( col.m_eSummary ) { case eSummary_Max: summary.m_fFloat = MAX( summary.m_fFloat, v.m_fFloat ); break; case eSummary_Total: summary.m_fFloat += v.m_fFloat; break; } break; case eCol_Int: nColWidth = MAX( nColWidth, CountIntWidth( v.m_nInt, col.m_eIntDisplay ) ); switch( col.m_eSummary ) { case eSummary_Max: summary.m_nInt = MAX( summary.m_nInt, v.m_nInt ); break; case eSummary_Total: summary.m_nInt += v.m_nInt; break; } break; } } //make sure the summary value contributes to the column width switch( col.m_eType ) { case eCol_Float: nColWidth = MAX( nColWidth, CountDigits( ( int64 )summary.m_fFloat ) + 1 + col.m_nNumDecimals ); break; case eCol_Int: nColWidth = MAX( nColWidth, CountIntWidth( summary.m_nInt, col.m_eIntDisplay ) ); break; } //initialize our column sizes vColWidths.AddToTail( nColWidth ); vSummary.AddToTail( summary ); vMsg.AppendFormat( "%*s", nColWidth, col.m_sName.String() ); vMsg.Append( ' ' ); for( uint32 nChar = 0; nChar < nColWidth; nChar++ ) vSeparator.Append( '-' ); vSeparator.Append( ' ' ); } //now print our header vMsg.Append( '\n' ); vSeparator.Append( '\n' ); EG_MSG( eg, "%s", vMsg.String() ); EG_MSG( eg, "%s", vSeparator.String() ); //buffer for compositing our value CFmtStr1024 vValue; //now print each of our columns FOR_EACH_VEC( m_Rows, nRow ) { //bail after the first N elements if( ( nTop > 0 ) && ( ( uint32 )nRow >= nTop ) ) break; vMsg.Clear(); FOR_EACH_VEC( m_Columns, nCol ) { const Column_t& col = m_Columns[ nCol ]; const Variant_t& v = ( *m_Rows[ nRow ] )[ nCol ]; const uint32 nColWidth = vColWidths[ nCol ]; vValue.Clear(); switch( col.m_eType ) { case eCol_String: vValue.Append( v.m_sStr.String() ); break; case eCol_SteamID: vValue.Append( v.m_SteamID.Render() ); break; case eCol_Float: vValue.sprintf( "%.*f", col.m_nNumDecimals, v.m_fFloat ); break; case eCol_Int: vValue.Append( GetIntValueDisplay( v.m_nInt, col.m_eIntDisplay ) ); break; } //print out spaces before we do the link (so we don't have the whole table underlined) uint32 nValueLen = vValue.Length(); uint32 nNumSpaces = nColWidth - MIN( nColWidth, nValueLen ); for( uint32 nCurrSpace = 0; nCurrSpace < nNumSpaces; nCurrSpace++ ) vMsg.Append( ' ' ); //print out the link if one is provided if( !v.m_sLink.IsEmpty() ) { vMsg.AppendFormat( "", v.m_sLink.String() ); vMsg.Append( vValue ); vMsg.Append( "" ); } else { //allow for steam ID special linking if no link is specified if( col.m_eType == eCol_SteamID ) { // !FIXME! DOTAMERGE //vMsg.Append( v.m_SteamID.RenderLink() ); vMsg.Append( v.m_SteamID.Render() ); } else vMsg.Append( vValue ); } vMsg.Append( ' ' ); } vMsg.Append( '\n' ); EG_MSG( eg, "%s", vMsg.String() ); } //and finally our footer EG_MSG( eg, "%s", vSeparator.String() ); //and our summary { vMsg.Clear(); FOR_EACH_VEC( m_Columns, nCol ) { const Column_t& col = m_Columns[ nCol ]; const Variant_t& v = vSummary[ nCol ]; const uint32 nColWidth = vColWidths[ nCol ]; if( ( col.m_eType == eCol_String ) || ( col.m_eSummary == eSummary_None ) ) { vMsg.AppendFormat( "%*s ", nColWidth, "" ); } else { switch( col.m_eType ) { case eCol_Float: vMsg.AppendFormat( "%*.*f ", nColWidth, col.m_nNumDecimals, v.m_fFloat ); break; case eCol_Int: vMsg.AppendFormat( "%*s ", nColWidth, GetIntValueDisplay( v.m_nInt, col.m_eIntDisplay ) ); break; } } } vMsg.Append( '\n' ); EG_MSG( eg, "%s", vMsg.String() ); } } } // namespace GCSDK