summaryrefslogtreecommitdiff
path: root/replay/cl_sessionblockdownloader.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /replay/cl_sessionblockdownloader.cpp
downloadarchived-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.cpp331
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;
+}
+
+//----------------------------------------------------------------------------------------