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
451 lines
15 KiB
//========= 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(); |
|
} |
|
|
|
//----------------------------------------------------------------------------------------
|
|
|