Team Fortress 2 Source Code as on 22/4/2020
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

  1. //========= Copyright (c), Valve LLC, All rights reserved. ============
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================
  7. #include "stdafx.h"
  8. #include "gcreportprinter.h"
  9. // memdbgon must be the last include file in a .cpp file!!!
  10. #include "tier0/memdbgon.h"
  11. namespace GCSDK
  12. {
  13. CGCReportPrinter::Variant_t::Variant_t() :
  14. m_nInt( 0 ),
  15. m_fFloat( 0 )
  16. {}
  17. CGCReportPrinter::CGCReportPrinter()
  18. {
  19. }
  20. CGCReportPrinter::~CGCReportPrinter()
  21. {
  22. Clear();
  23. }
  24. bool CGCReportPrinter::AddStringColumn( const char* pszColumn )
  25. {
  26. //don't allow adding columns if data is already present
  27. if( m_Rows.Count() > 0 )
  28. return false;
  29. int nIndex = m_Columns.AddToTail();
  30. Column_t& col = m_Columns[ nIndex ];
  31. col.m_sName = pszColumn;
  32. col.m_eType = eCol_String;
  33. col.m_eSummary = eSummary_None;
  34. col.m_nNumDecimals = 0;
  35. col.m_eIntDisplay = eIntDisplay_Normal;
  36. return true;
  37. }
  38. bool CGCReportPrinter::AddIntColumn( const char* pszColumn, ESummaryType eSummary, EIntDisplayType eIntDisplay /* = eIntDisplay_Normal */ )
  39. {
  40. //don't allow adding columns if data is already present
  41. if( m_Rows.Count() > 0 )
  42. return false;
  43. int nIndex = m_Columns.AddToTail();
  44. Column_t& col = m_Columns[ nIndex ];
  45. col.m_sName = pszColumn;
  46. col.m_eType = eCol_Int;
  47. col.m_eSummary = eSummary;
  48. col.m_nNumDecimals = 0;
  49. col.m_eIntDisplay = eIntDisplay;
  50. return true;
  51. }
  52. bool CGCReportPrinter::AddFloatColumn( const char* pszColumn, ESummaryType eSummary, uint32 unNumDecimal /* = 2 */ )
  53. {
  54. //don't allow adding columns if data is already present
  55. if( m_Rows.Count() > 0 )
  56. return false;
  57. int nIndex = m_Columns.AddToTail();
  58. Column_t& col = m_Columns[ nIndex ];
  59. col.m_sName = pszColumn;
  60. col.m_eType = eCol_Float;
  61. col.m_eSummary = eSummary;
  62. col.m_nNumDecimals = unNumDecimal;
  63. col.m_eIntDisplay = eIntDisplay_Normal;
  64. return true;
  65. }
  66. bool CGCReportPrinter::AddSteamIDColumn( const char* pszColumn )
  67. {
  68. //don't allow adding columns if data is already present
  69. if( m_Rows.Count() > 0 )
  70. return false;
  71. int nIndex = m_Columns.AddToTail();
  72. Column_t& col = m_Columns[ nIndex ];
  73. col.m_sName = pszColumn;
  74. col.m_eType = eCol_SteamID;
  75. col.m_eSummary = eSummary_None;
  76. col.m_nNumDecimals = 0;
  77. col.m_eIntDisplay = eIntDisplay_Normal;
  78. return true;
  79. }
  80. void CGCReportPrinter::ClearData()
  81. {
  82. m_Rows.PurgeAndDeleteElements();
  83. }
  84. //called to reset the entire report
  85. void CGCReportPrinter::Clear()
  86. {
  87. ClearData();
  88. m_Columns.Purge();
  89. }
  90. //called to commit the values that have been added as a new row
  91. bool CGCReportPrinter::CommitRow()
  92. {
  93. //only let full rows be committed
  94. if( m_RowBuilder.Count() != m_Columns.Count() )
  95. return false;
  96. if( m_Columns.IsEmpty() )
  97. return false;
  98. m_Rows.AddToTail( new TRow( m_RowBuilder ) );
  99. m_RowBuilder.RemoveAll();
  100. return true;
  101. }
  102. //called to add the various data to the report, the order of this must match the columns that were added originally
  103. bool CGCReportPrinter::StrValue( const char* pszStr, const char* pszLink )
  104. {
  105. //make sure we have a following column and that the type matches
  106. if( ( m_RowBuilder.Count() >= m_Columns.Count() ) || ( m_Columns[ m_RowBuilder.Count() ].m_eType != eCol_String ) )
  107. return false;
  108. Variant_t& val = m_RowBuilder[ m_RowBuilder.AddToTail() ];
  109. val.m_sStr = pszStr;
  110. val.m_sLink = pszLink;
  111. return true;
  112. }
  113. bool CGCReportPrinter::IntValue( int64 nValue, const char* pszLink )
  114. {
  115. //make sure we have a following column and that the type matches
  116. if( ( m_RowBuilder.Count() >= m_Columns.Count() ) || ( m_Columns[ m_RowBuilder.Count() ].m_eType != eCol_Int ) )
  117. return false;
  118. Variant_t& val = m_RowBuilder[ m_RowBuilder.AddToTail() ];
  119. val.m_nInt = nValue;
  120. val.m_sLink = pszLink;
  121. return true;
  122. }
  123. bool CGCReportPrinter::FloatValue( double fValue, const char* pszLink )
  124. {
  125. //make sure we have a following column and that the type matches
  126. if( ( m_RowBuilder.Count() >= m_Columns.Count() ) || ( m_Columns[ m_RowBuilder.Count() ].m_eType != eCol_Float ) )
  127. return false;
  128. Variant_t& val = m_RowBuilder[ m_RowBuilder.AddToTail() ];
  129. val.m_fFloat = fValue;
  130. val.m_sLink = pszLink;
  131. return true;
  132. }
  133. bool CGCReportPrinter::SteamIDValue( CSteamID id, const char* pszLink )
  134. {
  135. //make sure we have a following column and that the type matches
  136. if( ( m_RowBuilder.Count() >= m_Columns.Count() ) || ( m_Columns[ m_RowBuilder.Count() ].m_eType != eCol_SteamID ) )
  137. return false;
  138. Variant_t& val = m_RowBuilder[ m_RowBuilder.AddToTail() ];
  139. val.m_SteamID = id;
  140. val.m_sLink = pszLink;
  141. return true;
  142. }
  143. //class that implements sorting our rows based upon a provided column with ascending or descending ordering
  144. class CReportRowSorter
  145. {
  146. public:
  147. CReportRowSorter( bool bDescending, uint32 nCol, CGCReportPrinter::EColumnType eType ) :
  148. m_bDescending( bDescending ), m_nCol( nCol ), m_eType( eType )
  149. {
  150. }
  151. bool operator()( const CGCReportPrinter::TRow* pR1, const CGCReportPrinter::TRow* pR2 )
  152. {
  153. //to implement ascending vs descending, we can just flip our inputs
  154. if( m_bDescending )
  155. std::swap( pR1, pR2 );
  156. const CGCReportPrinter::Variant_t& v1 = ( *pR1 )[ m_nCol ];
  157. const CGCReportPrinter::Variant_t& v2 = ( *pR2 )[ m_nCol ];
  158. switch( m_eType )
  159. {
  160. case CGCReportPrinter::eCol_String:
  161. return stricmp( v1.m_sStr, v2.m_sStr ) < 0;
  162. case CGCReportPrinter::eCol_Int:
  163. return v1.m_nInt < v2.m_nInt;
  164. case CGCReportPrinter::eCol_Float:
  165. return v1.m_fFloat < v2.m_fFloat;
  166. case CGCReportPrinter::eCol_SteamID:
  167. return v1.m_SteamID < v2.m_SteamID;
  168. }
  169. return false;
  170. }
  171. bool m_bDescending;
  172. uint32 m_nCol;
  173. CGCReportPrinter::EColumnType m_eType;
  174. };
  175. //sorts the report based upon the specified column name
  176. void CGCReportPrinter::SortReport( const char* pszColumn, bool bDescending )
  177. {
  178. //find our column
  179. FOR_EACH_VEC( m_Columns, nCol )
  180. {
  181. if( stricmp( m_Columns[ nCol ].m_sName, pszColumn ) == 0 )
  182. {
  183. CReportRowSorter sorter( bDescending, nCol, m_Columns[ nCol ].m_eType );
  184. std::sort( m_Rows.begin(), m_Rows.end(), sorter );
  185. break;
  186. }
  187. }
  188. }
  189. void CGCReportPrinter::SortReport( uint32 nColIndex, bool bDescending )
  190. {
  191. if( nColIndex < ( uint32 )m_Columns.Count() )
  192. {
  193. CReportRowSorter sorter( bDescending, nColIndex, m_Columns[ nColIndex ].m_eType );
  194. std::sort( m_Rows.begin(), m_Rows.end(), sorter );
  195. }
  196. }
  197. //utility to count the number of digits on the provided integer
  198. static uint CountDigits( int64 nInt )
  199. {
  200. //the zero special case, since it would otherwise fall out of the loop too early
  201. if( nInt == 0 )
  202. return 1;
  203. int nDigits = 0;
  204. if( nInt < 0 )
  205. {
  206. //for the minus sign
  207. nDigits++;
  208. }
  209. while( nInt != 0 )
  210. {
  211. nInt /= 10;
  212. nDigits++;
  213. }
  214. return nDigits;
  215. }
  216. static uint CountIntWidth( int64 nValue, CGCReportPrinter::EIntDisplayType eIntDisplay )
  217. {
  218. uint unDigits;
  219. switch ( eIntDisplay )
  220. {
  221. case CGCReportPrinter::eIntDisplay_Memory_MB:
  222. // "1234.56 MB"
  223. unDigits = CountDigits( nValue / k_nMegabyte ) + 1 + 2 + 3;
  224. break;
  225. case CGCReportPrinter::eIntDisplay_Normal:
  226. default:
  227. // 12345678
  228. unDigits = CountDigits( nValue );
  229. break;
  230. }
  231. return unDigits;
  232. }
  233. static const char * GetIntValueDisplay( int64 nValue, CGCReportPrinter::EIntDisplayType eIntDisplay )
  234. {
  235. static CFmtStr1024 s_fmtResult;
  236. switch ( eIntDisplay )
  237. {
  238. case CGCReportPrinter::eIntDisplay_Memory_MB:
  239. // "1234.56 MB"
  240. s_fmtResult.sprintf( "%lld.%02u MB", ( nValue / k_nMegabyte ), (uint32)( 100.0f * ( ( abs( nValue ) % k_nMegabyte ) / (float)k_nMegabyte ) ) );
  241. break;
  242. case CGCReportPrinter::eIntDisplay_Normal:
  243. default:
  244. // 12345678
  245. s_fmtResult.sprintf( "%lld", nValue );
  246. break;
  247. }
  248. return s_fmtResult;
  249. }
  250. //called to print out the provided report
  251. void CGCReportPrinter::PrintReport( CGCEmitGroup& eg, uint32 nTop )
  252. {
  253. //we need to determine our totals and maximum row widths for our columns first
  254. CUtlVector< uint32 > vColWidths;
  255. vColWidths.EnsureCapacity( m_Columns.Count() );
  256. CUtlVector< Variant_t > vSummary;
  257. vSummary.EnsureCapacity( m_Columns.Count() );
  258. CFmtStr1024 vMsg;
  259. CFmtStr1024 vSeparator;
  260. FOR_EACH_VEC( m_Columns, nCol )
  261. {
  262. const Column_t& col = m_Columns[ nCol ];
  263. uint32 nColWidth = V_strlen( col.m_sName );
  264. Variant_t summary;
  265. //run through all the values to find the row widths and the summary values
  266. FOR_EACH_VEC( m_Rows, nRow )
  267. {
  268. //bail after the first N elements
  269. if( ( nTop > 0 ) && ( ( uint32 )nRow >= nTop ) )
  270. break;
  271. const Variant_t& v = ( *m_Rows[ nRow ] )[ nCol ];
  272. switch( col.m_eType )
  273. {
  274. case eCol_String:
  275. nColWidth = MAX( nColWidth, strlen( v.m_sStr ) );
  276. break;
  277. case eCol_SteamID:
  278. nColWidth = MAX( nColWidth, strlen( v.m_SteamID.Render() ) );
  279. break;
  280. case eCol_Float:
  281. nColWidth = MAX( nColWidth, CountDigits( ( int64 )v.m_fFloat ) + 1 + col.m_nNumDecimals );
  282. switch( col.m_eSummary )
  283. {
  284. case eSummary_Max:
  285. summary.m_fFloat = MAX( summary.m_fFloat, v.m_fFloat );
  286. break;
  287. case eSummary_Total:
  288. summary.m_fFloat += v.m_fFloat;
  289. break;
  290. }
  291. break;
  292. case eCol_Int:
  293. nColWidth = MAX( nColWidth, CountIntWidth( v.m_nInt, col.m_eIntDisplay ) );
  294. switch( col.m_eSummary )
  295. {
  296. case eSummary_Max:
  297. summary.m_nInt = MAX( summary.m_nInt, v.m_nInt );
  298. break;
  299. case eSummary_Total:
  300. summary.m_nInt += v.m_nInt;
  301. break;
  302. }
  303. break;
  304. }
  305. }
  306. //make sure the summary value contributes to the column width
  307. switch( col.m_eType )
  308. {
  309. case eCol_Float:
  310. nColWidth = MAX( nColWidth, CountDigits( ( int64 )summary.m_fFloat ) + 1 + col.m_nNumDecimals );
  311. break;
  312. case eCol_Int:
  313. nColWidth = MAX( nColWidth, CountIntWidth( summary.m_nInt, col.m_eIntDisplay ) );
  314. break;
  315. }
  316. //initialize our column sizes
  317. vColWidths.AddToTail( nColWidth );
  318. vSummary.AddToTail( summary );
  319. vMsg.AppendFormat( "%*s", nColWidth, col.m_sName.String() );
  320. vMsg.Append( ' ' );
  321. for( uint32 nChar = 0; nChar < nColWidth; nChar++ )
  322. vSeparator.Append( '-' );
  323. vSeparator.Append( ' ' );
  324. }
  325. //now print our header
  326. vMsg.Append( '\n' );
  327. vSeparator.Append( '\n' );
  328. EG_MSG( eg, "%s", vMsg.String() );
  329. EG_MSG( eg, "%s", vSeparator.String() );
  330. //buffer for compositing our value
  331. CFmtStr1024 vValue;
  332. //now print each of our columns
  333. FOR_EACH_VEC( m_Rows, nRow )
  334. {
  335. //bail after the first N elements
  336. if( ( nTop > 0 ) && ( ( uint32 )nRow >= nTop ) )
  337. break;
  338. vMsg.Clear();
  339. FOR_EACH_VEC( m_Columns, nCol )
  340. {
  341. const Column_t& col = m_Columns[ nCol ];
  342. const Variant_t& v = ( *m_Rows[ nRow ] )[ nCol ];
  343. const uint32 nColWidth = vColWidths[ nCol ];
  344. vValue.Clear();
  345. switch( col.m_eType )
  346. {
  347. case eCol_String:
  348. vValue.Append( v.m_sStr.String() );
  349. break;
  350. case eCol_SteamID:
  351. vValue.Append( v.m_SteamID.Render() );
  352. break;
  353. case eCol_Float:
  354. vValue.sprintf( "%.*f", col.m_nNumDecimals, v.m_fFloat );
  355. break;
  356. case eCol_Int:
  357. vValue.Append( GetIntValueDisplay( v.m_nInt, col.m_eIntDisplay ) );
  358. break;
  359. }
  360. //print out spaces before we do the link (so we don't have the whole table underlined)
  361. uint32 nValueLen = vValue.Length();
  362. uint32 nNumSpaces = nColWidth - MIN( nColWidth, nValueLen );
  363. for( uint32 nCurrSpace = 0; nCurrSpace < nNumSpaces; nCurrSpace++ )
  364. vMsg.Append( ' ' );
  365. //print out the link if one is provided
  366. if( !v.m_sLink.IsEmpty() )
  367. {
  368. vMsg.AppendFormat( "<link cmd=\"%s\">", v.m_sLink.String() );
  369. vMsg.Append( vValue );
  370. vMsg.Append( "</link>" );
  371. }
  372. else
  373. {
  374. //allow for steam ID special linking if no link is specified
  375. if( col.m_eType == eCol_SteamID )
  376. {
  377. // !FIXME! DOTAMERGE
  378. //vMsg.Append( v.m_SteamID.RenderLink() );
  379. vMsg.Append( v.m_SteamID.Render() );
  380. } else
  381. vMsg.Append( vValue );
  382. }
  383. vMsg.Append( ' ' );
  384. }
  385. vMsg.Append( '\n' );
  386. EG_MSG( eg, "%s", vMsg.String() );
  387. }
  388. //and finally our footer
  389. EG_MSG( eg, "%s", vSeparator.String() );
  390. //and our summary
  391. {
  392. vMsg.Clear();
  393. FOR_EACH_VEC( m_Columns, nCol )
  394. {
  395. const Column_t& col = m_Columns[ nCol ];
  396. const Variant_t& v = vSummary[ nCol ];
  397. const uint32 nColWidth = vColWidths[ nCol ];
  398. if( ( col.m_eType == eCol_String ) || ( col.m_eSummary == eSummary_None ) )
  399. {
  400. vMsg.AppendFormat( "%*s ", nColWidth, "" );
  401. }
  402. else
  403. {
  404. switch( col.m_eType )
  405. {
  406. case eCol_Float:
  407. vMsg.AppendFormat( "%*.*f ", nColWidth, col.m_nNumDecimals, v.m_fFloat );
  408. break;
  409. case eCol_Int:
  410. vMsg.AppendFormat( "%*s ", nColWidth, GetIntValueDisplay( v.m_nInt, col.m_eIntDisplay ) );
  411. break;
  412. }
  413. }
  414. }
  415. vMsg.Append( '\n' );
  416. EG_MSG( eg, "%s", vMsg.String() );
  417. }
  418. }
  419. } // namespace GCSDK