summaryrefslogtreecommitdiff
path: root/gcsdk/gcreportprinter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gcsdk/gcreportprinter.cpp')
-rw-r--r--gcsdk/gcreportprinter.cpp489
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