|
|
//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "client.h"
#include "clockdriftmgr.h"
#include "demo.h"
#include "server.h"
#include "enginethreads.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.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( DEDICATED )
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 = GetBaseLocalClient().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 || ( demoplayer && demoplayer->IsPlayingBack() && clientTick > nTick ) ) { GetBaseLocalClient().SetClientTickCount( nTick - (g_ClientGlobalVariables.simTicksThisFrame - 1) ); if ( GetBaseLocalClient().GetClientTickCount() < GetBaseLocalClient().oldtickcount ) { GetBaseLocalClient().oldtickcount = GetBaseLocalClient().GetClientTickCount(); } memset( m_ClockOffsets, 0, sizeof( m_ClockOffsets ) ); } } else { // Used for testing..
GetBaseLocalClient().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 // DEDICATED
}
float CClockDriftMgr::AdjustFrameTime( float inputFrameTime ) { float flAdjustmentThisFrame = 0; float flAdjustmentPerSec = 0; if ( IsClockCorrectionEnabled() #if !defined( DEDICATED )
&& !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 ) { #ifndef DEDICATED
if ( !cl_clock_showdebuginfo.GetInt() ) return;
#ifndef DEDICATED
if ( IsClockCorrectionEnabled() ) { int high=-999, low=999; int exactDiff = GetBaseLocalClient().GetClientTickCount() - m_nServerTick; for ( int i=0; i < NUM_CLOCKDRIFT_SAMPLES; i++ ) { high = MAX( high, m_ClockOffsets[i] ); low = MIN( 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 #endif
{ Msg( "Clock drift disabled.\n" ); } #endif
}
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 DEDICATED
return false; #else
if ( sv.IsActive() && ( IsGameConsole() || NET_GetFakeLag() <= 0.0f ) ) { // Never want this in listen server. Has the result of slamming the server time
// as well in host.cpp, thus can never recover. Yields the server simulation running
// noticably faster until a full network update is triggered. [3/31/2009 tom]
return false; }
return cl_clock_correction.GetBool(); #endif
}
|