Common_network.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. /*
  2. ===========================================================================
  3. Doom 3 BFG Edition GPL Source Code
  4. Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
  6. Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
  17. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
  18. ===========================================================================
  19. */
  20. #include "../idlib/precompiled.h"
  21. #pragma hdrstop
  22. #include "Common_local.h"
  23. idCVar net_clientMaxPrediction( "net_clientMaxPrediction", "5000", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "maximum number of milliseconds a client can predict ahead of server." );
  24. idCVar net_snapRate( "net_snapRate", "100", CVAR_SYSTEM | CVAR_INTEGER, "How many milliseconds between sending snapshots" );
  25. idCVar net_ucmdRate( "net_ucmdRate", "40", CVAR_SYSTEM | CVAR_INTEGER, "How many milliseconds between sending usercmds" );
  26. idCVar net_debug_snapShotTime( "net_debug_snapShotTime", "0", CVAR_BOOL | CVAR_ARCHIVE, "" );
  27. idCVar com_forceLatestSnap( "com_forceLatestSnap", "0", CVAR_BOOL, "" );
  28. // Enables effective snap rate: dynamically adjust the client snap rate based on:
  29. // -client FPS
  30. // -server FPS (interpolated game time received / interval it was received over)
  31. // -local buffered time (leave a cushion to absorb spikes, slow down when infront of it, speed up when behind it) ie: net_minBufferedSnapPCT_Static
  32. idCVar net_effectiveSnapRateEnable( "net_effectiveSnapRateEnable", "1", CVAR_BOOL, "Dynamically adjust client snaprate");
  33. idCVar net_effectiveSnapRateDebug( "net_effectiveSnapRateDebug", "0", CVAR_BOOL, "Debug");
  34. // Min buffered snapshot time to keep as a percentage of the effective snaprate
  35. // -ie we want to keep 50% of the amount of time difference between last two snaps.
  36. // -we need to scale this because we may get throttled at the snaprate may change
  37. // -Acts as a buffer to absorb spikes
  38. idCVar net_minBufferedSnapPCT_Static( "net_minBufferedSnapPCT_Static", "1.0", CVAR_FLOAT, "Min amount of snapshot buffer time we want need to buffer");
  39. idCVar net_maxBufferedSnapMS( "net_maxBufferedSnapMS", "336", CVAR_INTEGER, "Max time to allow for interpolation cushion");
  40. idCVar net_minBufferedSnapWinPCT_Static( "net_minBufferedSnapWinPCT_Static", "1.0", CVAR_FLOAT, "Min amount of snapshot buffer time we want need to buffer");
  41. // Factor at which we catch speed up interpolation if we fall behind our optimal interpolation window
  42. // -This is a static factor. We may experiment with a dynamic one that would be faster the farther you are from the ideal window
  43. idCVar net_interpolationCatchupRate( "net_interpolationCatchupRate", "1.3", CVAR_FLOAT, "Scale interpolationg rate when we fall behind");
  44. idCVar net_interpolationFallbackRate( "net_interpolationFallbackRate", "0.95", CVAR_FLOAT, "Scale interpolationg rate when we fall behind");
  45. idCVar net_interpolationBaseRate( "net_interpolationBaseRate", "1.0", CVAR_FLOAT, "Scale interpolationg rate when we fall behind");
  46. // Enabled a dynamic ideal snap buffer window: we will scale the distance and size
  47. idCVar net_optimalDynamic( "net_optimalDynamic", "1", CVAR_BOOL, "How fast to add to our optimal time buffer when we are playing snapshots faster than server is feeding them to us");
  48. // These values are used instead if net_optimalDynamic is 0 (don't scale by actual snap rate/interval)
  49. idCVar net_optimalSnapWindow( "net_optimalSnapWindow", "112", CVAR_FLOAT, "");
  50. idCVar net_optimalSnapTime( "net_optimalSnapTime", "112", CVAR_FLOAT, "How fast to add to our optimal time buffer when we are playing snapshots faster than server is feeding them to us");
  51. // this is at what percentage of being ahead of the interpolation buffer that we start slowing down (we ramp down from 1.0 to 0.0 starting here)
  52. // this is a percentage of the total cushion time.
  53. idCVar net_interpolationSlowdownStart( "net_interpolationSlowdownStart", "0.5", CVAR_FLOAT, "Scale interpolation rate when we fall behind");
  54. // Extrapolation is now disabled
  55. idCVar net_maxExtrapolationInMS( "net_maxExtrapolationInMS", "0", CVAR_INTEGER, "Max time in MS that extrapolation is allowed to occur.");
  56. static const int SNAP_USERCMDS = 8192;
  57. /*
  58. ===============
  59. idCommonLocal::IsMultiplayer
  60. ===============
  61. */
  62. bool idCommonLocal::IsMultiplayer() {
  63. idLobbyBase & lobby = session->GetPartyLobbyBase();
  64. return ( ( ( lobby.GetMatchParms().matchFlags & MATCH_ONLINE ) != 0 ) && ( session->GetState() > idSession::IDLE ) );
  65. }
  66. /*
  67. ===============
  68. idCommonLocal::IsServer
  69. ===============
  70. */
  71. bool idCommonLocal::IsServer() {
  72. return IsMultiplayer() && session->GetActingGameStateLobbyBase().IsHost();
  73. }
  74. /*
  75. ===============
  76. idCommonLocal::IsClient
  77. ===============
  78. */
  79. bool idCommonLocal::IsClient() {
  80. return IsMultiplayer() && session->GetActingGameStateLobbyBase().IsPeer();
  81. }
  82. /*
  83. ===============
  84. idCommonLocal::SendSnapshots
  85. ===============
  86. */
  87. int idCommonLocal::GetSnapRate() {
  88. return net_snapRate.GetInteger();
  89. }
  90. /*
  91. ===============
  92. idCommonLocal::SendSnapshots
  93. ===============
  94. */
  95. void idCommonLocal::SendSnapshots() {
  96. if ( !mapSpawned ) {
  97. return;
  98. }
  99. int currentTime = Sys_Milliseconds();
  100. if ( currentTime < nextSnapshotSendTime ) {
  101. return;
  102. }
  103. idLobbyBase & lobby = session->GetActingGameStateLobbyBase();
  104. if ( !lobby.IsHost() ) {
  105. return;
  106. }
  107. if ( !lobby.HasActivePeers() ) {
  108. return;
  109. }
  110. idSnapShot ss;
  111. game->ServerWriteSnapshot( ss );
  112. session->SendSnapshot( ss );
  113. nextSnapshotSendTime = MSEC_ALIGN_TO_FRAME( currentTime + net_snapRate.GetInteger() );
  114. }
  115. /*
  116. ===============
  117. idCommonLocal::NetReceiveSnapshot
  118. ===============
  119. */
  120. void idCommonLocal::NetReceiveSnapshot( class idSnapShot & ss ) {
  121. ss.SetRecvTime( Sys_Milliseconds() );
  122. // If we are about to overwrite the oldest snap, then force a read, which will cause a pop on screen, but we have to do this.
  123. if ( writeSnapshotIndex - readSnapshotIndex >= RECEIVE_SNAPSHOT_BUFFER_SIZE ) {
  124. idLib::Printf( "Overwritting oldest snapshot %d with new snapshot %d\n", readSnapshotIndex, writeSnapshotIndex );
  125. assert( writeSnapshotIndex % RECEIVE_SNAPSHOT_BUFFER_SIZE == readSnapshotIndex % RECEIVE_SNAPSHOT_BUFFER_SIZE );
  126. ProcessNextSnapshot();
  127. }
  128. receivedSnaps[ writeSnapshotIndex % RECEIVE_SNAPSHOT_BUFFER_SIZE ] = ss;
  129. writeSnapshotIndex++;
  130. // Force read the very first 2 snapshots
  131. if ( readSnapshotIndex < 2 ) {
  132. ProcessNextSnapshot();
  133. }
  134. }
  135. /*
  136. ===============
  137. idCommonLocal::SendUsercmd
  138. ===============
  139. */
  140. void idCommonLocal::SendUsercmds( int localClientNum ) {
  141. if ( !mapSpawned ) {
  142. return;
  143. }
  144. int currentTime = Sys_Milliseconds();
  145. if ( currentTime < nextUsercmdSendTime ) {
  146. return;
  147. }
  148. idLobbyBase & lobby = session->GetActingGameStateLobbyBase();
  149. if ( lobby.IsHost() ) {
  150. return;
  151. }
  152. // We always send the last NUM_USERCMD_SEND usercmds
  153. // Which may result in duplicate usercmds being sent in the case of a low net_ucmdRate
  154. // But the LZW compressor means the extra usercmds are not large and the redundancy can smooth packet loss
  155. byte buffer[idPacketProcessor::MAX_FINAL_PACKET_SIZE];
  156. idBitMsg msg( buffer, sizeof( buffer ) );
  157. idSerializer ser( msg, true );
  158. usercmd_t empty;
  159. usercmd_t * last = &empty;
  160. usercmd_t * cmdBuffer[NUM_USERCMD_SEND];
  161. const int numCmds = userCmdMgr.GetPlayerCmds( localClientNum, cmdBuffer, NUM_USERCMD_SEND );
  162. msg.WriteByte( numCmds );
  163. for ( int i = 0; i < numCmds; i++ ) {
  164. cmdBuffer[i]->Serialize( ser, *last );
  165. last = cmdBuffer[i];
  166. }
  167. session->SendUsercmds( msg );
  168. nextUsercmdSendTime = MSEC_ALIGN_TO_FRAME( currentTime + net_ucmdRate.GetInteger() );
  169. }
  170. /*
  171. ===============
  172. idCommonLocal::NetReceiveUsercmds
  173. ===============
  174. */
  175. void idCommonLocal::NetReceiveUsercmds( int peer, idBitMsg & msg ) {
  176. int clientNum = Game()->MapPeerToClient( peer );
  177. if ( clientNum == -1 ) {
  178. idLib::Warning( "NetReceiveUsercmds: Could not find client for peer %d", peer );
  179. return;
  180. }
  181. NetReadUsercmds( clientNum, msg );
  182. }
  183. /*
  184. ===============
  185. idCommonLocal::NetReceiveReliable
  186. ===============
  187. */
  188. void idCommonLocal::NetReceiveReliable( int peer, int type, idBitMsg & msg ) {
  189. int clientNum = Game()->MapPeerToClient( peer );
  190. // Only servers care about the client num. Band-aid for problems related to the host's peerIndex being -1 on clients.
  191. if ( common->IsServer() && clientNum == -1 ) {
  192. idLib::Warning( "NetReceiveReliable: Could not find client for peer %d", peer );
  193. return;
  194. }
  195. const byte * msgData = msg.GetReadData() + msg.GetReadCount();
  196. int msgSize = msg.GetRemainingData();
  197. reliableMsg_t & reliable = reliableQueue.Alloc();
  198. reliable.client = clientNum;
  199. reliable.type = type;
  200. reliable.dataSize = msgSize;
  201. reliable.data = (byte *)Mem_Alloc( msgSize, TAG_NETWORKING );
  202. memcpy( reliable.data, msgData, msgSize );
  203. }
  204. /*
  205. ========================
  206. idCommonLocal::ProcessSnapshot
  207. ========================
  208. */
  209. void idCommonLocal::ProcessSnapshot( idSnapShot & ss ) {
  210. int time = Sys_Milliseconds();
  211. snapTime = time;
  212. snapPrevious = snapCurrent;
  213. snapCurrent.serverTime = ss.GetTime();
  214. snapRate = snapCurrent.serverTime - snapPrevious.serverTime;
  215. static int lastReceivedLocalTime = 0;
  216. int timeSinceLastSnap = ( time - lastReceivedLocalTime );
  217. if ( net_debug_snapShotTime.GetBool() ) {
  218. idLib::Printf( "^2ProcessSnapshot. delta serverTime: %d delta localTime: %d \n", ( snapCurrent.serverTime-snapPrevious.serverTime ), timeSinceLastSnap );
  219. }
  220. lastReceivedLocalTime = time;
  221. /* JAF ?
  222. for ( int i = 0; i < MAX_PLAYERS; i++ ) {
  223. idBitMsg msg;
  224. if ( ss.GetObjectMsgByID( idSession::SS_PLAYER + i, msg ) ) {
  225. if ( msg.GetSize() == 0 ) {
  226. snapCurrent.players[ i ].valid = false;
  227. continue;
  228. }
  229. idSerializer ser( msg, false );
  230. SerializePlayer( ser, snapCurrent.players[ i ] );
  231. snapCurrent.players[ i ].valid = true;
  232. extern idCVar com_drawSnapshots;
  233. if ( com_drawSnapshots.GetInteger() == 3 ) {
  234. console->AddSnapObject( "players", msg.GetSize(), ss.CompareObject( &oldss, idSession::SS_PLAYER + i ) );
  235. }
  236. }
  237. }
  238. */
  239. // Read usercmds from other players
  240. for ( int p = 0; p < MAX_PLAYERS; p++ ) {
  241. if ( p == game->GetLocalClientNum() ) {
  242. continue;
  243. }
  244. idBitMsg msg;
  245. if ( ss.GetObjectMsgByID( SNAP_USERCMDS + p, msg ) ) {
  246. NetReadUsercmds( p, msg );
  247. }
  248. }
  249. // Set server game time here so that it accurately reflects the time when this frame was saved out, in case any serialize function needs it.
  250. int oldTime = Game()->GetServerGameTimeMs();
  251. Game()->SetServerGameTimeMs( snapCurrent.serverTime );
  252. Game()->ClientReadSnapshot( ss ); //, &oldss );
  253. // Restore server game time
  254. Game()->SetServerGameTimeMs( oldTime );
  255. snapTimeDelta = ss.GetRecvTime() - oldss.GetRecvTime();
  256. oldss = ss;
  257. }
  258. /*
  259. ========================
  260. idCommonLocal::NetReadUsercmds
  261. ========================
  262. */
  263. void idCommonLocal::NetReadUsercmds( int clientNum, idBitMsg & msg ) {
  264. if ( clientNum == -1 ) {
  265. idLib::Warning( "NetReadUsercmds: Trying to read commands from invalid clientNum %d", clientNum );
  266. return;
  267. }
  268. // TODO: This shouldn't actually happen. Figure out why it does.
  269. // Seen on clients when another client leaves a match.
  270. if ( msg.GetReadData() == NULL ) {
  271. return;
  272. }
  273. idSerializer ser( msg, false );
  274. usercmd_t fakeCmd;
  275. usercmd_t * base = &fakeCmd;
  276. usercmd_t lastCmd;
  277. bool gotNewCmd = false;
  278. idStaticList< usercmd_t, NUM_USERCMD_RELAY > newCmdBuffer;
  279. usercmd_t baseCmd = userCmdMgr.NewestUserCmdForPlayer( clientNum );
  280. int curMilliseconds = baseCmd.clientGameMilliseconds;
  281. const int numCmds = msg.ReadByte();
  282. for ( int i = 0; i < numCmds; i++ ) {
  283. usercmd_t newCmd;
  284. newCmd.Serialize( ser, *base );
  285. lastCmd = newCmd;
  286. base = &lastCmd;
  287. int newMilliseconds = newCmd.clientGameMilliseconds;
  288. if ( newMilliseconds > curMilliseconds ) {
  289. if ( verify( i < NUM_USERCMD_RELAY ) ) {
  290. newCmdBuffer.Append( newCmd );
  291. gotNewCmd = true;
  292. curMilliseconds = newMilliseconds;
  293. }
  294. }
  295. }
  296. // Push the commands into the buffer.
  297. for ( int i = 0; i < newCmdBuffer.Num(); ++i ) {
  298. userCmdMgr.PutUserCmdForPlayer( clientNum, newCmdBuffer[i] );
  299. }
  300. }
  301. /*
  302. ========================
  303. idCommonLocal::ProcessNextSnapshot
  304. ========================
  305. */
  306. void idCommonLocal::ProcessNextSnapshot() {
  307. if ( readSnapshotIndex == writeSnapshotIndex ) {
  308. idLib::Printf("No snapshots to process.\n");
  309. return; // No snaps to process
  310. }
  311. ProcessSnapshot( receivedSnaps[ readSnapshotIndex % RECEIVE_SNAPSHOT_BUFFER_SIZE ] );
  312. readSnapshotIndex++;
  313. }
  314. /*
  315. ========================
  316. idCommonLocal::CalcSnapTimeBuffered
  317. Return the amount of game time left of buffered snapshots
  318. totalBufferedTime - total amount of snapshot time (includng what we've already past in current interpolate)
  319. totalRecvTime - total real time (sys_milliseconds) all of totalBufferedTime was received over
  320. ========================
  321. */
  322. int idCommonLocal::CalcSnapTimeBuffered( int & totalBufferedTime, int & totalRecvTime ) {
  323. totalBufferedTime = snapRate;
  324. totalRecvTime = snapTimeDelta;
  325. // oldSS = last ss we deserialized
  326. int lastBuffTime = oldss.GetTime();
  327. int lastRecvTime = oldss.GetRecvTime();
  328. // receivedSnaps[readSnapshotIndex % RECEIVE_SNAPSHOT_BUFFER_SIZE] = next buffered snapshot we haven't processed yet (might not exist)
  329. for ( int i = readSnapshotIndex; i < writeSnapshotIndex; i++ ) {
  330. int buffTime = receivedSnaps[i % RECEIVE_SNAPSHOT_BUFFER_SIZE].GetTime();
  331. int recvTime = receivedSnaps[i % RECEIVE_SNAPSHOT_BUFFER_SIZE].GetRecvTime();
  332. totalBufferedTime += buffTime - lastBuffTime;
  333. totalRecvTime += recvTime - lastRecvTime;
  334. lastRecvTime = recvTime;
  335. lastBuffTime = buffTime;
  336. }
  337. totalRecvTime = Max( 1, totalRecvTime );
  338. totalRecvTime = static_cast<float>( initialBaseTicksPerSec ) * static_cast<float>( totalRecvTime / 1000.0f ); // convert realMS to gameMS
  339. // remove time we've already interpolated over
  340. int timeLeft = totalBufferedTime - Min< int >( snapRate, snapCurrentTime );
  341. //idLib::Printf( "CalcSnapTimeBuffered. timeLeft: %d totalRecvTime: %d, totalTimeBuffered: %d\n", timeLeft, totalRecvTime, totalBufferedTime );
  342. return timeLeft;
  343. }
  344. /*
  345. ========================
  346. idCommonLocal::InterpolateSnapshot
  347. ========================
  348. */
  349. void idCommonLocal::InterpolateSnapshot( netTimes_t & prev, netTimes_t & next, float fraction, bool predict ) {
  350. int serverTime = Lerp( prev.serverTime, next.serverTime, fraction );
  351. Game()->SetServerGameTimeMs( serverTime ); // Set the global server time to the interpolated time of the server
  352. Game()->SetInterpolation( fraction, serverTime, prev.serverTime, next.serverTime );
  353. //Game()->RunFrame( &userCmdMgr, &ret, true );
  354. }
  355. /*
  356. ========================
  357. idCommonLocal::RunNetworkSnapshotFrame
  358. ========================
  359. */
  360. void idCommonLocal::RunNetworkSnapshotFrame() {
  361. // Process any reliable messages we've received
  362. for ( int i = 0; i < reliableQueue.Num(); i++ ) {
  363. game->ProcessReliableMessage( reliableQueue[i].client, reliableQueue[i].type, idBitMsg( (const byte *)reliableQueue[i].data, reliableQueue[i].dataSize ) );
  364. Mem_Free( reliableQueue[i].data );
  365. }
  366. reliableQueue.Clear();
  367. // abuse the game timing to time presentable thinking on clients
  368. time_gameFrame = Sys_Microseconds();
  369. time_maxGameFrame = 0;
  370. count_numGameFrames = 0;
  371. if ( snapPrevious.serverTime >= 0 ) {
  372. int msec_interval = 1 + idMath::Ftoi( (float)initialBaseTicksPerSec );
  373. static int clientTimeResidual = 0;
  374. static int lastTime = Sys_Milliseconds();
  375. int currentTime = Sys_Milliseconds();
  376. int deltaFrameTime = idMath::ClampInt( 1, 33, currentTime - lastTime );
  377. clientTimeResidual += idMath::ClampInt( 0, 50, currentTime - lastTime );
  378. lastTime = currentTime;
  379. extern idCVar com_fixedTic;
  380. if ( com_fixedTic.GetBool() ) {
  381. clientTimeResidual = 0;
  382. }
  383. do {
  384. // If we are extrapolating and have fresher snapshots, then use the freshest one
  385. while ( ( snapCurrentTime >= snapRate || com_forceLatestSnap.GetBool() ) && readSnapshotIndex < writeSnapshotIndex ) {
  386. snapCurrentTime -= snapRate;
  387. ProcessNextSnapshot();
  388. }
  389. // this only matters when running < 60 fps
  390. // JAF Game()->GetRenderWorld()->UpdateDeferredPositions();
  391. // Clamp the current time so that it doesn't fall outside of our extrapolation bounds
  392. snapCurrentTime = idMath::ClampInt( 0, snapRate + Min( (int)snapRate, (int)net_maxExtrapolationInMS.GetInteger() ), snapCurrentTime );
  393. if ( snapRate <= 0 ) {
  394. idLib::Warning("snapRate <= 0. Resetting to 100");
  395. snapRate = 100;
  396. }
  397. float fraction = (float)snapCurrentTime / (float)snapRate;
  398. if ( !IsValid( fraction ) ) {
  399. idLib::Warning("Interpolation Fraction invalid: snapCurrentTime %d / snapRate %d", (int)snapCurrentTime, (int)snapRate );
  400. fraction = 0.0f;
  401. }
  402. InterpolateSnapshot( snapPrevious, snapCurrent, fraction, true );
  403. // Default to a snap scale of 1
  404. float snapRateScale = net_interpolationBaseRate.GetFloat();
  405. snapTimeBuffered = CalcSnapTimeBuffered( totalBufferedTime, totalRecvTime );
  406. effectiveSnapRate = static_cast< float > ( totalBufferedTime ) / static_cast< float > ( totalRecvTime );
  407. if ( net_minBufferedSnapPCT_Static.GetFloat() > 0.0f ) {
  408. optimalPCTBuffer = session->GetTitleStorageFloat( "net_minBufferedSnapPCT_Static", net_minBufferedSnapPCT_Static.GetFloat() );
  409. }
  410. // Calculate optimal amount of buffered time we want
  411. if ( net_optimalDynamic.GetBool() ) {
  412. optimalTimeBuffered = idMath::ClampInt( 0, net_maxBufferedSnapMS.GetInteger(), snapRate * optimalPCTBuffer );
  413. optimalTimeBufferedWindow = snapRate * net_minBufferedSnapWinPCT_Static.GetFloat();
  414. } else {
  415. optimalTimeBuffered = net_optimalSnapTime.GetFloat();
  416. optimalTimeBufferedWindow = net_optimalSnapWindow.GetFloat();
  417. }
  418. // Scale snapRate based on where we are in the buffer
  419. if ( snapTimeBuffered <= optimalTimeBuffered ) {
  420. if ( snapTimeBuffered <= idMath::FLT_SMALLEST_NON_DENORMAL ) {
  421. snapRateScale = 0;
  422. } else {
  423. snapRateScale = net_interpolationFallbackRate.GetFloat();
  424. // When we interpolate past our cushion of buffered snapshot, we want to slow smoothly slow the
  425. // rate of interpolation. frac will go from 1.0 to 0.0 (if snapshots stop coming in).
  426. float startSlowdown = ( net_interpolationSlowdownStart.GetFloat() * optimalTimeBuffered );
  427. if ( startSlowdown > 0 && snapTimeBuffered < startSlowdown ) {
  428. float frac = idMath::ClampFloat( 0.0f, 1.0f, snapTimeBuffered / startSlowdown );
  429. if ( !IsValid( frac ) ) {
  430. frac = 0.0f;
  431. }
  432. snapRateScale = Square( frac ) * snapRateScale;
  433. if ( !IsValid( snapRateScale ) ) {
  434. snapRateScale = 0.0f;
  435. }
  436. }
  437. }
  438. } else if ( snapTimeBuffered > optimalTimeBuffered + optimalTimeBufferedWindow ) {
  439. // Go faster
  440. snapRateScale = net_interpolationCatchupRate.GetFloat();
  441. }
  442. float delta_interpolate = (float)initialBaseTicksPerSec * snapRateScale;
  443. if ( net_effectiveSnapRateEnable.GetBool() ) {
  444. float deltaFrameGameMS = static_cast<float>( initialBaseTicksPerSec ) * static_cast<float>( deltaFrameTime / 1000.0f );
  445. delta_interpolate = ( deltaFrameGameMS * snapRateScale * effectiveSnapRate ) + snapCurrentResidual;
  446. if ( !IsValid( delta_interpolate ) ) {
  447. delta_interpolate = 0.0f;
  448. }
  449. snapCurrentResidual = idMath::Frac( delta_interpolate ); // fixme: snapCurrentTime should just be a float, but would require changes in d4 too
  450. if ( !IsValid( snapCurrentResidual ) ) {
  451. snapCurrentResidual = 0.0f;
  452. }
  453. if ( net_effectiveSnapRateDebug.GetBool() ) {
  454. idLib::Printf("%d/%.2f snapRateScale: %.2f effectiveSR: %.2f d.interp: %.2f snapTimeBuffered: %.2f res: %.2f\n", deltaFrameTime, deltaFrameGameMS, snapRateScale, effectiveSnapRate, delta_interpolate, snapTimeBuffered, snapCurrentResidual );
  455. }
  456. }
  457. assert( IsValid( delta_interpolate ) );
  458. int interpolate_interval = idMath::Ftoi( delta_interpolate );
  459. snapCurrentTime += interpolate_interval; // advance interpolation time by the scaled interpolate_interval
  460. clientTimeResidual -= msec_interval; // advance local client residual time (fixed step)
  461. } while ( clientTimeResidual >= msec_interval );
  462. if ( clientTimeResidual < 0 ) {
  463. clientTimeResidual = 0;
  464. }
  465. }
  466. time_gameFrame = Sys_Microseconds() - time_gameFrame;
  467. }
  468. /*
  469. ========================
  470. idCommonLocal::ExecuteReliableMessages
  471. ========================
  472. */
  473. void idCommonLocal::ExecuteReliableMessages() {
  474. // Process any reliable messages we've received
  475. for ( int i = 0; i < reliableQueue.Num(); i++ ) {
  476. reliableMsg_t & reliable = reliableQueue[i];
  477. game->ProcessReliableMessage( reliable.client, reliable.type, idBitMsg( (const byte *)reliable.data, reliable.dataSize ) );
  478. Mem_Free( reliable.data );
  479. }
  480. reliableQueue.Clear();
  481. }
  482. /*
  483. ========================
  484. idCommonLocal::ResetNetworkingState
  485. ========================
  486. */
  487. void idCommonLocal::ResetNetworkingState() {
  488. snapTime = 0;
  489. snapTimeWrite = 0;
  490. snapCurrentTime = 0;
  491. snapCurrentResidual = 0.0f;
  492. snapTimeBuffered = 0.0f;
  493. effectiveSnapRate = 0.0f;
  494. totalBufferedTime = 0;
  495. totalRecvTime = 0;
  496. readSnapshotIndex = 0;
  497. writeSnapshotIndex = 0;
  498. snapRate = 100000;
  499. optimalTimeBuffered = 0.0f;
  500. optimalPCTBuffer = 0.5f;
  501. optimalTimeBufferedWindow = 0.0;
  502. // Clear snapshot queue
  503. for ( int i = 0; i < RECEIVE_SNAPSHOT_BUFFER_SIZE; i++ ) {
  504. receivedSnaps[i].Clear();
  505. }
  506. userCmdMgr.SetDefaults();
  507. snapCurrent.localTime = -1;
  508. snapPrevious.localTime = -1;
  509. snapCurrent.serverTime = -1;
  510. snapPrevious.serverTime = -1;
  511. // Make sure our current snap state is cleared so state from last game doesn't carry over into new game
  512. oldss.Clear();
  513. gameFrame = 0;
  514. clientPrediction = 0;
  515. nextUsercmdSendTime = 0;
  516. nextSnapshotSendTime = 0;
  517. }