summaryrefslogtreecommitdiff
path: root/engine/clockdriftmgr.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/clockdriftmgr.cpp')
-rw-r--r--engine/clockdriftmgr.cpp240
1 files changed, 240 insertions, 0 deletions
diff --git a/engine/clockdriftmgr.cpp b/engine/clockdriftmgr.cpp
new file mode 100644
index 0000000..45d725c
--- /dev/null
+++ b/engine/clockdriftmgr.cpp
@@ -0,0 +1,240 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "client.h"
+#include "clockdriftmgr.h"
+#include "demo.h"
+#include "server.h"
+#include "enginethreads.h"
+
+
+ConVar cl_clock_correction( "cl_clock_correction", "1", FCVAR_CHEAT, "Enable/disable clock correction on the client." );
+
+ConVar cl_clockdrift_max_ms( "cl_clockdrift_max_ms", "150", FCVAR_CHEAT, "Maximum number of milliseconds the clock is allowed to drift before the client snaps its clock to the server's." );
+ConVar cl_clockdrift_max_ms_threadmode( "cl_clockdrift_max_ms_threadmode", "0", FCVAR_CHEAT, "Maximum number of milliseconds the clock is allowed to drift before the client snaps its clock to the server's." );
+
+ConVar cl_clock_showdebuginfo( "cl_clock_showdebuginfo", "0", FCVAR_CHEAT, "Show debugging info about the clock drift. ");
+
+ConVar cl_clock_correction_force_server_tick( "cl_clock_correction_force_server_tick", "999", FCVAR_CHEAT, "Force clock correction to match the server tick + this offset (-999 disables it)." );
+
+ConVar cl_clock_correction_adjustment_max_amount( "cl_clock_correction_adjustment_max_amount", "200", FCVAR_CHEAT,
+ "Sets the maximum number of milliseconds per second it is allowed to correct the client clock. "
+ "It will only correct this amount if the difference between the client and server clock is equal to or larger than cl_clock_correction_adjustment_max_offset." );
+
+ConVar cl_clock_correction_adjustment_min_offset( "cl_clock_correction_adjustment_min_offset", "10", FCVAR_CHEAT,
+ "If the clock offset is less than this amount (in milliseconds), then no clock correction is applied." );
+
+ConVar cl_clock_correction_adjustment_max_offset( "cl_clock_correction_adjustment_max_offset", "90", FCVAR_CHEAT,
+ "As the clock offset goes from cl_clock_correction_adjustment_min_offset to this value (in milliseconds), "
+ "it moves towards applying cl_clock_correction_adjustment_max_amount of adjustment. That way, the response "
+ "is small when the offset is small." );
+
+
+
+// Given the offset (in milliseconds) of the client clock from the server clock,
+// returns how much correction we'd like to apply per second (in seconds).
+static float GetClockAdjustmentAmount( float flCurDiffInMS )
+{
+ flCurDiffInMS = clamp( flCurDiffInMS, cl_clock_correction_adjustment_min_offset.GetFloat(), cl_clock_correction_adjustment_max_offset.GetFloat() );
+
+ float flReturnValue = RemapVal( flCurDiffInMS,
+ cl_clock_correction_adjustment_min_offset.GetFloat(), cl_clock_correction_adjustment_max_offset.GetFloat(),
+ 0, cl_clock_correction_adjustment_max_amount.GetFloat() / 1000.0f );
+
+ return flReturnValue;
+}
+
+
+// -------------------------------------------------------------------------------------------------- /
+// CClockDriftMgr implementation.
+// -------------------------------------------------------------------------------------------------- /
+
+CClockDriftMgr::CClockDriftMgr()
+{
+ Clear();
+}
+
+
+void CClockDriftMgr::Clear()
+{
+ m_nClientTick = 0;
+ m_nServerTick = 0;
+ m_iCurClockOffset = 0;
+ memset( m_ClockOffsets, 0, sizeof( m_ClockOffsets ) );
+}
+
+
+// when running in threaded host mode, the clock drifts by a predictable algorithm
+// because the client lags the server by one frame
+// so at each update from the network we have lastframeticks-1 pending ticks to execute
+// on the client. If the clock has drifted by exactly that amount, allow it to drift temporarily
+// NOTE: When the server gets paused the tick count is still incorrect for a frame
+// NOTE: It should be possible to fix this by applying pause before the tick is incremented
+// NOTE: or decrementing the client tick after receiving pause
+// NOTE: This is due to the fact that currently pause is applied at frame start on the server
+// NOTE: and frame end on the client
+void CClockDriftMgr::SetServerTick( int nTick )
+{
+#if !defined( SWDS )
+ m_nServerTick = nTick;
+
+ int nMaxDriftTicks = IsEngineThreaded() ?
+ TIME_TO_TICKS( (cl_clockdrift_max_ms_threadmode.GetFloat() / 1000.0) ) :
+ TIME_TO_TICKS( (cl_clockdrift_max_ms.GetFloat() / 1000.0) );
+
+ int clientTick = cl.GetClientTickCount() + g_ClientGlobalVariables.simTicksThisFrame - 1;
+ if ( cl_clock_correction_force_server_tick.GetInt() == 999 )
+ {
+ // If this is the first tick from the server, or if we get further than cl_clockdrift_max_ticks off, then
+ // use the old behavior and slam the server's tick into the client tick.
+ if ( !IsClockCorrectionEnabled() ||
+ clientTick == 0 ||
+ abs(nTick - clientTick) > nMaxDriftTicks
+ )
+ {
+ cl.SetClientTickCount( nTick - (g_ClientGlobalVariables.simTicksThisFrame - 1) );
+ if ( cl.GetClientTickCount() < cl.oldtickcount )
+ {
+ cl.oldtickcount = cl.GetClientTickCount();
+ }
+ memset( m_ClockOffsets, 0, sizeof( m_ClockOffsets ) );
+ }
+ }
+ else
+ {
+ // Used for testing..
+ cl.SetClientTickCount( nTick + cl_clock_correction_force_server_tick.GetInt() );
+ }
+
+ // adjust the clock offset by the clock with thread mode compensation
+ m_ClockOffsets[m_iCurClockOffset] = clientTick - m_nServerTick;
+ m_iCurClockOffset = (m_iCurClockOffset + 1) % NUM_CLOCKDRIFT_SAMPLES;
+#endif // SWDS
+}
+
+
+float CClockDriftMgr::AdjustFrameTime( float inputFrameTime )
+{
+ float flAdjustmentThisFrame = 0;
+ float flAdjustmentPerSec = 0;
+ if ( IsClockCorrectionEnabled()
+#if !defined( _XBOX ) && !defined( SWDS )
+ && !demoplayer->IsPlayingBack()
+#endif
+ )
+ {
+ // Get the clock difference in seconds.
+ float flCurDiffInSeconds = GetCurrentClockDifference() * host_state.interval_per_tick;
+ float flCurDiffInMS = flCurDiffInSeconds * 1000.0f;
+
+ // Is the server ahead or behind us?
+ if ( flCurDiffInMS > cl_clock_correction_adjustment_min_offset.GetFloat() )
+ {
+ flAdjustmentPerSec = -GetClockAdjustmentAmount( flCurDiffInMS );
+ flAdjustmentThisFrame = inputFrameTime * flAdjustmentPerSec;
+ flAdjustmentThisFrame = max( flAdjustmentThisFrame, -flCurDiffInSeconds );
+ }
+ else if ( flCurDiffInMS < -cl_clock_correction_adjustment_min_offset.GetFloat() )
+ {
+ flAdjustmentPerSec = GetClockAdjustmentAmount( -flCurDiffInMS );
+ flAdjustmentThisFrame = inputFrameTime * flAdjustmentPerSec;
+ flAdjustmentThisFrame = min( flAdjustmentThisFrame, -flCurDiffInSeconds );
+ }
+
+ if ( IsEngineThreaded() )
+ {
+ flAdjustmentThisFrame = -flCurDiffInSeconds;
+ }
+
+ AdjustAverageDifferenceBy( flAdjustmentThisFrame );
+ }
+
+ ShowDebugInfo( flAdjustmentPerSec );
+ return inputFrameTime + flAdjustmentThisFrame;
+}
+
+
+float CClockDriftMgr::GetCurrentClockDifference() const
+{
+ // Note: this could be optimized a little by updating it each time we add
+ // a sample (subtract the old value from the total and add the new one in).
+ float total = 0;
+ for ( int i=0; i < NUM_CLOCKDRIFT_SAMPLES; i++ )
+ total += m_ClockOffsets[i];
+
+ return total / NUM_CLOCKDRIFT_SAMPLES;
+}
+
+
+void CClockDriftMgr::ShowDebugInfo( float flAdjustment )
+{
+ if ( !cl_clock_showdebuginfo.GetInt() )
+ return;
+
+ if ( IsClockCorrectionEnabled() )
+ {
+ int high=-999, low=999;
+ int exactDiff = cl.GetClientTickCount() - m_nServerTick;
+ for ( int i=0; i < NUM_CLOCKDRIFT_SAMPLES; i++ )
+ {
+ high = max( (float)high, m_ClockOffsets[i] );
+ low = min( (float)low, m_ClockOffsets[i] );
+ }
+
+ Msg( "Clock drift: adjustment (per sec): %.2fms, avg: %.3f, lo: %d, hi: %d, ex: %d\n", flAdjustment*1000.0f, GetCurrentClockDifference(), low, high, exactDiff );
+ }
+ else
+ {
+ Msg( "Clock drift disabled.\n" );
+ }
+}
+
+
+void CClockDriftMgr::AdjustAverageDifferenceBy( float flAmountInSeconds )
+{
+ // Don't adjust the average if it's already tiny.
+ float c = GetCurrentClockDifference();
+ if ( c < 0.05f )
+ return;
+
+ float flAmountInTicks = flAmountInSeconds / host_state.interval_per_tick;
+ float factor = 1 + flAmountInTicks / c;
+
+ for ( int i=0; i < NUM_CLOCKDRIFT_SAMPLES; i++ )
+ m_ClockOffsets[i] *= factor;
+
+ Assert( fabs( GetCurrentClockDifference() - (c + flAmountInTicks) ) < 0.001f );
+}
+
+extern float NET_GetFakeLag();
+extern ConVar net_usesocketsforloopback;
+
+bool CClockDriftMgr::IsClockCorrectionEnabled()
+{
+#ifdef SWDS
+ return false;
+#else
+
+ bool bIsMultiplayer = NET_IsMultiplayer();
+ // Assume we always want it in multiplayer
+ bool bWantsClockDriftMgr = bIsMultiplayer;
+ // If we're a multiplayer listen server, we can back off of that if we have zero latency (faked or due to sockets)
+ if ( bIsMultiplayer )
+ {
+ bool bIsListenServer = sv.IsActive();
+ bool bLocalConnectionHasZeroLatency = ( NET_GetFakeLag() <= 0.0f ) && !net_usesocketsforloopback.GetBool();
+
+ if ( bIsListenServer && bLocalConnectionHasZeroLatency )
+ {
+ bWantsClockDriftMgr = false;
+ }
+ }
+
+ // Only in multi-threaded client/server OR in multi player, but don't use it if we're the listen server w/ no fake lag
+ return cl_clock_correction.GetInt() &&
+ ( IsEngineThreaded() || bWantsClockDriftMgr );
+#endif
+}