Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

240 lines
8.7 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "client.h"
  7. #include "clockdriftmgr.h"
  8. #include "demo.h"
  9. #include "server.h"
  10. #include "enginethreads.h"
  11. ConVar cl_clock_correction( "cl_clock_correction", "1", FCVAR_CHEAT, "Enable/disable clock correction on the client." );
  12. 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." );
  13. 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." );
  14. ConVar cl_clock_showdebuginfo( "cl_clock_showdebuginfo", "0", FCVAR_CHEAT, "Show debugging info about the clock drift. ");
  15. 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)." );
  16. ConVar cl_clock_correction_adjustment_max_amount( "cl_clock_correction_adjustment_max_amount", "200", FCVAR_CHEAT,
  17. "Sets the maximum number of milliseconds per second it is allowed to correct the client clock. "
  18. "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." );
  19. ConVar cl_clock_correction_adjustment_min_offset( "cl_clock_correction_adjustment_min_offset", "10", FCVAR_CHEAT,
  20. "If the clock offset is less than this amount (in milliseconds), then no clock correction is applied." );
  21. ConVar cl_clock_correction_adjustment_max_offset( "cl_clock_correction_adjustment_max_offset", "90", FCVAR_CHEAT,
  22. "As the clock offset goes from cl_clock_correction_adjustment_min_offset to this value (in milliseconds), "
  23. "it moves towards applying cl_clock_correction_adjustment_max_amount of adjustment. That way, the response "
  24. "is small when the offset is small." );
  25. // Given the offset (in milliseconds) of the client clock from the server clock,
  26. // returns how much correction we'd like to apply per second (in seconds).
  27. static float GetClockAdjustmentAmount( float flCurDiffInMS )
  28. {
  29. flCurDiffInMS = clamp( flCurDiffInMS, cl_clock_correction_adjustment_min_offset.GetFloat(), cl_clock_correction_adjustment_max_offset.GetFloat() );
  30. float flReturnValue = RemapVal( flCurDiffInMS,
  31. cl_clock_correction_adjustment_min_offset.GetFloat(), cl_clock_correction_adjustment_max_offset.GetFloat(),
  32. 0, cl_clock_correction_adjustment_max_amount.GetFloat() / 1000.0f );
  33. return flReturnValue;
  34. }
  35. // -------------------------------------------------------------------------------------------------- /
  36. // CClockDriftMgr implementation.
  37. // -------------------------------------------------------------------------------------------------- /
  38. CClockDriftMgr::CClockDriftMgr()
  39. {
  40. Clear();
  41. }
  42. void CClockDriftMgr::Clear()
  43. {
  44. m_nClientTick = 0;
  45. m_nServerTick = 0;
  46. m_iCurClockOffset = 0;
  47. memset( m_ClockOffsets, 0, sizeof( m_ClockOffsets ) );
  48. }
  49. // when running in threaded host mode, the clock drifts by a predictable algorithm
  50. // because the client lags the server by one frame
  51. // so at each update from the network we have lastframeticks-1 pending ticks to execute
  52. // on the client. If the clock has drifted by exactly that amount, allow it to drift temporarily
  53. // NOTE: When the server gets paused the tick count is still incorrect for a frame
  54. // NOTE: It should be possible to fix this by applying pause before the tick is incremented
  55. // NOTE: or decrementing the client tick after receiving pause
  56. // NOTE: This is due to the fact that currently pause is applied at frame start on the server
  57. // NOTE: and frame end on the client
  58. void CClockDriftMgr::SetServerTick( int nTick )
  59. {
  60. #if !defined( SWDS )
  61. m_nServerTick = nTick;
  62. int nMaxDriftTicks = IsEngineThreaded() ?
  63. TIME_TO_TICKS( (cl_clockdrift_max_ms_threadmode.GetFloat() / 1000.0) ) :
  64. TIME_TO_TICKS( (cl_clockdrift_max_ms.GetFloat() / 1000.0) );
  65. int clientTick = cl.GetClientTickCount() + g_ClientGlobalVariables.simTicksThisFrame - 1;
  66. if ( cl_clock_correction_force_server_tick.GetInt() == 999 )
  67. {
  68. // If this is the first tick from the server, or if we get further than cl_clockdrift_max_ticks off, then
  69. // use the old behavior and slam the server's tick into the client tick.
  70. if ( !IsClockCorrectionEnabled() ||
  71. clientTick == 0 ||
  72. abs(nTick - clientTick) > nMaxDriftTicks
  73. )
  74. {
  75. cl.SetClientTickCount( nTick - (g_ClientGlobalVariables.simTicksThisFrame - 1) );
  76. if ( cl.GetClientTickCount() < cl.oldtickcount )
  77. {
  78. cl.oldtickcount = cl.GetClientTickCount();
  79. }
  80. memset( m_ClockOffsets, 0, sizeof( m_ClockOffsets ) );
  81. }
  82. }
  83. else
  84. {
  85. // Used for testing..
  86. cl.SetClientTickCount( nTick + cl_clock_correction_force_server_tick.GetInt() );
  87. }
  88. // adjust the clock offset by the clock with thread mode compensation
  89. m_ClockOffsets[m_iCurClockOffset] = clientTick - m_nServerTick;
  90. m_iCurClockOffset = (m_iCurClockOffset + 1) % NUM_CLOCKDRIFT_SAMPLES;
  91. #endif // SWDS
  92. }
  93. float CClockDriftMgr::AdjustFrameTime( float inputFrameTime )
  94. {
  95. float flAdjustmentThisFrame = 0;
  96. float flAdjustmentPerSec = 0;
  97. if ( IsClockCorrectionEnabled()
  98. #if !defined( _XBOX ) && !defined( SWDS )
  99. && !demoplayer->IsPlayingBack()
  100. #endif
  101. )
  102. {
  103. // Get the clock difference in seconds.
  104. float flCurDiffInSeconds = GetCurrentClockDifference() * host_state.interval_per_tick;
  105. float flCurDiffInMS = flCurDiffInSeconds * 1000.0f;
  106. // Is the server ahead or behind us?
  107. if ( flCurDiffInMS > cl_clock_correction_adjustment_min_offset.GetFloat() )
  108. {
  109. flAdjustmentPerSec = -GetClockAdjustmentAmount( flCurDiffInMS );
  110. flAdjustmentThisFrame = inputFrameTime * flAdjustmentPerSec;
  111. flAdjustmentThisFrame = max( flAdjustmentThisFrame, -flCurDiffInSeconds );
  112. }
  113. else if ( flCurDiffInMS < -cl_clock_correction_adjustment_min_offset.GetFloat() )
  114. {
  115. flAdjustmentPerSec = GetClockAdjustmentAmount( -flCurDiffInMS );
  116. flAdjustmentThisFrame = inputFrameTime * flAdjustmentPerSec;
  117. flAdjustmentThisFrame = min( flAdjustmentThisFrame, -flCurDiffInSeconds );
  118. }
  119. if ( IsEngineThreaded() )
  120. {
  121. flAdjustmentThisFrame = -flCurDiffInSeconds;
  122. }
  123. AdjustAverageDifferenceBy( flAdjustmentThisFrame );
  124. }
  125. ShowDebugInfo( flAdjustmentPerSec );
  126. return inputFrameTime + flAdjustmentThisFrame;
  127. }
  128. float CClockDriftMgr::GetCurrentClockDifference() const
  129. {
  130. // Note: this could be optimized a little by updating it each time we add
  131. // a sample (subtract the old value from the total and add the new one in).
  132. float total = 0;
  133. for ( int i=0; i < NUM_CLOCKDRIFT_SAMPLES; i++ )
  134. total += m_ClockOffsets[i];
  135. return total / NUM_CLOCKDRIFT_SAMPLES;
  136. }
  137. void CClockDriftMgr::ShowDebugInfo( float flAdjustment )
  138. {
  139. if ( !cl_clock_showdebuginfo.GetInt() )
  140. return;
  141. if ( IsClockCorrectionEnabled() )
  142. {
  143. int high=-999, low=999;
  144. int exactDiff = cl.GetClientTickCount() - m_nServerTick;
  145. for ( int i=0; i < NUM_CLOCKDRIFT_SAMPLES; i++ )
  146. {
  147. high = max( (float)high, m_ClockOffsets[i] );
  148. low = min( (float)low, m_ClockOffsets[i] );
  149. }
  150. Msg( "Clock drift: adjustment (per sec): %.2fms, avg: %.3f, lo: %d, hi: %d, ex: %d\n", flAdjustment*1000.0f, GetCurrentClockDifference(), low, high, exactDiff );
  151. }
  152. else
  153. {
  154. Msg( "Clock drift disabled.\n" );
  155. }
  156. }
  157. void CClockDriftMgr::AdjustAverageDifferenceBy( float flAmountInSeconds )
  158. {
  159. // Don't adjust the average if it's already tiny.
  160. float c = GetCurrentClockDifference();
  161. if ( c < 0.05f )
  162. return;
  163. float flAmountInTicks = flAmountInSeconds / host_state.interval_per_tick;
  164. float factor = 1 + flAmountInTicks / c;
  165. for ( int i=0; i < NUM_CLOCKDRIFT_SAMPLES; i++ )
  166. m_ClockOffsets[i] *= factor;
  167. Assert( fabs( GetCurrentClockDifference() - (c + flAmountInTicks) ) < 0.001f );
  168. }
  169. extern float NET_GetFakeLag();
  170. extern ConVar net_usesocketsforloopback;
  171. bool CClockDriftMgr::IsClockCorrectionEnabled()
  172. {
  173. #ifdef SWDS
  174. return false;
  175. #else
  176. bool bIsMultiplayer = NET_IsMultiplayer();
  177. // Assume we always want it in multiplayer
  178. bool bWantsClockDriftMgr = bIsMultiplayer;
  179. // If we're a multiplayer listen server, we can back off of that if we have zero latency (faked or due to sockets)
  180. if ( bIsMultiplayer )
  181. {
  182. bool bIsListenServer = sv.IsActive();
  183. bool bLocalConnectionHasZeroLatency = ( NET_GetFakeLag() <= 0.0f ) && !net_usesocketsforloopback.GetBool();
  184. if ( bIsListenServer && bLocalConnectionHasZeroLatency )
  185. {
  186. bWantsClockDriftMgr = false;
  187. }
  188. }
  189. // 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
  190. return cl_clock_correction.GetInt() &&
  191. ( IsEngineThreaded() || bWantsClockDriftMgr );
  192. #endif
  193. }