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.

451 lines
15 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //=======================================================================================//
  4. #include "cl_recordingsession.h"
  5. #include "cl_sessioninfodownloader.h"
  6. #include "cl_recordingsessionmanager.h"
  7. #include "cl_replaymanager.h"
  8. #include "cl_recordingsessionblock.h"
  9. #include "cl_sessionblockdownloader.h"
  10. #include "replay/ienginereplay.h"
  11. #include "KeyValues.h"
  12. // memdbgon must be the last include file in a .cpp file!!!
  13. #include "tier0/memdbgon.h"
  14. //----------------------------------------------------------------------------------------
  15. extern IEngineReplay *g_pEngine;
  16. //----------------------------------------------------------------------------------------
  17. #define MAX_SESSION_INFO_DOWNLOAD_ATTEMPTS 3
  18. //----------------------------------------------------------------------------------------
  19. CClientRecordingSession::CClientRecordingSession( IReplayContext *pContext )
  20. : CBaseRecordingSession( pContext ),
  21. m_iLastBlockToDownload( -1 ),
  22. m_iGreatestConsecutiveBlockDownloaded( -1 ),
  23. m_nSessionInfoDownloadAttempts( 0 ),
  24. m_flLastUpdateTime( -1.0f ),
  25. m_pSessionInfoDownloader( NULL ),
  26. m_bTimedOut( false ),
  27. m_bAllBlocksDownloaded( false )
  28. {
  29. }
  30. CClientRecordingSession::~CClientRecordingSession()
  31. {
  32. delete m_pSessionInfoDownloader;
  33. }
  34. bool CClientRecordingSession::AllReplaysReconstructed() const
  35. {
  36. FOR_EACH_LL( m_lstReplays, it )
  37. {
  38. const CReplay *pCurReplay = m_lstReplays[ it ];
  39. if ( !pCurReplay->HasReconstructedReplay() )
  40. return false;
  41. }
  42. return true;
  43. }
  44. void CClientRecordingSession::DeleteBlocks()
  45. {
  46. // Only delete blocks if all replays have been reconstructed for this session
  47. if ( !AllReplaysReconstructed() )
  48. return;
  49. // Delete each block
  50. FOR_EACH_VEC( m_vecBlocks, i )
  51. {
  52. m_pContext->GetRecordingSessionBlockManager()->DeleteBlock( m_vecBlocks[ i ] );
  53. }
  54. // Clear out the list
  55. m_vecBlocks.RemoveAll();
  56. // Clear these out so we don't try to download the blocks again
  57. m_iLastBlockToDownload = -1;
  58. m_iGreatestConsecutiveBlockDownloaded = -1;
  59. }
  60. void CClientRecordingSession::SyncSessionBlocks()
  61. {
  62. // If the last update time hasn't been initialized yet, initialize it now since this will be the first time
  63. // we are attempting to download the session info file.
  64. if ( m_flLastUpdateTime < 0.0f )
  65. {
  66. m_flLastUpdateTime = g_pEngine->GetHostTime();
  67. }
  68. Assert( !m_pSessionInfoDownloader );
  69. IF_REPLAY_DBG( Warning( "Downloading session info...\n" ) );
  70. m_pSessionInfoDownloader = new CSessionInfoDownloader();
  71. m_pSessionInfoDownloader->DownloadSessionInfoAndUpdateBlocks( this );
  72. }
  73. void CClientRecordingSession::OnReplayDeleted( CReplay *pReplay )
  74. {
  75. m_lstReplays.FindAndRemove( pReplay );
  76. // This will load session blocks and delete them from disk if possible. In the case
  77. // that all other replays for a session have already been reconstructed and pReplay
  78. // was the last replay that was never reconstructed, we should delete session's blocks now.
  79. // Note that these calls will only (a) load blocks if they aren't loaded already, and
  80. // (b) Delete blocks if all associated replays have been reconstructed.
  81. LoadBlocksForSession();
  82. DeleteBlocks();
  83. }
  84. bool CClientRecordingSession::Read( KeyValues *pIn )
  85. {
  86. if ( !BaseClass::Read( pIn ) )
  87. return false;
  88. m_iLastBlockToDownload = pIn->GetInt( "last_block_to_download", -1 );
  89. m_iGreatestConsecutiveBlockDownloaded = pIn->GetInt( "last_consec_block_downloaded", -1 );
  90. // m_bTimedOut = pIn->GetBool( "timed_out" );
  91. m_uServerSessionID = pIn->GetUint64( "server_session_id" );
  92. m_bAllBlocksDownloaded = pIn->GetBool( "all_blocks_downloaded" );
  93. return true;
  94. }
  95. void CClientRecordingSession::Write( KeyValues *pOut )
  96. {
  97. BaseClass::Write( pOut );
  98. pOut->SetInt( "last_block_to_download", m_iLastBlockToDownload );
  99. pOut->SetInt( "last_consec_block_downloaded", m_iGreatestConsecutiveBlockDownloaded );
  100. // pOut->SetInt( "timed_out", (int)m_bTimedOut );
  101. pOut->SetUint64( "server_session_id", m_uServerSessionID );
  102. pOut->SetInt( "all_blocks_downloaded", (int)m_bAllBlocksDownloaded );
  103. }
  104. void CClientRecordingSession::AdjustLastBlockToDownload( int iNewLastBlockToDownload )
  105. {
  106. Assert( m_iLastBlockToDownload > iNewLastBlockToDownload );
  107. m_iLastBlockToDownload = iNewLastBlockToDownload;
  108. // Adjust any replays that refer to this session
  109. FOR_EACH_LL( m_lstReplays, i )
  110. {
  111. CReplay *pCurReplay = m_lstReplays[ i ];
  112. if ( pCurReplay->m_iMaxSessionBlockRequired > iNewLastBlockToDownload )
  113. {
  114. // Adjust replay
  115. pCurReplay->m_iMaxSessionBlockRequired = iNewLastBlockToDownload;
  116. }
  117. }
  118. }
  119. int CClientRecordingSession::UpdateLastBlockToDownload()
  120. {
  121. // Here we calculate the block we'll need in order to reconstruct the replay at the post-death time,
  122. // based on replay_postdeathrecordtime, NOT the current time. The index calculated here may be greater
  123. // than the actual last block the server writes, since the round may end or the map may change. This
  124. // is adjusted for when we actually download the blocks.
  125. extern ConVar replay_postdeathrecordtime;
  126. CClientRecordingSessionManager::ServerRecordingState_t *pServerState = &CL_GetRecordingSessionManager()->m_ServerRecordingState;
  127. const int nCurBlock = pServerState->m_nCurrentBlock;
  128. const int nDumpInterval = pServerState->m_nDumpInterval; Assert( nDumpInterval > 0 );
  129. const int nAddedBlocks = (int)ceil( replay_postdeathrecordtime.GetFloat() / nDumpInterval ); // Round up
  130. const int iPostDeathBlock = nCurBlock + nAddedBlocks;
  131. IF_REPLAY_DBG( Warning( "nCurBlock: %i\n", nCurBlock ) );
  132. IF_REPLAY_DBG( Warning( "nDumpInterval: %i\n", nDumpInterval ) );
  133. IF_REPLAY_DBG( Warning( "nAddedBlocks: %i\n", nAddedBlocks ) );
  134. IF_REPLAY_DBG( Warning( "iPostDeathBlock: %i\n", iPostDeathBlock ) );
  135. // Never assign less blocks than we already need
  136. m_iLastBlockToDownload = MAX( m_iLastBlockToDownload, iPostDeathBlock );
  137. CL_GetRecordingSessionManager()->FlagForFlush( this, false );
  138. IF_REPLAY_DBG( ConColorMsg( 0, Color(0,255,0), "Max block currently needed: %i\n", m_iLastBlockToDownload ) );
  139. return m_iLastBlockToDownload;
  140. }
  141. void CClientRecordingSession::Think()
  142. {
  143. CBaseThinker::Think();
  144. // If the session info downloader's done and can be deleted, free it.
  145. if ( m_pSessionInfoDownloader &&
  146. m_pSessionInfoDownloader->IsDone() &&
  147. m_pSessionInfoDownloader->CanDelete() )
  148. {
  149. // Failure?
  150. if ( m_pSessionInfoDownloader->m_nError != CSessionInfoDownloader::ERROR_NONE )
  151. {
  152. // If there was an error, increment the error count and update the appropriate replays if
  153. // we've tried a sufficient number of times.
  154. ++m_nSessionInfoDownloadAttempts;
  155. if ( m_nSessionInfoDownloadAttempts >= MAX_SESSION_INFO_DOWNLOAD_ATTEMPTS )
  156. {
  157. FOR_EACH_LL( m_lstReplays, i )
  158. {
  159. CReplay *pCurReplay = m_lstReplays[ i ];
  160. // If this replay has already been set to "ready to convert" state (or beyond), skip.
  161. if ( pCurReplay->m_nStatus >= CReplay::REPLAYSTATUS_READYTOCONVERT )
  162. continue;
  163. // Update status
  164. pCurReplay->m_nStatus = CReplay::REPLAYSTATUS_ERROR;
  165. // Display an error message
  166. ShowDownloadFailedMessage( pCurReplay );
  167. // Save now
  168. CL_GetReplayManager()->FlagReplayForFlush( pCurReplay, true );
  169. }
  170. }
  171. }
  172. IF_REPLAY_DBG( Warning( "...session info download complete. Freeing.\n" ) );
  173. delete m_pSessionInfoDownloader;
  174. m_pSessionInfoDownloader = NULL;
  175. }
  176. }
  177. float CClientRecordingSession::GetNextThinkTime() const
  178. {
  179. return g_pEngine->GetHostTime() + 0.5f;
  180. }
  181. void CClientRecordingSession::UpdateAllBlocksDownloaded()
  182. {
  183. // We're only "done" if this session is no longer recording and all blocks are downloaded.
  184. const bool bOld = m_bAllBlocksDownloaded;
  185. m_bAllBlocksDownloaded = !m_bRecording && ( m_iGreatestConsecutiveBlockDownloaded >= m_iLastBlockToDownload );
  186. // Flag as modified if changed
  187. if ( bOld != m_bAllBlocksDownloaded )
  188. {
  189. CL_GetRecordingSessionManager()->FlagForFlush( this, false );
  190. }
  191. }
  192. void CClientRecordingSession::EnsureDownloadingEnabled()
  193. {
  194. m_bAllBlocksDownloaded = false;
  195. }
  196. void CClientRecordingSession::UpdateGreatestConsecutiveBlockDownloaded()
  197. {
  198. // Assumes m_vecBlocks is sorted in ascending order (for both reconstruction indices and handle, which should be parallel)
  199. int j = 0;
  200. int iGreatestConsecutiveBlockDownloaded = 0;
  201. FOR_EACH_VEC( m_vecBlocks, i )
  202. {
  203. CClientRecordingSessionBlock *pCurBlock = CL_CastBlock( m_vecBlocks[ i ] );
  204. AssertMsg( pCurBlock->m_iReconstruction == j, "Session blocks must be sorted!" );
  205. // If the block hasn't been downloaded, stop here
  206. if ( pCurBlock->m_nDownloadStatus != CClientRecordingSessionBlock::DOWNLOADSTATUS_DOWNLOADED )
  207. break;
  208. // Block has been downloaded - update the counter
  209. iGreatestConsecutiveBlockDownloaded = MAX( iGreatestConsecutiveBlockDownloaded, pCurBlock->m_iReconstruction );
  210. ++j;
  211. }
  212. Assert( iGreatestConsecutiveBlockDownloaded >= 0 );
  213. Assert( iGreatestConsecutiveBlockDownloaded < m_vecBlocks.Count() );
  214. // Cache
  215. m_iGreatestConsecutiveBlockDownloaded = iGreatestConsecutiveBlockDownloaded;
  216. // Mark session as dirty
  217. CL_GetRecordingSessionManager()->FlagForFlush( this, false );
  218. }
  219. void CClientRecordingSession::UpdateReplayStatuses( CClientRecordingSessionBlock *pBlock )
  220. {
  221. AssertMsg( m_vecBlocks.Find( pBlock ) != m_vecBlocks.InvalidIndex(), "Block doesn't belong to session or was not added" );
  222. // If the download was successful, update the greatest consecutive block downloaded index
  223. if ( pBlock->m_nDownloadStatus == CClientRecordingSessionBlock::DOWNLOADSTATUS_DOWNLOADED )
  224. {
  225. UpdateGreatestConsecutiveBlockDownloaded();
  226. UpdateAllBlocksDownloaded();
  227. }
  228. // Block in error state?
  229. const bool bFailed = pBlock->m_nDownloadStatus == CClientRecordingSessionBlock::DOWNLOADSTATUS_ERROR;
  230. // Go through all replays that refer to this session and update their status if necessary
  231. FOR_EACH_LL( m_lstReplays, i )
  232. {
  233. CReplay *pCurReplay = m_lstReplays[ i ];
  234. // If this replay has already been set to "ready to convert" state (or beyond), skip.
  235. if ( pCurReplay->m_nStatus >= CReplay::REPLAYSTATUS_READYTOCONVERT )
  236. continue;
  237. bool bFlush = false;
  238. // If the download failed and the block is required for this replay, mark as such
  239. if ( bFailed && pCurReplay->m_iMaxSessionBlockRequired >= pBlock->m_iReconstruction )
  240. {
  241. pCurReplay->m_nStatus = CReplay::REPLAYSTATUS_ERROR;
  242. bFlush = true;
  243. // Display an error message
  244. ShowDownloadFailedMessage( pCurReplay );
  245. }
  246. // Have we downloaded all blocks required for the given replay?
  247. else if ( !bFailed && pCurReplay->m_iMaxSessionBlockRequired <= m_iGreatestConsecutiveBlockDownloaded )
  248. {
  249. // Update replay's status and mark as dirty
  250. pCurReplay->m_nStatus = CReplay::REPLAYSTATUS_READYTOCONVERT;
  251. // Display a message on the client
  252. g_pClient->DisplayReplayMessage( "#Replay_DownloadComplete", false, false, "replay\\downloadcomplete.wav" );
  253. bFlush = true;
  254. }
  255. // Mark replay as dirty?
  256. if ( bFlush )
  257. {
  258. CL_GetReplayManager()->FlagForFlush( pCurReplay, false );
  259. }
  260. }
  261. }
  262. void CClientRecordingSession::OnDownloadTimeout()
  263. {
  264. m_bTimedOut = true;
  265. // Go through all replays that refer to this session and update their status if necessary
  266. FOR_EACH_LL( m_lstReplays, i )
  267. {
  268. CReplay *pCurReplay = m_lstReplays[ i ];
  269. // If this replay has already been set to "ready to convert" state (or beyond), skip.
  270. if ( pCurReplay->m_nStatus >= CReplay::REPLAYSTATUS_READYTOCONVERT )
  271. continue;
  272. // Check to see if we have enough block info for the current replay
  273. if ( m_iGreatestConsecutiveBlockDownloaded >= pCurReplay->m_iMaxSessionBlockRequired )
  274. continue;
  275. // Update replay status
  276. pCurReplay->m_nStatus = CReplay::REPLAYSTATUS_ERROR;
  277. // Display an error message
  278. ShowDownloadFailedMessage( pCurReplay );
  279. // Save the replay
  280. CL_GetReplayManager()->FlagForFlush( pCurReplay, false );
  281. }
  282. }
  283. void CClientRecordingSession::RefreshLastUpdateTime()
  284. {
  285. m_flLastUpdateTime = g_pEngine->GetHostTime();
  286. }
  287. void CClientRecordingSession::ShowDownloadFailedMessage( const CReplay *pReplay )
  288. {
  289. // Don't show the download failed message for replays that were saved during this run of the game.
  290. if ( !pReplay || !pReplay->m_bSavedDuringThisSession )
  291. return;
  292. // Display an error message
  293. g_pClient->DisplayReplayMessage( "#Replay_DownloadFailed", true, false, "replay\\downloadfailed.wav" );
  294. }
  295. void CClientRecordingSession::CacheReplay( CReplay *pReplay )
  296. {
  297. Assert( m_lstReplays.Find( pReplay ) == m_lstReplays.InvalidIndex() );
  298. m_lstReplays.AddToTail( pReplay );
  299. // We should no longer auto-delete this session if CacheReplay() is being called. This
  300. // can happen if the user connects to a server, saves a replay, deletes the replay (at
  301. // which point auto-delete is flagged for the recording session), and then saves another
  302. // replay. In this situation, we obviously don't want to delete the session anymore.
  303. if ( m_bAutoDelete )
  304. {
  305. m_bAutoDelete = false;
  306. }
  307. }
  308. bool CClientRecordingSession::ShouldSyncBlocksWithServer() const
  309. {
  310. // Already downloaded all blocks?
  311. if ( m_bAllBlocksDownloaded )
  312. return false;
  313. // If block count is out of sync with the m_iLastBlockDownloaded we need to sync up
  314. const bool bReachedMaxDownloadAttempts = m_nSessionInfoDownloadAttempts >= MAX_SESSION_INFO_DOWNLOAD_ATTEMPTS;
  315. const bool bNeedToDownloadBlocks = m_iLastBlockToDownload >= 0;
  316. // const bool bAlreadyDownloadedAllNeededBlocks = m_iLastBlockToDownload <= m_iGreatestConsecutiveBlockDownloaded;
  317. const bool bAlreadyDownloadedAllNeededBlocks = m_iLastBlockToDownload < m_vecBlocks.Count(); // NOTE/TODO: Shouldn't this look at m_iGreatestConsecutiveBlockDownloaded? Tried for a week, but it caused bugs. Reverting for now. TODO
  318. const bool bTimedOut = false;//TimedOut();
  319. const bool bResult = !bReachedMaxDownloadAttempts &&
  320. bNeedToDownloadBlocks &&
  321. !bAlreadyDownloadedAllNeededBlocks &&
  322. !bTimedOut;
  323. if ( bResult )
  324. {
  325. IF_REPLAY_DBG( Warning( "Blocks out of sync for session %i - downloading session info now.\n", GetHandle() ) );
  326. }
  327. else
  328. {
  329. DBG3( "NOT syncing because:\n" );
  330. if ( bReachedMaxDownloadAttempts ) DBG3( " - Reached maximum download attempts\n" );
  331. if ( !bNeedToDownloadBlocks ) DBG3( " - No replay saved yet\n" );
  332. if ( bAlreadyDownloadedAllNeededBlocks ) DBG3( " - Already downloaded all needed blocks\n" );
  333. if ( bTimedOut ) DBG3( " - Download timed out (session info file didn't change after 90 seconds)\n" );
  334. }
  335. return bResult;
  336. }
  337. void CClientRecordingSession::PopulateWithRecordingData( int nCurrentRecordingStartTick )
  338. {
  339. BaseClass::PopulateWithRecordingData( nCurrentRecordingStartTick );
  340. CClientRecordingSessionManager::ServerRecordingState_t *pServerState = &CL_GetRecordingSessionManager()->m_ServerRecordingState;
  341. m_strName = pServerState->m_strSessionName;
  342. // Get download URL from replicated cvars
  343. m_strBaseDownloadURL = Replay_GetDownloadURL();
  344. // Get server session ID
  345. m_uServerSessionID = g_pClient->GetServerSessionId();
  346. }
  347. bool CClientRecordingSession::ShouldDitchSession() const
  348. {
  349. return BaseClass::ShouldDitchSession() || m_lstReplays.Count() == 0;
  350. }
  351. void CClientRecordingSession::OnDelete()
  352. {
  353. // Abort any session block downloads now
  354. CL_GetSessionBlockDownloader()->AbortDownloadsAndCleanup( this );
  355. if ( m_pSessionInfoDownloader )
  356. {
  357. m_pSessionInfoDownloader->CleanupDownloader();
  358. }
  359. // Delete blocks
  360. BaseClass::OnDelete();
  361. }
  362. //----------------------------------------------------------------------------------------