source-engine/gcsdk/gcreportprinter.cpp

490 lines
12 KiB
C++
Raw Normal View History

2020-04-23 00:56:21 +08:00
//========= 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