summaryrefslogtreecommitdiff
path: root/replay/sv_sessionrecorder.cpp
blob: b52665e9ef0964cfcb7ed1d85cd0ecea4070ec98 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//=======================================================================================//

#include "sv_sessionrecorder.h"
#include "replay/replayutils.h"
#include "replay/shared_defs.h"
#include "baserecordingsessionblock.h"
#include "replaysystem.h"
#include "baserecordingsessionblockmanager.h"
#include "sv_recordingsessionmanager.h"
#include "sv_replaycontext.h"
#include "sv_sessionpublishmanager.h"
#include "sv_recordingsession.h"
#include "sv_recordingsessionblock.h"
#include "fmtstr.h"
#include "vprof.h"
#include "iserver.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#undef CreateEvent

//----------------------------------------------------------------------------------------

#define SERVER_REPLAY_INDEX_FILENAME			".replayindex"
#define SERVER_REPLAY_ERROR_LOST				"The server crashed before the replay could be finalized.  Replay lost."

//----------------------------------------------------------------------------------------

CSessionRecorder::CSessionRecorder()
:	m_bRecordingAborted( false ),
	m_nCurrentRecordingStartTick( -1 )
{
}

CSessionRecorder::~CSessionRecorder()
{
}

bool CSessionRecorder::Init()
{
	g_pFullFileSystem->CreateDirHierarchy( Replay_va( "%s%s", SV_GetBasePath(), SUBDIR_SESSIONS ) );
	return true;
}

void CSessionRecorder::AbortCurrentSessionRecording()
{
	StopRecording( true );

	CSessionPublishManager *pCurrentPublishManager = GetCurrentPublishManager();
	if ( !pCurrentPublishManager )
	{
		AssertMsg( 0, "Could not get current publish manager." );
		return;
	}

	pCurrentPublishManager->AbortPublish();

	m_bRecordingAborted = true;
}

void CSessionRecorder::SetCurrentRecordingStartTick( int nStartTick )
{
	m_nCurrentRecordingStartTick = nStartTick;
}

void CSessionRecorder::PublishAllSynchronous()
{
	FOR_EACH_LL( m_lstPublishManagers, i )
	{
		m_lstPublishManagers[ i ]->PublishAllSynchronous();
	}
}

void CSessionRecorder::StartRecording()
{
	m_bRecordingAborted = false;

	IServer *pServer = ReplayServerAsIServer();
	if ( !pServer || !pServer->IsActive() )
	{
		ConMsg( "ERROR: Replay not active.\n" );
		return;
	}

	// We only care about local fileserver path in the case that we aren't offloading files to an external sfileserver
	const char *pWritePath = g_pServerReplayContext->GetLocalFileServerPath();
	if ( ( !pWritePath || !pWritePath[0] ) )
	{
		ConMsg( "\n*\n* ERROR: Failed to begin record: make sure \"replay_local_fileserver_path\" refers to a valid path!\n** replay_local_fileserver_path is currently set to: \"%s\"\n*\n\n", pWritePath );
		return;
	}

	IReplayServer *pReplayServer = ReplayServer();
	if ( pReplayServer->IsRecording() )
	{
		ConMsg( "ERROR: Replay already recording.\n" );
		return;
	}

	// Tell the replay server to begin recording
	pReplayServer->StartRecording();

	// Notify session manager
	CBaseRecordingSession *pSession = SV_GetRecordingSessionManager()->OnSessionStart( m_nCurrentRecordingStartTick, NULL );

	// Create a new publish manager and add it.  The dump interval and any additional setup is done there.
	CreateAndAddNewPublishManager( static_cast< CServerRecordingSession * >( pSession ) );
}

void CSessionRecorder::CreateAndAddNewPublishManager( CServerRecordingSession *pSession )
{
	CSessionPublishManager *pNewPublishManager = new CSessionPublishManager( pSession );

	// Let the publish manager know that it is the 'current' publish manager.
	pNewPublishManager->OnStartRecording();

	// Add to the head of the list, since the desired convention is for the list to be
	// sorted from newest to oldest.
	m_lstPublishManagers.AddToHead( pNewPublishManager );
}

float CSessionRecorder::GetNextThinkTime() const
{
	return 0.0f;
}

void CSessionRecorder::Think()
{
	CBaseThinker::Think();

	VPROF_BUDGET( "CSessionRecorder::Think", VPROF_BUDGETGROUP_REPLAY );

	// This gets called even if replay is disabled.  This is intentional.
	PublishThink();
}

CSessionPublishManager *CSessionRecorder::GetCurrentPublishManager() const
{
	if ( !m_lstPublishManagers.Count() )
		return NULL;

	return m_lstPublishManagers[ m_lstPublishManagers.Head() ];
}

void CSessionRecorder::PublishThink()
{
	UpdateSessionLocks();
}

void CSessionRecorder::UpdateSessionLocks()
{
	for ( int i = m_lstPublishManagers.Head(); i != m_lstPublishManagers.InvalidIndex(); )
	{
		CSessionPublishManager *pCurManager = m_lstPublishManagers[ i ];

		// Cache off 'next' in case we delete the current object
		const int itNext = m_lstPublishManagers.Next( i );

		if ( pCurManager->IsDone() )
		{
#ifdef _DEBUG
			pCurManager->Validate();
#endif

			// We can unlock the associated session now.
			pCurManager->UnlockSession();

			// Remove and delete it.
			m_lstPublishManagers.Remove( i );
			delete pCurManager;

			IF_REPLAY_DBG( Warning( "\n---\n*\n* All publishing done for session.  %i still publishing.\n*\n---\n", m_lstPublishManagers.Count() ) );
		}
		else
		{
			pCurManager->Think();
		}

		i = itNext;
	}
}

void CSessionRecorder::StopRecording( bool bAborting )
{
#if !defined( DEDICATED )
	if ( g_pEngineClient->IsPlayingReplayDemo() )
		return;
#endif
	if ( !ReplayServer() )
		return;

	DBG( "StopRecording()\n" );

	CServerRecordingSession *pSession = SV_GetRecordingSessionInProgress();
	if ( pSession )
	{
		// Mark the session as not recording
		pSession->OnStopRecording();

		// Get the current publish manager and notify it that recording has stopped.
		CSessionPublishManager *pManager = GetCurrentPublishManager();
		if ( pManager )
		{
			pManager->OnStopRecord( bAborting );
		}

		// Notify session manager - the session will be flagged for unload or deletion, but
		// will not actually be free'd until it is "unlocked" by the publish manager.
		SV_GetRecordingSessionManager()->OnSessionEnd();
	}

	// Stop recording
	ReplayServer()->StopRecording();

	// Clear replay_recording
	extern ConVar replay_recording;
	replay_recording.SetValue( 0 );
}

//----------------------------------------------------------------------------------------