summaryrefslogtreecommitdiff
path: root/replay/cl_recordingsession.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'replay/cl_recordingsession.cpp')
-rw-r--r--replay/cl_recordingsession.cpp451
1 files changed, 451 insertions, 0 deletions
diff --git a/replay/cl_recordingsession.cpp b/replay/cl_recordingsession.cpp
new file mode 100644
index 0000000..59969b4
--- /dev/null
+++ b/replay/cl_recordingsession.cpp
@@ -0,0 +1,451 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cl_recordingsession.h"
+#include "cl_sessioninfodownloader.h"
+#include "cl_recordingsessionmanager.h"
+#include "cl_replaymanager.h"
+#include "cl_recordingsessionblock.h"
+#include "cl_sessionblockdownloader.h"
+#include "replay/ienginereplay.h"
+#include "KeyValues.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//----------------------------------------------------------------------------------------
+
+extern IEngineReplay *g_pEngine;
+
+//----------------------------------------------------------------------------------------
+
+#define MAX_SESSION_INFO_DOWNLOAD_ATTEMPTS 3
+
+//----------------------------------------------------------------------------------------
+
+CClientRecordingSession::CClientRecordingSession( IReplayContext *pContext )
+: CBaseRecordingSession( pContext ),
+ m_iLastBlockToDownload( -1 ),
+ m_iGreatestConsecutiveBlockDownloaded( -1 ),
+ m_nSessionInfoDownloadAttempts( 0 ),
+ m_flLastUpdateTime( -1.0f ),
+ m_pSessionInfoDownloader( NULL ),
+ m_bTimedOut( false ),
+ m_bAllBlocksDownloaded( false )
+{
+}
+
+CClientRecordingSession::~CClientRecordingSession()
+{
+ delete m_pSessionInfoDownloader;
+}
+
+bool CClientRecordingSession::AllReplaysReconstructed() const
+{
+ FOR_EACH_LL( m_lstReplays, it )
+ {
+ const CReplay *pCurReplay = m_lstReplays[ it ];
+ if ( !pCurReplay->HasReconstructedReplay() )
+ return false;
+ }
+
+ return true;
+}
+
+void CClientRecordingSession::DeleteBlocks()
+{
+ // Only delete blocks if all replays have been reconstructed for this session
+ if ( !AllReplaysReconstructed() )
+ return;
+
+ // Delete each block
+ FOR_EACH_VEC( m_vecBlocks, i )
+ {
+ m_pContext->GetRecordingSessionBlockManager()->DeleteBlock( m_vecBlocks[ i ] );
+ }
+
+ // Clear out the list
+ m_vecBlocks.RemoveAll();
+
+ // Clear these out so we don't try to download the blocks again
+ m_iLastBlockToDownload = -1;
+ m_iGreatestConsecutiveBlockDownloaded = -1;
+}
+
+void CClientRecordingSession::SyncSessionBlocks()
+{
+ // If the last update time hasn't been initialized yet, initialize it now since this will be the first time
+ // we are attempting to download the session info file.
+ if ( m_flLastUpdateTime < 0.0f )
+ {
+ m_flLastUpdateTime = g_pEngine->GetHostTime();
+ }
+
+ Assert( !m_pSessionInfoDownloader );
+ IF_REPLAY_DBG( Warning( "Downloading session info...\n" ) );
+ m_pSessionInfoDownloader = new CSessionInfoDownloader();
+ m_pSessionInfoDownloader->DownloadSessionInfoAndUpdateBlocks( this );
+}
+
+void CClientRecordingSession::OnReplayDeleted( CReplay *pReplay )
+{
+ m_lstReplays.FindAndRemove( pReplay );
+
+ // This will load session blocks and delete them from disk if possible. In the case
+ // that all other replays for a session have already been reconstructed and pReplay
+ // was the last replay that was never reconstructed, we should delete session's blocks now.
+ // Note that these calls will only (a) load blocks if they aren't loaded already, and
+ // (b) Delete blocks if all associated replays have been reconstructed.
+ LoadBlocksForSession();
+ DeleteBlocks();
+}
+
+bool CClientRecordingSession::Read( KeyValues *pIn )
+{
+ if ( !BaseClass::Read( pIn ) )
+ return false;
+
+ m_iLastBlockToDownload = pIn->GetInt( "last_block_to_download", -1 );
+ m_iGreatestConsecutiveBlockDownloaded = pIn->GetInt( "last_consec_block_downloaded", -1 );
+// m_bTimedOut = pIn->GetBool( "timed_out" );
+ m_uServerSessionID = pIn->GetUint64( "server_session_id" );
+ m_bAllBlocksDownloaded = pIn->GetBool( "all_blocks_downloaded" );
+
+ return true;
+}
+
+void CClientRecordingSession::Write( KeyValues *pOut )
+{
+ BaseClass::Write( pOut );
+
+ pOut->SetInt( "last_block_to_download", m_iLastBlockToDownload );
+ pOut->SetInt( "last_consec_block_downloaded", m_iGreatestConsecutiveBlockDownloaded );
+// pOut->SetInt( "timed_out", (int)m_bTimedOut );
+ pOut->SetUint64( "server_session_id", m_uServerSessionID );
+ pOut->SetInt( "all_blocks_downloaded", (int)m_bAllBlocksDownloaded );
+}
+
+void CClientRecordingSession::AdjustLastBlockToDownload( int iNewLastBlockToDownload )
+{
+ Assert( m_iLastBlockToDownload > iNewLastBlockToDownload );
+ m_iLastBlockToDownload = iNewLastBlockToDownload;
+
+ // Adjust any replays that refer to this session
+ FOR_EACH_LL( m_lstReplays, i )
+ {
+ CReplay *pCurReplay = m_lstReplays[ i ];
+ if ( pCurReplay->m_iMaxSessionBlockRequired > iNewLastBlockToDownload )
+ {
+ // Adjust replay
+ pCurReplay->m_iMaxSessionBlockRequired = iNewLastBlockToDownload;
+ }
+ }
+}
+
+int CClientRecordingSession::UpdateLastBlockToDownload()
+{
+ // Here we calculate the block we'll need in order to reconstruct the replay at the post-death time,
+ // based on replay_postdeathrecordtime, NOT the current time. The index calculated here may be greater
+ // than the actual last block the server writes, since the round may end or the map may change. This
+ // is adjusted for when we actually download the blocks.
+ extern ConVar replay_postdeathrecordtime;
+ CClientRecordingSessionManager::ServerRecordingState_t *pServerState = &CL_GetRecordingSessionManager()->m_ServerRecordingState;
+
+ const int nCurBlock = pServerState->m_nCurrentBlock;
+ const int nDumpInterval = pServerState->m_nDumpInterval; Assert( nDumpInterval > 0 );
+ const int nAddedBlocks = (int)ceil( replay_postdeathrecordtime.GetFloat() / nDumpInterval ); // Round up
+ const int iPostDeathBlock = nCurBlock + nAddedBlocks;
+
+ IF_REPLAY_DBG( Warning( "nCurBlock: %i\n", nCurBlock ) );
+ IF_REPLAY_DBG( Warning( "nDumpInterval: %i\n", nDumpInterval ) );
+ IF_REPLAY_DBG( Warning( "nAddedBlocks: %i\n", nAddedBlocks ) );
+ IF_REPLAY_DBG( Warning( "iPostDeathBlock: %i\n", iPostDeathBlock ) );
+
+ // Never assign less blocks than we already need
+ m_iLastBlockToDownload = MAX( m_iLastBlockToDownload, iPostDeathBlock );
+
+ CL_GetRecordingSessionManager()->FlagForFlush( this, false );
+
+ IF_REPLAY_DBG( ConColorMsg( 0, Color(0,255,0), "Max block currently needed: %i\n", m_iLastBlockToDownload ) );
+
+ return m_iLastBlockToDownload;
+}
+
+void CClientRecordingSession::Think()
+{
+ CBaseThinker::Think();
+
+ // If the session info downloader's done and can be deleted, free it.
+ if ( m_pSessionInfoDownloader &&
+ m_pSessionInfoDownloader->IsDone() &&
+ m_pSessionInfoDownloader->CanDelete() )
+ {
+ // Failure?
+ if ( m_pSessionInfoDownloader->m_nError != CSessionInfoDownloader::ERROR_NONE )
+ {
+ // If there was an error, increment the error count and update the appropriate replays if
+ // we've tried a sufficient number of times.
+ ++m_nSessionInfoDownloadAttempts;
+ if ( m_nSessionInfoDownloadAttempts >= MAX_SESSION_INFO_DOWNLOAD_ATTEMPTS )
+ {
+ FOR_EACH_LL( m_lstReplays, i )
+ {
+ CReplay *pCurReplay = m_lstReplays[ i ];
+
+ // If this replay has already been set to "ready to convert" state (or beyond), skip.
+ if ( pCurReplay->m_nStatus >= CReplay::REPLAYSTATUS_READYTOCONVERT )
+ continue;
+
+ // Update status
+ pCurReplay->m_nStatus = CReplay::REPLAYSTATUS_ERROR;
+
+ // Display an error message
+ ShowDownloadFailedMessage( pCurReplay );
+
+ // Save now
+ CL_GetReplayManager()->FlagReplayForFlush( pCurReplay, true );
+ }
+ }
+ }
+
+ IF_REPLAY_DBG( Warning( "...session info download complete. Freeing.\n" ) );
+ delete m_pSessionInfoDownloader;
+ m_pSessionInfoDownloader = NULL;
+ }
+}
+
+float CClientRecordingSession::GetNextThinkTime() const
+{
+ return g_pEngine->GetHostTime() + 0.5f;
+}
+
+void CClientRecordingSession::UpdateAllBlocksDownloaded()
+{
+ // We're only "done" if this session is no longer recording and all blocks are downloaded.
+ const bool bOld = m_bAllBlocksDownloaded;
+ m_bAllBlocksDownloaded = !m_bRecording && ( m_iGreatestConsecutiveBlockDownloaded >= m_iLastBlockToDownload );
+
+ // Flag as modified if changed
+ if ( bOld != m_bAllBlocksDownloaded )
+ {
+ CL_GetRecordingSessionManager()->FlagForFlush( this, false );
+ }
+}
+
+void CClientRecordingSession::EnsureDownloadingEnabled()
+{
+ m_bAllBlocksDownloaded = false;
+}
+
+void CClientRecordingSession::UpdateGreatestConsecutiveBlockDownloaded()
+{
+ // Assumes m_vecBlocks is sorted in ascending order (for both reconstruction indices and handle, which should be parallel)
+ int j = 0;
+ int iGreatestConsecutiveBlockDownloaded = 0;
+ FOR_EACH_VEC( m_vecBlocks, i )
+ {
+ CClientRecordingSessionBlock *pCurBlock = CL_CastBlock( m_vecBlocks[ i ] );
+
+ AssertMsg( pCurBlock->m_iReconstruction == j, "Session blocks must be sorted!" );
+
+ // If the block hasn't been downloaded, stop here
+ if ( pCurBlock->m_nDownloadStatus != CClientRecordingSessionBlock::DOWNLOADSTATUS_DOWNLOADED )
+ break;
+
+ // Block has been downloaded - update the counter
+ iGreatestConsecutiveBlockDownloaded = MAX( iGreatestConsecutiveBlockDownloaded, pCurBlock->m_iReconstruction );
+
+ ++j;
+ }
+
+ Assert( iGreatestConsecutiveBlockDownloaded >= 0 );
+ Assert( iGreatestConsecutiveBlockDownloaded < m_vecBlocks.Count() );
+
+ // Cache
+ m_iGreatestConsecutiveBlockDownloaded = iGreatestConsecutiveBlockDownloaded;
+
+ // Mark session as dirty
+ CL_GetRecordingSessionManager()->FlagForFlush( this, false );
+}
+
+void CClientRecordingSession::UpdateReplayStatuses( CClientRecordingSessionBlock *pBlock )
+{
+ AssertMsg( m_vecBlocks.Find( pBlock ) != m_vecBlocks.InvalidIndex(), "Block doesn't belong to session or was not added" );
+
+ // If the download was successful, update the greatest consecutive block downloaded index
+ if ( pBlock->m_nDownloadStatus == CClientRecordingSessionBlock::DOWNLOADSTATUS_DOWNLOADED )
+ {
+ UpdateGreatestConsecutiveBlockDownloaded();
+ UpdateAllBlocksDownloaded();
+ }
+
+ // Block in error state?
+ const bool bFailed = pBlock->m_nDownloadStatus == CClientRecordingSessionBlock::DOWNLOADSTATUS_ERROR;
+
+ // Go through all replays that refer to this session and update their status if necessary
+ FOR_EACH_LL( m_lstReplays, i )
+ {
+ CReplay *pCurReplay = m_lstReplays[ i ];
+
+ // If this replay has already been set to "ready to convert" state (or beyond), skip.
+ if ( pCurReplay->m_nStatus >= CReplay::REPLAYSTATUS_READYTOCONVERT )
+ continue;
+
+ bool bFlush = false;
+
+ // If the download failed and the block is required for this replay, mark as such
+ if ( bFailed && pCurReplay->m_iMaxSessionBlockRequired >= pBlock->m_iReconstruction )
+ {
+ pCurReplay->m_nStatus = CReplay::REPLAYSTATUS_ERROR;
+ bFlush = true;
+
+ // Display an error message
+ ShowDownloadFailedMessage( pCurReplay );
+ }
+
+ // Have we downloaded all blocks required for the given replay?
+ else if ( !bFailed && pCurReplay->m_iMaxSessionBlockRequired <= m_iGreatestConsecutiveBlockDownloaded )
+ {
+ // Update replay's status and mark as dirty
+ pCurReplay->m_nStatus = CReplay::REPLAYSTATUS_READYTOCONVERT;
+
+ // Display a message on the client
+ g_pClient->DisplayReplayMessage( "#Replay_DownloadComplete", false, false, "replay\\downloadcomplete.wav" );
+
+ bFlush = true;
+ }
+
+ // Mark replay as dirty?
+ if ( bFlush )
+ {
+ CL_GetReplayManager()->FlagForFlush( pCurReplay, false );
+ }
+ }
+}
+
+void CClientRecordingSession::OnDownloadTimeout()
+{
+ m_bTimedOut = true;
+
+ // Go through all replays that refer to this session and update their status if necessary
+ FOR_EACH_LL( m_lstReplays, i )
+ {
+ CReplay *pCurReplay = m_lstReplays[ i ];
+
+ // If this replay has already been set to "ready to convert" state (or beyond), skip.
+ if ( pCurReplay->m_nStatus >= CReplay::REPLAYSTATUS_READYTOCONVERT )
+ continue;
+
+ // Check to see if we have enough block info for the current replay
+ if ( m_iGreatestConsecutiveBlockDownloaded >= pCurReplay->m_iMaxSessionBlockRequired )
+ continue;
+
+ // Update replay status
+ pCurReplay->m_nStatus = CReplay::REPLAYSTATUS_ERROR;
+
+ // Display an error message
+ ShowDownloadFailedMessage( pCurReplay );
+
+ // Save the replay
+ CL_GetReplayManager()->FlagForFlush( pCurReplay, false );
+ }
+}
+
+void CClientRecordingSession::RefreshLastUpdateTime()
+{
+ m_flLastUpdateTime = g_pEngine->GetHostTime();
+}
+
+void CClientRecordingSession::ShowDownloadFailedMessage( const CReplay *pReplay )
+{
+ // Don't show the download failed message for replays that were saved during this run of the game.
+ if ( !pReplay || !pReplay->m_bSavedDuringThisSession )
+ return;
+
+ // Display an error message
+ g_pClient->DisplayReplayMessage( "#Replay_DownloadFailed", true, false, "replay\\downloadfailed.wav" );
+}
+
+void CClientRecordingSession::CacheReplay( CReplay *pReplay )
+{
+ Assert( m_lstReplays.Find( pReplay ) == m_lstReplays.InvalidIndex() );
+ m_lstReplays.AddToTail( pReplay );
+
+ // We should no longer auto-delete this session if CacheReplay() is being called. This
+ // can happen if the user connects to a server, saves a replay, deletes the replay (at
+ // which point auto-delete is flagged for the recording session), and then saves another
+ // replay. In this situation, we obviously don't want to delete the session anymore.
+ if ( m_bAutoDelete )
+ {
+ m_bAutoDelete = false;
+ }
+}
+
+bool CClientRecordingSession::ShouldSyncBlocksWithServer() const
+{
+ // Already downloaded all blocks?
+ if ( m_bAllBlocksDownloaded )
+ return false;
+
+ // If block count is out of sync with the m_iLastBlockDownloaded we need to sync up
+ const bool bReachedMaxDownloadAttempts = m_nSessionInfoDownloadAttempts >= MAX_SESSION_INFO_DOWNLOAD_ATTEMPTS;
+ const bool bNeedToDownloadBlocks = m_iLastBlockToDownload >= 0;
+// const bool bAlreadyDownloadedAllNeededBlocks = m_iLastBlockToDownload <= m_iGreatestConsecutiveBlockDownloaded;
+ 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
+ const bool bTimedOut = false;//TimedOut();
+
+ const bool bResult = !bReachedMaxDownloadAttempts &&
+ bNeedToDownloadBlocks &&
+ !bAlreadyDownloadedAllNeededBlocks &&
+ !bTimedOut;
+
+ if ( bResult )
+ {
+ IF_REPLAY_DBG( Warning( "Blocks out of sync for session %i - downloading session info now.\n", GetHandle() ) );
+ }
+ else
+ {
+ DBG3( "NOT syncing because:\n" );
+ if ( bReachedMaxDownloadAttempts ) DBG3( " - Reached maximum download attempts\n" );
+ if ( !bNeedToDownloadBlocks ) DBG3( " - No replay saved yet\n" );
+ if ( bAlreadyDownloadedAllNeededBlocks ) DBG3( " - Already downloaded all needed blocks\n" );
+ if ( bTimedOut ) DBG3( " - Download timed out (session info file didn't change after 90 seconds)\n" );
+ }
+
+ return bResult;
+}
+
+void CClientRecordingSession::PopulateWithRecordingData( int nCurrentRecordingStartTick )
+{
+ BaseClass::PopulateWithRecordingData( nCurrentRecordingStartTick );
+
+ CClientRecordingSessionManager::ServerRecordingState_t *pServerState = &CL_GetRecordingSessionManager()->m_ServerRecordingState;
+ m_strName = pServerState->m_strSessionName;
+
+ // Get download URL from replicated cvars
+ m_strBaseDownloadURL = Replay_GetDownloadURL();
+
+ // Get server session ID
+ m_uServerSessionID = g_pClient->GetServerSessionId();
+}
+
+bool CClientRecordingSession::ShouldDitchSession() const
+{
+ return BaseClass::ShouldDitchSession() || m_lstReplays.Count() == 0;
+}
+
+void CClientRecordingSession::OnDelete()
+{
+ // Abort any session block downloads now
+ CL_GetSessionBlockDownloader()->AbortDownloadsAndCleanup( this );
+ if ( m_pSessionInfoDownloader )
+ {
+ m_pSessionInfoDownloader->CleanupDownloader();
+ }
+
+ // Delete blocks
+ BaseClass::OnDelete();
+}
+
+//----------------------------------------------------------------------------------------