diff options
Diffstat (limited to 'gcsdk/gcreportprinter.cpp')
| -rw-r--r-- | gcsdk/gcreportprinter.cpp | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/gcsdk/gcreportprinter.cpp b/gcsdk/gcreportprinter.cpp new file mode 100644 index 0000000..9671437 --- /dev/null +++ b/gcsdk/gcreportprinter.cpp @@ -0,0 +1,489 @@ +//========= 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( "<link cmd=\"%s\">", v.m_sLink.String() ); + vMsg.Append( vValue ); + vMsg.Append( "</link>" ); + } + 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 |