You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
489 lines
12 KiB
489 lines
12 KiB
//========= 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
|
|
|