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.

367 lines
11 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "isaverestore.h"
  8. #include "env_debughistory.h"
  9. #include "tier0/vprof.h"
  10. // memdbgon must be the last include file in a .cpp file!!!
  11. #include "tier0/memdbgon.h"
  12. // Number of characters worth of debug to use per history category
  13. #define DEBUG_HISTORY_VERSION 6
  14. #define DEBUG_HISTORY_FIRST_VERSIONED 5
  15. #define MAX_DEBUG_HISTORY_LINE_LENGTH 256
  16. #define MAX_DEBUG_HISTORY_LENGTH (1000 * MAX_DEBUG_HISTORY_LINE_LENGTH)
  17. //-----------------------------------------------------------------------------
  18. // Purpose: Stores debug history in savegame files for debugging reference
  19. //-----------------------------------------------------------------------------
  20. class CDebugHistory : public CBaseEntity
  21. {
  22. DECLARE_CLASS( CDebugHistory, CBaseEntity );
  23. public:
  24. DECLARE_DATADESC();
  25. void Spawn();
  26. void AddDebugHistoryLine( int iCategory, const char *szLine );
  27. void ClearHistories( void );
  28. void DumpDebugHistory( int iCategory );
  29. int Save( ISave &save );
  30. int Restore( IRestore &restore );
  31. private:
  32. char m_DebugLines[MAX_HISTORY_CATEGORIES][MAX_DEBUG_HISTORY_LENGTH];
  33. char *m_DebugLineEnd[MAX_HISTORY_CATEGORIES];
  34. };
  35. BEGIN_DATADESC( CDebugHistory )
  36. //DEFINE_FIELD( m_DebugLines, FIELD_CHARACTER ), // Not saved because we write it out manually
  37. //DEFINE_FIELD( m_DebugLineEnd, FIELD_CHARACTER ),
  38. END_DATADESC()
  39. LINK_ENTITY_TO_CLASS( env_debughistory, CDebugHistory );
  40. // The handle to the debug history singleton. Created on first access via GetDebugHistory.
  41. static CHandle< CDebugHistory > s_DebugHistory;
  42. //-----------------------------------------------------------------------------
  43. // Spawn
  44. //-----------------------------------------------------------------------------
  45. void CDebugHistory::Spawn()
  46. {
  47. BaseClass::Spawn();
  48. #ifdef DISABLE_DEBUG_HISTORY
  49. UTIL_Remove( this );
  50. #else
  51. if ( g_pGameRules && g_pGameRules->IsMultiplayer() )
  52. {
  53. UTIL_Remove( this );
  54. }
  55. else
  56. {
  57. Warning( "DEBUG HISTORY IS ENABLED. Disable before release (in env_debughistory.h).\n" );
  58. }
  59. #endif
  60. ClearHistories();
  61. }
  62. //-----------------------------------------------------------------------------
  63. // Purpose:
  64. //-----------------------------------------------------------------------------
  65. void CDebugHistory::AddDebugHistoryLine( int iCategory, const char *szLine )
  66. {
  67. if ( iCategory < 0 || iCategory >= MAX_HISTORY_CATEGORIES )
  68. {
  69. Warning("Attempted to add a debughistory line to category %d. Valid categories are %d to %d.\n", iCategory, 0, (MAX_HISTORY_CATEGORIES-1) );
  70. return;
  71. }
  72. // Don't do debug history before the singleton is properly set up.
  73. if ( !m_DebugLineEnd[iCategory] )
  74. return;
  75. const char *pszRemaining = szLine;
  76. int iCharsToWrite = strlen( pszRemaining ) + 1; // Add 1 so that we copy the null terminator
  77. // Clip the line if it's too long. Wasteful doing it this way, but keeps code below nice & simple.
  78. char szTmpBuffer[MAX_DEBUG_HISTORY_LINE_LENGTH];
  79. if ( iCharsToWrite > MAX_DEBUG_HISTORY_LINE_LENGTH)
  80. {
  81. memcpy( szTmpBuffer, szLine, sizeof(szTmpBuffer) );
  82. szTmpBuffer[MAX_DEBUG_HISTORY_LINE_LENGTH-1] = '\0';
  83. pszRemaining = szTmpBuffer;
  84. iCharsToWrite = MAX_DEBUG_HISTORY_LINE_LENGTH;
  85. }
  86. while ( iCharsToWrite )
  87. {
  88. int iCharsLeftBeforeLoop = sizeof(m_DebugLines[iCategory]) - (m_DebugLineEnd[iCategory] - m_DebugLines[iCategory]);
  89. // Write into the buffer
  90. int iWrote = MIN( iCharsToWrite, iCharsLeftBeforeLoop );
  91. memcpy( m_DebugLineEnd[iCategory], pszRemaining, iWrote );
  92. m_DebugLineEnd[iCategory] += iWrote;
  93. pszRemaining += iWrote;
  94. // Did we loop?
  95. if ( iWrote == iCharsLeftBeforeLoop )
  96. {
  97. m_DebugLineEnd[iCategory] = m_DebugLines[iCategory];
  98. }
  99. iCharsToWrite -= iWrote;
  100. }
  101. }
  102. //-----------------------------------------------------------------------------
  103. // Purpose:
  104. //-----------------------------------------------------------------------------
  105. void CDebugHistory::DumpDebugHistory( int iCategory )
  106. {
  107. if ( iCategory < 0 || iCategory >= MAX_HISTORY_CATEGORIES )
  108. {
  109. Warning("Attempted to dump a history for category %d. Valid categories are %d to %d.\n", iCategory, 0, (MAX_HISTORY_CATEGORIES-1) );
  110. return;
  111. }
  112. // Find the start of the oldest whole debug line.
  113. const char *pszLine = m_DebugLineEnd[iCategory] + 1;
  114. if ( (pszLine - m_DebugLines[iCategory]) >= sizeof(m_DebugLines[iCategory]) )
  115. {
  116. pszLine = m_DebugLines[iCategory];
  117. }
  118. // Are we at the start of a line? If there's a null terminator before us, then we're good to go.
  119. while ( (!( pszLine == m_DebugLines[iCategory] && *(m_DebugLines[iCategory]+sizeof(m_DebugLines[iCategory])-1) == '\0' ) &&
  120. !( pszLine != m_DebugLines[iCategory] && *(pszLine-1) == '\0' ))
  121. || *pszLine == '\0' )
  122. {
  123. pszLine++;
  124. // Have we looped?
  125. if ( (pszLine - m_DebugLines[iCategory]) >= sizeof(m_DebugLines[iCategory]) )
  126. {
  127. pszLine = m_DebugLines[iCategory];
  128. }
  129. if ( pszLine == m_DebugLineEnd[iCategory] )
  130. {
  131. // We looped through the entire history, and found nothing.
  132. Msg( "Debug History of Category %d is EMPTY\n", iCategory );
  133. return;
  134. }
  135. }
  136. // Now print everything up till the end
  137. char szMsgBuffer[MAX_DEBUG_HISTORY_LINE_LENGTH];
  138. char *pszMsg = szMsgBuffer;
  139. Msg( "Starting Debug History Dump of Category %d\n", iCategory );
  140. while ( pszLine != m_DebugLineEnd[iCategory] )
  141. {
  142. *pszMsg = *pszLine;
  143. if ( *pszLine == '\0' )
  144. {
  145. if ( szMsgBuffer[0] != '\0' )
  146. {
  147. // Found a full line, so print it
  148. Msg( "%s", szMsgBuffer );
  149. }
  150. // Clear the buffer
  151. pszMsg = szMsgBuffer;
  152. *pszMsg = '\0';
  153. }
  154. else
  155. {
  156. pszMsg++;
  157. }
  158. pszLine++;
  159. // Have we looped?
  160. if ( (pszLine - m_DebugLines[iCategory]) >= sizeof(m_DebugLines[iCategory]) )
  161. {
  162. pszLine = m_DebugLines[iCategory];
  163. }
  164. }
  165. Msg("Ended Debug History Dump of Category %d\n", iCategory );
  166. }
  167. //-----------------------------------------------------------------------------
  168. // Purpose:
  169. //-----------------------------------------------------------------------------
  170. void CDebugHistory::ClearHistories( void )
  171. {
  172. for ( int i = 0; i < MAX_HISTORY_CATEGORIES; i++ )
  173. {
  174. memset( m_DebugLines[i], 0, sizeof(m_DebugLines[i]) );
  175. m_DebugLineEnd[i] = m_DebugLines[i];
  176. }
  177. }
  178. //-----------------------------------------------------------------------------
  179. // Purpose:
  180. //-----------------------------------------------------------------------------
  181. int CDebugHistory::Save( ISave &save )
  182. {
  183. int iVersion = DEBUG_HISTORY_VERSION;
  184. save.WriteInt( &iVersion );
  185. int iMaxCategorys = MAX_HISTORY_CATEGORIES;
  186. save.WriteInt( &iMaxCategorys );
  187. for ( int iCategory = 0; iCategory < MAX_HISTORY_CATEGORIES; iCategory++ )
  188. {
  189. int iEnd = m_DebugLineEnd[iCategory] - m_DebugLines[iCategory];
  190. save.WriteInt( &iEnd );
  191. save.WriteData( m_DebugLines[iCategory], MAX_DEBUG_HISTORY_LENGTH );
  192. }
  193. return BaseClass::Save(save);
  194. }
  195. //-----------------------------------------------------------------------------
  196. // Purpose:
  197. //-----------------------------------------------------------------------------
  198. int CDebugHistory::Restore( IRestore &restore )
  199. {
  200. ClearHistories();
  201. int iVersion = restore.ReadInt();
  202. if ( iVersion >= DEBUG_HISTORY_FIRST_VERSIONED )
  203. {
  204. int iMaxCategorys = restore.ReadInt();
  205. for ( int iCategory = 0; iCategory < MIN(iMaxCategorys,MAX_HISTORY_CATEGORIES); iCategory++ )
  206. {
  207. int iEnd = restore.ReadInt();
  208. m_DebugLineEnd[iCategory] = m_DebugLines[iCategory] + iEnd;
  209. restore.ReadData( m_DebugLines[iCategory], sizeof(m_DebugLines[iCategory]), 0 );
  210. }
  211. }
  212. else
  213. {
  214. int iMaxCategorys = iVersion;
  215. for ( int iCategory = 0; iCategory < MIN(iMaxCategorys,MAX_HISTORY_CATEGORIES); iCategory++ )
  216. {
  217. int iEnd = restore.ReadInt();
  218. m_DebugLineEnd[iCategory] = m_DebugLines[iCategory] + iEnd;
  219. restore.ReadData( m_DebugLines[iCategory], sizeof(m_DebugLines[iCategory]), 0 );
  220. }
  221. }
  222. return BaseClass::Restore(restore);
  223. }
  224. //-----------------------------------------------------------------------------
  225. // Purpose: Singleton debug history. Created by first usage.
  226. //-----------------------------------------------------------------------------
  227. CDebugHistory *GetDebugHistory()
  228. {
  229. #ifdef DISABLE_DEBUG_HISTORY
  230. return NULL;
  231. #endif
  232. if ( g_pGameRules && g_pGameRules->IsMultiplayer() )
  233. return NULL;
  234. if ( s_DebugHistory == NULL )
  235. {
  236. CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "env_debughistory" );
  237. if ( pEnt )
  238. {
  239. s_DebugHistory = dynamic_cast<CDebugHistory*>(pEnt);
  240. }
  241. else
  242. {
  243. s_DebugHistory = ( CDebugHistory * )CreateEntityByName( "env_debughistory" );
  244. if ( s_DebugHistory )
  245. {
  246. s_DebugHistory->Spawn();
  247. }
  248. }
  249. }
  250. Assert( s_DebugHistory );
  251. return s_DebugHistory;
  252. }
  253. //-----------------------------------------------------------------------------
  254. // Purpose:
  255. //-----------------------------------------------------------------------------
  256. void AddDebugHistoryLine( int iCategory, const char *pszLine )
  257. {
  258. #ifdef DISABLE_DEBUG_HISTORY
  259. return;
  260. #else
  261. if ( g_pGameRules && g_pGameRules->IsMultiplayer() )
  262. return;
  263. if ( !GetDebugHistory() )
  264. {
  265. Warning("Failed to find or create an env_debughistory.\n" );
  266. return;
  267. }
  268. GetDebugHistory()->AddDebugHistoryLine( iCategory, pszLine );
  269. #endif
  270. }
  271. //-----------------------------------------------------------------------------
  272. // Purpose:
  273. //-----------------------------------------------------------------------------
  274. void CC_DebugHistory_AddLine( const CCommand &args )
  275. {
  276. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  277. return;
  278. if ( args.ArgC() < 3 )
  279. {
  280. Warning("Incorrect parameters. Format: <category id> <line>\n");
  281. return;
  282. }
  283. int iCategory = atoi(args[ 1 ]);
  284. const char *pszLine = args[ 2 ];
  285. AddDebugHistoryLine( iCategory, pszLine );
  286. }
  287. static ConCommand dbghist_addline( "dbghist_addline", CC_DebugHistory_AddLine, "Add a line to the debug history. Format: <category id> <line>", FCVAR_NONE );
  288. //-----------------------------------------------------------------------------
  289. // Purpose:
  290. //-----------------------------------------------------------------------------
  291. void CC_DebugHistory_Dump( const CCommand &args )
  292. {
  293. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  294. return;
  295. if ( args.ArgC() < 2 )
  296. {
  297. Warning("Incorrect parameters. Format: <category id>\n");
  298. return;
  299. }
  300. if ( GetDebugHistory() )
  301. {
  302. int iCategory = atoi(args[ 1 ]);
  303. GetDebugHistory()->DumpDebugHistory( iCategory );
  304. }
  305. }
  306. static ConCommand dbghist_dump("dbghist_dump", CC_DebugHistory_Dump,
  307. "Dump the debug history to the console. Format: <category id>\n"
  308. " Categories:\n"
  309. " 0: Entity I/O\n"
  310. " 1: AI Decisions\n"
  311. " 2: Scene Print\n"
  312. " 3: Alyx Blind\n"
  313. " 4: Log of damage done to player",
  314. FCVAR_NONE );