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.

309 lines
9.7 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //=======================================================================================//
  4. #include "cl_recordingsessionmanager.h"
  5. #include "replaysystem.h"
  6. #include "cl_replaymanager.h"
  7. #include "cl_recordingsession.h"
  8. #include "cl_sessionblockdownloader.h"
  9. #include "vprof.h"
  10. // memdbgon must be the last include file in a .cpp file!!!
  11. #include "tier0/memdbgon.h"
  12. //----------------------------------------------------------------------------------------
  13. #define CLIENTRECORDINGSESSIONMANAGER_VERSION 0
  14. //----------------------------------------------------------------------------------------
  15. CClientRecordingSessionManager::CClientRecordingSessionManager( IReplayContext *pContext )
  16. : CBaseRecordingSessionManager( pContext ),
  17. m_nNumSessionBlockDownloaders( 0 ),
  18. m_flNextBlockUpdateTime( 0.0f ),
  19. m_flNextPossibleDownloadTime( 0.0f )
  20. {
  21. }
  22. CClientRecordingSessionManager::~CClientRecordingSessionManager()
  23. {
  24. }
  25. bool CClientRecordingSessionManager::Init()
  26. {
  27. AddEventsForListen();
  28. return BaseClass::Init();
  29. }
  30. void CClientRecordingSessionManager::CleanupUnneededBlocks()
  31. {
  32. Msg( "Cleaning up unneeded replay block data...\n" );
  33. FOR_EACH_OBJ( this, i )
  34. {
  35. CClientRecordingSession *pCurSession = CL_CastSession( m_vecObjs[ i ] );
  36. pCurSession->LoadBlocksForSession();
  37. pCurSession->DeleteBlocks();
  38. }
  39. Msg( "Replay cleanup done.\n" );
  40. }
  41. void CClientRecordingSessionManager::AddEventsForListen()
  42. {
  43. g_pGameEventManager->AddListener( this, "replay_endrecord", false );
  44. g_pGameEventManager->AddListener( this, "replay_sessioninfo", false );
  45. g_pGameEventManager->AddListener( this, "player_death", false );
  46. }
  47. const char *CClientRecordingSessionManager::GetNewSessionName() const
  48. {
  49. return m_ServerRecordingState.m_strSessionName;
  50. }
  51. CBaseRecordingSession *CClientRecordingSessionManager::OnSessionStart( int nCurrentRecordingStartTick, const char *pSessionName )
  52. {
  53. return BaseClass::OnSessionStart( nCurrentRecordingStartTick, pSessionName );
  54. }
  55. void CClientRecordingSessionManager::OnSessionEnd()
  56. {
  57. if ( m_pRecordingSession )
  58. {
  59. // Update whether all blocks have been downloaded
  60. AssertMsg( !m_pRecordingSession->m_bRecording, "This flag should have been cleared already! See CBaseRecordingSession::OnStopRecording()" );
  61. CL_CastSession( m_pRecordingSession )->UpdateAllBlocksDownloaded();
  62. }
  63. BaseClass::OnSessionEnd();
  64. m_ServerRecordingState.Clear();
  65. }
  66. void CClientRecordingSessionManager::FireGameEvent( IGameEvent *pEvent )
  67. {
  68. DBG( "CReplayHistoryManager::FireGameEvent()\n" );
  69. if ( g_pEngineClient->IsDemoPlayingBack() )
  70. return;
  71. const char *pEventName = pEvent->GetName();
  72. if ( !V_stricmp( "replay_sessioninfo", pEventName ) )
  73. {
  74. DBG( " replay_sessioninfo\n" );
  75. bool bDisableReplayOnClient = false;
  76. const CUtlString strSessionName = pEvent->GetString( "sn" );
  77. if ( strSessionName.IsEmpty() )
  78. {
  79. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_SessionInfo_BadSessionName" );
  80. bDisableReplayOnClient = true;
  81. }
  82. const int nDumpInterval = pEvent->GetInt( "di", 0 );
  83. if ( nDumpInterval < MIN_SERVER_DUMP_INTERVAL ||
  84. nDumpInterval > MAX_SERVER_DUMP_INTERVAL )
  85. {
  86. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_SessionInfo_BadDumpInterval" );
  87. bDisableReplayOnClient = true;
  88. }
  89. const int nCurrentBlock = pEvent->GetInt( "cb", -1 );
  90. if ( nCurrentBlock < 0 )
  91. {
  92. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_SessionInfo_BadCurrentBlock" );
  93. bDisableReplayOnClient = true;
  94. }
  95. const int nStartTick = pEvent->GetInt( "st", -1 );
  96. if ( nStartTick < 0 )
  97. {
  98. CL_GetErrorSystem()->AddErrorFromTokenName( "Replay_Err_SessionInfo_BadStartTick" );
  99. bDisableReplayOnClient = true;
  100. }
  101. // Cache session state
  102. m_ServerRecordingState.m_strSessionName = strSessionName;
  103. m_ServerRecordingState.m_nDumpInterval = nDumpInterval;
  104. m_ServerRecordingState.m_nCurrentBlock = nCurrentBlock + 1; // This will account for any different between when the server actually dumps a block and the client predicts a dump
  105. m_ServerRecordingState.m_nStartTick = nStartTick;
  106. // If the server's in a weird state, disable replay on the client so they don't
  107. // create any replays that don't play, etc.
  108. g_pClientReplayContextInternal->DisableReplayOnClient( bDisableReplayOnClient );
  109. if ( bDisableReplayOnClient )
  110. return;
  111. OnSessionStart( nStartTick, strSessionName.Get() );
  112. CL_GetReplayManager()->OnSessionStart();
  113. // Update the current block based on the dump interval passed down from
  114. // the server so the client stays in sync w/ the current block index.
  115. m_flNextBlockUpdateTime = g_pEngine->GetHostTime() + nDumpInterval;
  116. }
  117. else if ( !V_stricmp( "replay_endrecord", pEventName ) )
  118. {
  119. DBG( " replay_stoprecord\n" );
  120. // Clear pending replay URL cache, complete any pending replay
  121. CL_GetReplayManager()->OnSessionEnd();
  122. // Notify the session - it will mark itself as no longer recording.
  123. if ( m_pRecordingSession )
  124. {
  125. m_pRecordingSession->OnStopRecording();
  126. }
  127. // Resets current session pointer
  128. OnSessionEnd();
  129. }
  130. // When the player dies, we fill out the rest of the data here
  131. else if ( !V_stricmp( "player_death", pEventName ) &&
  132. pEvent->GetInt( "victim_entindex" ) == g_pEngineClient->GetPlayerSlot() + 1 &&
  133. g_pClient->ShouldCompletePendingReplay( pEvent ) )
  134. {
  135. CL_GetReplayManager()->CompletePendingReplay();
  136. }
  137. }
  138. int CClientRecordingSessionManager::GetVersion() const
  139. {
  140. return CLIENTRECORDINGSESSIONMANAGER_VERSION;
  141. }
  142. void CClientRecordingSessionManager::Think()
  143. {
  144. VPROF_BUDGET( "CClientRecordingSessionManager::Think", VPROF_BUDGETGROUP_REPLAY );
  145. BaseClass::Think();
  146. // Manage all session block downloads
  147. DownloadThink();
  148. if ( !g_pReplay->IsRecording() )
  149. return;
  150. if ( g_pEngineClient->IsDemoPlayingBack() )
  151. return;
  152. const float flHostTime = g_pEngine->GetHostTime();
  153. if ( replay_debug.GetBool() )
  154. {
  155. extern ConVar replay_postdeathrecordtime;
  156. g_pEngineClient->Con_NPrintf( 100, "Time until block dump: ~%f", m_flNextBlockUpdateTime - flHostTime );
  157. g_pEngineClient->Con_NPrintf( 101, "Post-death record time: %f", replay_postdeathrecordtime.GetFloat() );
  158. }
  159. if ( m_flNextBlockUpdateTime <= flHostTime )
  160. {
  161. // Increment current block
  162. ++m_ServerRecordingState.m_nCurrentBlock;
  163. // NOTE: Now the number of blocks in the recording session in progress should be
  164. // different from the number of blocks in its list - so it should spawn a download
  165. // thread to grab the session info and create the new block.
  166. IF_REPLAY_DBG( Warning( "# session blocks updating: %i\n", m_ServerRecordingState.m_nCurrentBlock ) );
  167. // Setup next think
  168. m_flNextBlockUpdateTime = flHostTime + m_ServerRecordingState.m_nDumpInterval;
  169. }
  170. }
  171. void CClientRecordingSessionManager::DownloadThink()
  172. {
  173. bool bKickedOffDownload = false;
  174. const float flHostTime = g_pEngine->GetHostTime();
  175. // For session in progress - check predicted # of blocks on the server based on current number
  176. // of blocks in our list - if different, download the session info and create any outstanding
  177. // blocks on the client.
  178. bool bEnoughTimeHasPassed = flHostTime >= m_flNextPossibleDownloadTime;
  179. if ( !bEnoughTimeHasPassed )
  180. return;
  181. // Go through all sessions to see if we need to create session block downloaders
  182. FOR_EACH_OBJ( this, i )
  183. {
  184. CClientRecordingSession *pCurSession = CL_CastSession( m_vecObjs[ i ] );
  185. // Already have a session block downloader? NOTE: The think manager calls its Think().
  186. if ( pCurSession->HasSessionInfoDownloader() )
  187. continue;
  188. // If the # of blocks on the client is out of sync with the number of blocks we need
  189. // to eventually download, sync with the server, i.e. download the session info and
  190. // create blocks/sync block data as needed.
  191. if ( pCurSession->ShouldSyncBlocksWithServer() )
  192. {
  193. pCurSession->SyncSessionBlocks();
  194. bKickedOffDownload = true;
  195. }
  196. }
  197. // Set next possible download time if we just kicked off a download
  198. if ( bKickedOffDownload )
  199. {
  200. m_flNextPossibleDownloadTime = flHostTime + MAX( MIN_SERVER_DUMP_INTERVAL, CL_GetRecordingSessionManager()->m_ServerRecordingState.m_nDumpInterval );
  201. }
  202. }
  203. CBaseRecordingSession *CClientRecordingSessionManager::Create()
  204. {
  205. return new CClientRecordingSession( m_pContext );
  206. }
  207. IReplayContext *CClientRecordingSessionManager::GetReplayContext() const
  208. {
  209. return g_pClientReplayContextInternal;
  210. }
  211. void CClientRecordingSessionManager::OnObjLoaded( CBaseRecordingSession *pSession )
  212. {
  213. // Make sure the session doesn't try to start downloading if it's done
  214. CL_CastSession( pSession )->UpdateAllBlocksDownloaded();
  215. }
  216. void CClientRecordingSessionManager::OnReplayDeleted( CReplay *pReplay )
  217. {
  218. // Notify the session that a replay has been deleted, in case it needs to do any cleanup.
  219. CClientRecordingSession *pSession = CL_CastSession( FindSession( pReplay->m_hSession ) );
  220. if ( pSession )
  221. {
  222. pSession->OnReplayDeleted( pReplay );
  223. }
  224. // Get the # of replays that depend on the given session
  225. int nNumDependentReplays = CL_GetReplayManager()->GetNumReplaysDependentOnSession( pReplay->m_hSession );
  226. if ( nNumDependentReplays == 1 )
  227. {
  228. // Delete the session - remove the item from the manager itself, delete the
  229. // .dem file, and any .dmx.
  230. DeleteSession( pReplay->m_hSession, false );
  231. }
  232. }
  233. void CClientRecordingSessionManager::OnReplaysLoaded()
  234. {
  235. // Cache replay pointers in sessions for quick access
  236. FOR_EACH_REPLAY( i )
  237. {
  238. CReplay *pCurReplay = GET_REPLAY_AT( i );
  239. CClientRecordingSession *pOwnerSession = CL_CastSession( CL_GetRecordingSessionManager()->FindSession( pCurReplay->m_hSession ) ); Assert( pOwnerSession );
  240. if ( !pOwnerSession )
  241. {
  242. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Load_BadOwnerSession" );
  243. continue;
  244. }
  245. pOwnerSession->CacheReplay( pCurReplay );
  246. }
  247. }
  248. //----------------------------------------------------------------------------------------