1
This commit is contained in:
522
replay/replaysystem.cpp
Normal file
522
replay/replaysystem.cpp
Normal file
@ -0,0 +1,522 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
//=======================================================================================//
|
||||
|
||||
#include "replaysystem.h"
|
||||
#include "tier2/tier2.h"
|
||||
#include "iserver.h"
|
||||
#include "iclient.h"
|
||||
#include "icliententitylist.h"
|
||||
#include "igameevents.h"
|
||||
#include "replay/ireplaymovierenderer.h"
|
||||
#include "replay/ireplayscreenshotsystem.h"
|
||||
#include "replay/replayutils.h"
|
||||
#include "replay/replaylib.h"
|
||||
#include "sv_sessionrecorder.h"
|
||||
#include "sv_recordingsession.h"
|
||||
#include "cl_screenshotmanager.h"
|
||||
#include "netmessages.h"
|
||||
#include "thinkmanager.h"
|
||||
#include "managertest.h"
|
||||
#include "vprof.h"
|
||||
#include "sv_fileservercleanup.h"
|
||||
|
||||
#if !defined( _X360 )
|
||||
#include "winlite.h"
|
||||
#include "xbox/xboxstubs.h"
|
||||
#endif
|
||||
|
||||
// TODO: Deal with linux build includes
|
||||
#ifdef IS_WINDOWS_PC
|
||||
#include <winsock.h>
|
||||
#endif
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
#undef CreateEvent // Can't call IGameEventManager2::CreateEvent() without this
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
#if !defined( DEDICATED )
|
||||
IEngineClientReplay *g_pEngineClient = NULL;
|
||||
CClientReplayContext *g_pClientReplayContextInternal = NULL;
|
||||
IVDebugOverlay *g_pDebugOverlay = NULL;
|
||||
IDownloadSystem *g_pDownloadSystem = NULL;
|
||||
#endif
|
||||
|
||||
vgui::ILocalize *g_pVGuiLocalize = NULL;
|
||||
CServerReplayContext *g_pServerReplayContext = NULL;
|
||||
IClientReplay *g_pClient = NULL;
|
||||
IServerReplay *g_pServer = NULL;
|
||||
IEngineReplay *g_pEngine = NULL;
|
||||
IGameEventManager2 *g_pGameEventManager = NULL;
|
||||
IEngineTrace *g_pEngineTraceClient = NULL;
|
||||
IReplayDemoPlayer *g_pReplayDemoPlayer = NULL;
|
||||
IClientEntityList *entitylist = NULL; // icliententitylist.h forces the use of this name by externing in the header
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
#define REPLAY_INIT( exp_ ) \
|
||||
if ( !( exp_ ) ) \
|
||||
{ \
|
||||
Warning( "CReplaySystem::Connect() failed on: \"%s\"!\n", #exp_ ); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
class CReplaySystem : public CTier2AppSystem< IReplaySystem >
|
||||
{
|
||||
typedef CTier2AppSystem< IReplaySystem > BaseClass;
|
||||
|
||||
public:
|
||||
virtual bool Connect( CreateInterfaceFn fnFactory )
|
||||
{
|
||||
REPLAY_INIT( fnFactory );
|
||||
REPLAY_INIT( BaseClass::Connect( fnFactory ) );
|
||||
|
||||
ConVar_Register( FCVAR_CLIENTDLL );
|
||||
|
||||
REPLAY_INIT( g_pFullFileSystem );
|
||||
|
||||
g_pEngine = (IEngineReplay *)fnFactory( ENGINE_REPLAY_INTERFACE_VERSION, NULL );
|
||||
REPLAY_INIT( g_pEngine );
|
||||
|
||||
REPLAY_INIT( g_pEngine->IsSupportedModAndPlatform() );
|
||||
|
||||
#if !defined( DEDICATED )
|
||||
g_pEngineClient = (IEngineClientReplay *)fnFactory( ENGINE_REPLAY_CLIENT_INTERFACE_VERSION, NULL );
|
||||
REPLAY_INIT( g_pEngineClient );
|
||||
|
||||
g_pEngineTraceClient = (IEngineTrace *)fnFactory( INTERFACEVERSION_ENGINETRACE_CLIENT, NULL );
|
||||
REPLAY_INIT( g_pEngineTraceClient );
|
||||
|
||||
g_pReplayDemoPlayer = (IReplayDemoPlayer *)fnFactory( INTERFACEVERSION_REPLAYDEMOPLAYER, NULL );
|
||||
REPLAY_INIT( g_pReplayDemoPlayer );
|
||||
|
||||
g_pVGuiLocalize = (vgui::ILocalize *)fnFactory( VGUI_LOCALIZE_INTERFACE_VERSION, NULL );
|
||||
REPLAY_INIT( g_pVGuiLocalize );
|
||||
|
||||
g_pDebugOverlay = ( IVDebugOverlay * )fnFactory( VDEBUG_OVERLAY_INTERFACE_VERSION, NULL );
|
||||
REPLAY_INIT( g_pDebugOverlay );
|
||||
#endif
|
||||
|
||||
g_pGameEventManager = (IGameEventManager2 *)fnFactory( INTERFACEVERSION_GAMEEVENTSMANAGER2, NULL );
|
||||
REPLAY_INIT( g_pGameEventManager );
|
||||
|
||||
#if !defined( DEDICATED )
|
||||
g_pDownloadSystem = (IDownloadSystem *)fnFactory( INTERFACEVERSION_DOWNLOADSYSTEM, NULL );
|
||||
REPLAY_INIT( g_pDownloadSystem );
|
||||
|
||||
// Create client context now if not running a dedicated server
|
||||
if ( !g_pEngine->IsDedicated() )
|
||||
{
|
||||
g_pClientReplayContextInternal = new CClientReplayContext();
|
||||
}
|
||||
|
||||
// ...and create server replay context if we are
|
||||
else
|
||||
#endif
|
||||
{
|
||||
g_pServerReplayContext = new CServerReplayContext();
|
||||
}
|
||||
|
||||
#if defined( DEDICATED )
|
||||
REPLAY_INIT( ReplayLib_Init( g_pEngine->GetGameDir(), NULL ) ) // Init without the client replay context
|
||||
#else
|
||||
REPLAY_INIT( ReplayLib_Init( g_pEngine->GetGameDir(), g_pClientReplayContextInternal ) );
|
||||
#endif
|
||||
|
||||
Test();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void Disconnect()
|
||||
{
|
||||
BaseClass::Disconnect();
|
||||
}
|
||||
|
||||
virtual InitReturnVal_t Init()
|
||||
{
|
||||
InitReturnVal_t nRetVal = BaseClass::Init();
|
||||
if ( nRetVal != INIT_OK )
|
||||
return nRetVal;
|
||||
|
||||
return INIT_OK;
|
||||
}
|
||||
|
||||
virtual void Shutdown()
|
||||
{
|
||||
BaseClass::Shutdown();
|
||||
|
||||
#if !defined( DEDICATED )
|
||||
delete g_pClientReplayContextInternal;
|
||||
g_pClientReplayContextInternal = NULL;
|
||||
#endif
|
||||
|
||||
delete g_pServerReplayContext;
|
||||
g_pServerReplayContext = NULL;
|
||||
}
|
||||
|
||||
virtual void Think()
|
||||
{
|
||||
VPROF_BUDGET( "CReplaySystem::Think", VPROF_BUDGETGROUP_REPLAY );
|
||||
|
||||
g_pThinkManager->Think();
|
||||
}
|
||||
|
||||
virtual bool IsReplayEnabled()
|
||||
{
|
||||
extern ConVar replay_enable;
|
||||
return replay_enable.GetInt() != 0;
|
||||
}
|
||||
|
||||
virtual bool IsRecording()
|
||||
{
|
||||
// NOTE: demoplayer->IsPlayingBack() needs to be checked here, as "replay_enable" and
|
||||
// "replay_recording" will inevitably get stored with signon data in any playing demo.
|
||||
// If the !demoplayer->IsPlayingBack() line is omitted below, Replay_IsRecording()
|
||||
// becomes useless during demo playback and will always return true.
|
||||
extern ConVar replay_recording;
|
||||
#if !defined( DEDICATED )
|
||||
return IsReplayEnabled() &&
|
||||
replay_recording.GetInt() &&
|
||||
!g_pEngineClient->IsDemoPlayingBack();
|
||||
#else
|
||||
return IsReplayEnabled() &&
|
||||
replay_recording.GetInt();
|
||||
#endif
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
// Client-specific implementation:
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
virtual bool CL_Init( CreateInterfaceFn fnClientFactory )
|
||||
{
|
||||
#if !defined( DEDICATED )
|
||||
g_pClient = (IClientReplay *)fnClientFactory( CLIENT_REPLAY_INTERFACE_VERSION, NULL );
|
||||
if ( !g_pClient )
|
||||
return false;
|
||||
|
||||
entitylist = (IClientEntityList *)fnClientFactory( VCLIENTENTITYLIST_INTERFACE_VERSION, NULL );
|
||||
if ( !entitylist )
|
||||
return false;
|
||||
|
||||
if ( !g_pClientReplayContextInternal->Init( fnClientFactory ) )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual void CL_Shutdown()
|
||||
{
|
||||
#if !defined( DEDICATED )
|
||||
if ( g_pClientReplayContextInternal && g_pClientReplayContextInternal->IsInitialized() )
|
||||
{
|
||||
g_pClientReplayContextInternal->Shutdown();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual void CL_Render()
|
||||
{
|
||||
#if !defined( DEDICATED )
|
||||
// If the replay system wants to take a screenshot, do it now
|
||||
if ( g_pClientReplayContextInternal->m_pScreenshotManager->ShouldCaptureScreenshot() )
|
||||
{
|
||||
g_pClientReplayContextInternal->m_pScreenshotManager->DoCaptureScreenshot();
|
||||
return;
|
||||
}
|
||||
|
||||
// Currently rendering? NOTE: GetMovieRenderer() only returns a valid ptr during rendering
|
||||
IReplayMovieRenderer *pReplayMovieRenderer = g_pClientReplayContextInternal->GetMovieRenderer();
|
||||
if ( !pReplayMovieRenderer )
|
||||
return;
|
||||
|
||||
pReplayMovieRenderer->RenderVideo();
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual IClientReplayContext *CL_GetContext()
|
||||
{
|
||||
#if !defined( DEDICATED )
|
||||
return g_pClientReplayContextInternal;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
// Server-specific implementation:
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
virtual bool SV_Init( CreateInterfaceFn fnServerFactory )
|
||||
{
|
||||
if ( !g_pEngine->IsDedicated() || !g_pServerReplayContext )
|
||||
return false;
|
||||
|
||||
g_pServer = (IServerReplay *)fnServerFactory( SERVER_REPLAY_INTERFACE_VERSION, NULL );
|
||||
if ( !g_pServer )
|
||||
return false;
|
||||
|
||||
Assert( !ReplayServer() );
|
||||
|
||||
return g_pServerReplayContext->Init( fnServerFactory );
|
||||
}
|
||||
|
||||
virtual void SV_Shutdown()
|
||||
{
|
||||
if ( g_pServerReplayContext && g_pServerReplayContext->IsInitialized() )
|
||||
{
|
||||
g_pServerReplayContext->Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
virtual IServerReplayContext *SV_GetContext()
|
||||
{
|
||||
return g_pServerReplayContext;
|
||||
}
|
||||
|
||||
virtual bool SV_ShouldBeginRecording( bool bIsInWaitingForPlayers )
|
||||
{
|
||||
extern ConVar replay_enable;
|
||||
|
||||
return !bIsInWaitingForPlayers &&
|
||||
#if !defined( DEDICATED )
|
||||
!g_pEngineClient->IsPlayingReplayDemo() &&
|
||||
#endif
|
||||
replay_enable.GetBool();
|
||||
}
|
||||
|
||||
virtual void SV_NotifyReplayRequested()
|
||||
{
|
||||
if ( !g_pEngine->IsSupportedModAndPlatform() )
|
||||
return;
|
||||
|
||||
CServerRecordingSession *pSession = SV_GetRecordingSessionInProgress();
|
||||
if ( !pSession )
|
||||
return;
|
||||
|
||||
// A replay was requested - notify the session so we don't throw it away at the end of the round
|
||||
pSession->NotifyReplayRequested();
|
||||
}
|
||||
|
||||
virtual void SV_SendReplayEvent( const char *pEventName, int nClientSlot )
|
||||
{
|
||||
// Attempt to create the event
|
||||
IGameEvent *pEvent = g_pGameEventManager->CreateEvent( pEventName, true );
|
||||
if ( !pEvent )
|
||||
return;
|
||||
|
||||
SV_SendReplayEvent( pEvent, nClientSlot );
|
||||
}
|
||||
|
||||
virtual void SV_SendReplayEvent( IGameEvent *pEvent, int nClientSlot/*=-1*/ )
|
||||
{
|
||||
IServer *pGameServer = g_pEngine->GetGameServer();
|
||||
|
||||
if ( !pEvent )
|
||||
return;
|
||||
|
||||
// Write event info to SVC_GameEvent msg
|
||||
char buffer_data[MAX_EVENT_BYTES];
|
||||
SVC_GameEvent msg;
|
||||
msg.SetReliable( false );
|
||||
msg.m_DataOut.StartWriting( buffer_data, sizeof( buffer_data ) );
|
||||
if ( !g_pGameEventManager->SerializeEvent( pEvent, &msg.m_DataOut ) )
|
||||
{
|
||||
DevMsg( "Replay_SendReplayEvent(): failed to serialize event '%s'.\n", pEvent->GetName() );
|
||||
goto free_event;
|
||||
}
|
||||
|
||||
// Send to all clients?
|
||||
if ( nClientSlot == -1 )
|
||||
{
|
||||
for ( int i = 0; i < pGameServer->GetClientCount(); ++i )
|
||||
{
|
||||
IClient *pClient = pGameServer->GetClient( i );
|
||||
if ( pClient )
|
||||
{
|
||||
// Send the message
|
||||
pClient->SendNetMsg( msg );
|
||||
}
|
||||
}
|
||||
}
|
||||
else // Send to just the one client?
|
||||
{
|
||||
IClient *pClient = pGameServer->GetClient( nClientSlot );
|
||||
if ( pClient )
|
||||
{
|
||||
// Send the message
|
||||
pClient->SendNetMsg( msg );
|
||||
}
|
||||
}
|
||||
|
||||
free_event:
|
||||
g_pGameEventManager->FreeEvent( pEvent );
|
||||
}
|
||||
|
||||
virtual void SV_EndRecordingSession( bool bForceSynchronousPublish/*=false*/ )
|
||||
{
|
||||
if ( !g_pEngine->IsSupportedModAndPlatform() )
|
||||
return;
|
||||
|
||||
if ( !ReplayServer() )
|
||||
return;
|
||||
|
||||
SV_GetSessionRecorder()->StopRecording( false );
|
||||
|
||||
if ( bForceSynchronousPublish )
|
||||
{
|
||||
// Publish all files
|
||||
SV_GetSessionRecorder()->PublishAllSynchronous();
|
||||
|
||||
// This should unlock all sessions
|
||||
SV_GetSessionRecorder()->UpdateSessionLocks();
|
||||
|
||||
// Let the session manager do any cleanup - this will remove files associated with a ditched
|
||||
// session. For example, if the server was shut down in the middle of a round where no one
|
||||
// saved a replay, the files will be published synchronously above and then cleaned up
|
||||
// synchronously here.
|
||||
SV_GetRecordingSessionManager()->DoSessionCleanup();
|
||||
|
||||
// Since the recording session manager will kick off a cleanup job, we need to wait for it
|
||||
// here since we're shutting down.
|
||||
SV_GetFileserverCleaner()->BlockForCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
void Test()
|
||||
{
|
||||
#if !defined( DEDICATED ) && _DEBUG
|
||||
// This gets called after interfaces are hooked up, and before any of the
|
||||
// internal replay systems get init'd.
|
||||
CTestManager::Test();
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
static CReplaySystem s_Replay;
|
||||
IReplaySystem *g_pReplay = &s_Replay;
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CReplaySystem, IReplaySystem, REPLAY_INTERFACE_VERSION,
|
||||
s_Replay );
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
void Replay_MsgBox( const char *pText )
|
||||
{
|
||||
g_pClient->DisplayReplayMessage( pText, false, true, NULL );
|
||||
}
|
||||
|
||||
void Replay_MsgBox( const wchar_t *pText )
|
||||
{
|
||||
g_pClient->DisplayReplayMessage( pText, false, true, NULL );
|
||||
}
|
||||
|
||||
const char *Replay_GetDownloadURLPath()
|
||||
{
|
||||
static char s_szDownloadURLPath[MAX_OSPATH];
|
||||
extern ConVar replay_fileserver_path; // NOTE: replicated
|
||||
|
||||
V_strcpy_safe( s_szDownloadURLPath, replay_fileserver_path.GetString() );
|
||||
V_StripTrailingSlash( s_szDownloadURLPath );
|
||||
V_FixSlashes( s_szDownloadURLPath, '/' );
|
||||
|
||||
// Get rid of starting slash
|
||||
if ( s_szDownloadURLPath[0] == '/' )
|
||||
return &s_szDownloadURLPath[1];
|
||||
|
||||
return s_szDownloadURLPath;
|
||||
}
|
||||
|
||||
const char *Replay_GetDownloadURL()
|
||||
{
|
||||
#if 0
|
||||
// Get the local host name
|
||||
char szHostname[MAX_OSPATH];
|
||||
if ( gethostname( szHostname, sizeof( szHostname ) ) == -1 )
|
||||
{
|
||||
Error( "Failed to send to Replay to client - couldn't get local IP.\n" );
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
|
||||
// Construct the URL based on replicated cvars
|
||||
static char s_szFileURL[ MAX_OSPATH ];
|
||||
extern ConVar replay_fileserver_protocol;
|
||||
extern ConVar replay_fileserver_host;
|
||||
extern ConVar replay_fileserver_port;
|
||||
V_snprintf(
|
||||
s_szFileURL, sizeof( s_szFileURL ),
|
||||
"%s://%s:%i/%s/",
|
||||
replay_fileserver_protocol.GetString(),
|
||||
replay_fileserver_host.GetString(),
|
||||
replay_fileserver_port.GetInt(),
|
||||
Replay_GetDownloadURLPath()
|
||||
);
|
||||
|
||||
// Cleanup
|
||||
V_FixDoubleSlashes( s_szFileURL + V_strlen("http://") );
|
||||
|
||||
return s_szFileURL;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
// Purpose: (client/server) Crack a URL into a base and a path
|
||||
// NOTE: the URL *must contain a port* !
|
||||
//
|
||||
// Example: http://some.base.url:8080/a/path/here.txt cracks into:
|
||||
// pBaseURL = "http://some.base.url:8080"
|
||||
// pURLPath = "/a/path/here.txt"
|
||||
//----------------------------------------------------------------------------------------
|
||||
void Replay_CrackURL( const char *pURL, char *pBaseURLOut, char *pURLPathOut )
|
||||
{
|
||||
const char *pColon;
|
||||
const char *pURLPath;
|
||||
|
||||
// Must at least have "http://"
|
||||
if ( V_strlen(pURL) < 6 )
|
||||
goto fail;
|
||||
|
||||
// Skip protocol ':' (eg http://)
|
||||
pColon = V_strstr( pURL, ":" );
|
||||
if ( !pColon )
|
||||
goto fail;
|
||||
|
||||
// Find next colon
|
||||
pColon = V_strstr( pColon + 1, ":" );
|
||||
if ( !pColon )
|
||||
goto fail;
|
||||
|
||||
// Copies "http[s]://<address>:<port>
|
||||
pURLPath = V_strstr( pColon, "/" );
|
||||
V_strncpy( pBaseURLOut, pURL, pURLPath - pURL + 1 );
|
||||
V_strcpy( pURLPathOut, pURLPath );
|
||||
|
||||
return;
|
||||
|
||||
fail:
|
||||
AssertMsg( 0, "Replay_CrackURL() was passed an invalid URL and has failed. This should never happen." );
|
||||
}
|
||||
|
||||
#ifndef DEDICATED
|
||||
void Replay_HudMsg( const char *pText, const char *pSound, bool bUrgent )
|
||||
{
|
||||
g_pClient->DisplayReplayMessage( pText, bUrgent, false, pSound );
|
||||
}
|
||||
#endif
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
Reference in New Issue
Block a user