diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /replay/cl_sessionblockdownloader.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'replay/cl_sessionblockdownloader.cpp')
| -rw-r--r-- | replay/cl_sessionblockdownloader.cpp | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/replay/cl_sessionblockdownloader.cpp b/replay/cl_sessionblockdownloader.cpp new file mode 100644 index 0000000..398a67c --- /dev/null +++ b/replay/cl_sessionblockdownloader.cpp @@ -0,0 +1,331 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +//=======================================================================================// + +#include "cl_sessionblockdownloader.h" +#include "replay/ienginereplay.h" +#include "cl_recordingsessionblockmanager.h" +#include "cl_replaycontext.h" +#include "cl_recordingsession.h" +#include "cl_recordingsessionblock.h" +#include "errorsystem.h" +#include "convar.h" +#include "vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//---------------------------------------------------------------------------------------- + +extern IEngineReplay *g_pEngine; +extern ConVar replay_maxconcurrentdownloads; + +//---------------------------------------------------------------------------------------- + +int CSessionBlockDownloader::sm_nNumCurrentDownloads = 0; + +//---------------------------------------------------------------------------------------- + +CSessionBlockDownloader::CSessionBlockDownloader() +: m_nMaxBlock( -1 ) +{ +} + +void CSessionBlockDownloader::Shutdown() +{ + AbortDownloadsAndCleanup( NULL ); + + Assert( sm_nNumCurrentDownloads == 0 ); +} + +void CSessionBlockDownloader::AbortDownloadsAndCleanup( CClientRecordingSession *pSession ) +{ + // NOTE: sm_nNumCurrentDownloads will be decremented in OnDownloadComplete(), which is + // invoked by CHttpDownloader::AbortDownloadAndCleanup() + + // Abort any remaining downloads - callbacks will be invoked, so this shutdown + // should be called before any of those objects are cleaned up. + FOR_EACH_LL( m_lstDownloaders, i ) + { + CHttpDownloader *pCurDownloader = m_lstDownloaders[ i ]; + + // If a session was passed in, make sure it has the same handle as the block + CClientRecordingSessionBlock *pBlock = (CClientRecordingSessionBlock *)pCurDownloader->GetUserData(); + if ( pSession && ( !pBlock || pBlock->m_hSession != pSession->GetHandle() ) ) + continue; + + pCurDownloader->AbortDownloadAndCleanup(); + delete pCurDownloader; + } + m_lstDownloaders.RemoveAll(); +} + +bool CSessionBlockDownloader::AtMaxConcurrentDownloads() const +{ + return ( sm_nNumCurrentDownloads >= replay_maxconcurrentdownloads.GetInt() ); +} + +float CSessionBlockDownloader::GetNextThinkTime() const +{ + return g_pEngine->GetHostTime() + 0.5f; +} + +void CSessionBlockDownloader::Think() +{ + VPROF_BUDGET( "CSessionBlockDownloader::Think", VPROF_BUDGETGROUP_REPLAY ); + + CBaseThinker::Think(); + + // Hack to not think right away + if ( g_pEngine->GetHostTime() < 3 ) + return; + + // Don't go over the desired maximum # of concurrent downloads + if ( !AtMaxConcurrentDownloads() ) + { + // Go through all blocks and begin downloading any that the server index downloader + // has determined are ready + CClientRecordingSessionBlockManager *pBlockManager = CL_GetRecordingSessionBlockManager(); + FOR_EACH_OBJ( pBlockManager, i ) + { + CClientRecordingSessionBlock *pBlock = CL_CastBlock( pBlockManager->m_vecObjs[ i ] ); + + // Checks to see if the remote status is marked as ready for download + if ( !pBlock->ShouldDownloadNow() ) + continue; + + // Lookup the session for the block + CClientRecordingSession *pSession = CL_CastSession( CL_GetRecordingSessionManager()->Find( pBlock->m_hSession ) ); + if ( !pSession ) + { + AssertMsg( 0, "Session for block not found! This should never happen!" ); + continue; + } + + // Do we need any blocks at all from this session? Is this block within range? + int iLastBlockToDownload = pSession->GetLastBlockToDownload(); + if ( iLastBlockToDownload < 0 || pBlock->m_iReconstruction > iLastBlockToDownload ) + { + continue; + } + + // Begin the download + CHttpDownloader *pDownloader = new CHttpDownloader( this ); + const char *pFilename = V_UnqualifiedFileName( pBlock->m_szFullFilename ); +#ifdef _DEBUG + extern ConVar replay_forcedownloadurl; + const char *pForceURL = replay_forcedownloadurl.GetString(); + const char *pURL = pForceURL[0] ? pForceURL : Replay_va( "%s%s", pSession->m_strBaseDownloadURL.Get(), pFilename ); +#else + const char *pURL = Replay_va( "%s%s", pSession->m_strBaseDownloadURL.Get(), pFilename ); +#endif + const char *pGamePath = Replay_va( "%s%s", CL_GetRecordingSessionBlockManager()->GetSavePath(), pFilename ); + pDownloader->BeginDownload( pURL, pGamePath, (void *)pBlock, &pBlock->m_uBytesDownloaded ); + + IF_REPLAY_DBG( + Warning ( "%s block %i from %s to path %s...\n", + pBlock->GetNumDownloadAttempts() ? "RETRYING download for" : "Downloading" , + pBlock->m_iReconstruction, pURL, pGamePath ) + ); + + // Add the downloader + m_lstDownloaders.AddToTail( pDownloader ); + + // Update block's status + pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_DOWNLOADING; + + // Mark as dirty + CL_GetRecordingSessionBlockManager()->FlagForFlush( pBlock, false ); + + // Update # of concurrent downloads + ++sm_nNumCurrentDownloads; + + // Get out if we're at max downloads now + if ( AtMaxConcurrentDownloads() ) + break; + } + } + + int it = m_lstDownloaders.Head(); + while ( it != m_lstDownloaders.InvalidIndex() ) + { + // Remove finished downloaders + CHttpDownloader *pCurDownloader = m_lstDownloaders[ it ]; + if ( pCurDownloader->IsDone() && pCurDownloader->CanDelete() ) + { + int itRemove = it; + + // Next + it = m_lstDownloaders.Next( it ); + + // Remove the downloader from the list + m_lstDownloaders.Remove( itRemove ); + + // Free the downloader + delete pCurDownloader; + } + else + { + // Let the downloader think + pCurDownloader->Think(); + + // Next + it = m_lstDownloaders.Next( it ); + } + } +} + +void CSessionBlockDownloader::OnConnecting( CHttpDownloader *pDownloader ) +{ + CClientRecordingSessionBlock *pBlock = (CClientRecordingSessionBlock *)pDownloader->GetUserData(); AssertValidReadPtr( pBlock ); + pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_CONNECTING; +} + +void CSessionBlockDownloader::OnFetch( CHttpDownloader *pDownloader ) +{ + CClientRecordingSessionBlock *pBlock = (CClientRecordingSessionBlock *)pDownloader->GetUserData(); AssertValidReadPtr( pBlock ); + pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_DOWNLOADING; +} + +void CSessionBlockDownloader::OnDownloadComplete( CHttpDownloader *pDownloader, const unsigned char *pData ) +{ + // TODO: Compare downloaded byte size (pDownloader->GetBytesDownloaded()) to size in block + // Write block size into session info on server + + int it = m_lstDownloaders.Find( pDownloader ); + if ( it == m_lstDownloaders.InvalidIndex() ) + { + AssertMsg( 0, "Downloader now found in session block downloader list! This should never happen!" ); + return; + } + + CClientRecordingSessionBlock *pBlock = (CClientRecordingSessionBlock *)pDownloader->GetUserData(); AssertValidReadPtr( pBlock ); + const int nSize = pDownloader->GetSize(); + + HTTPStatus_t nStatus = pDownloader->GetStatus(); + +#if _DEBUG + extern ConVar replay_simulatedownloadfailure; + if ( replay_simulatedownloadfailure.GetInt() == 3 ) + { + nStatus = HTTP_ERROR; + } +#endif + + switch ( nStatus ) + { + case HTTP_ABORTED: + + pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_ABORTED; + break; + + case HTTP_DONE: + + { + unsigned char aLocalHash[16]; + +#if _DEBUG + extern ConVar replay_simulate_size_discrepancy; + extern ConVar replay_simulate_bad_hash; + + const bool bSizesDiffer = replay_simulate_size_discrepancy.GetBool() || pBlock->m_uFileSize != pDownloader->GetBytesDownloaded(); + const bool bHashFail = replay_simulate_bad_hash.GetBool() || !pBlock->ValidateData( pData, nSize, aLocalHash ); +#else + const bool bSizesDiffer = pBlock->m_uFileSize != pDownloader->GetBytesDownloaded(); + const bool bHashFail = !pBlock->ValidateData( pData, nSize ); +#endif + + bool bTryAgain = false; + if ( bSizesDiffer ) + { + AssertMsg( 0, "Number of bytes downloaded differs from size specified in session info file." ); + bTryAgain = true; + } + else if ( bHashFail ) + { + DBG( "Download failed - either data validation failed\n" ); + + // Data validation failed + pBlock->m_bDataInvalid = true; + bTryAgain = true; + } + else + { + DBG( "Data validation successful.\n" ); + + // Data validation succeeded + pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_DOWNLOADED; + + // Clear out any previous errors + pBlock->m_nHttpError = HTTP_ERROR_NONE; + pBlock->m_bDataInvalid = false; + } + + // Failed? + if ( bTryAgain ) + { + // Attempt to download again if necessary + pBlock->AttemptToResetForDownload(); + + // Report error to OGS. + CL_GetErrorSystem()->OGS_ReportSessionBlockDownloadError( + pDownloader, pBlock, pDownloader->GetBytesDownloaded(), m_nMaxBlock, &bSizesDiffer, + &bHashFail, aLocalHash + ); + } + } + + break; + + case HTTP_ERROR: + + // If we've attempted and failed to download the block 3 times, report the error and + // put the block in error state. + if ( pBlock->AttemptToResetForDownload() ) + break; + + // Otherwise, we've max'd out attempts - cache the error state + pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_ERROR; + pBlock->m_nHttpError = pDownloader->GetError(); + + // Now that the block is in the error state, the replay's status will be updated to + // the error state as well (see pSession->UpdateReplayStatuses() below). + + // Report the error to user & OGS + { + // Create a session block download error. + CL_GetErrorSystem()->OGS_ReportSessionBlockDownloadError( + pDownloader, pBlock, pDownloader->GetBytesDownloaded(), m_nMaxBlock, NULL, NULL, NULL + ); + + // Report error to user. + const char *pToken = CHttpDownloader::GetHttpErrorToken( pDownloader->GetError() ); + CL_GetErrorSystem()->AddFormattedErrorFromTokenName( + "#Replay_DL_Err_HTTP_Prefix", + new KeyValues( + "args", + "err", + pToken + ) + ); + } + + break; + + default: + AssertMsg( 0, "Invalid download state in CSessionBlockDownloader::OnDownloadComplete()" ); + } + + // Flag block for flush + CL_GetRecordingSessionBlockManager()->FlagForFlush( pBlock, false ); + + CClientRecordingSession *pSession = CL_CastSession( CL_GetRecordingSessionManager()->FindSession( pBlock->m_hSession ) ); Assert( pSession ); + + // Update all replays that care about this block + pSession->UpdateReplayStatuses( pBlock ); + + // Decrement # of downloads + --sm_nNumCurrentDownloads; +} + +//---------------------------------------------------------------------------------------- |