sys_lobby_snapshot.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834
  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. #pragma hdrstop
  21. #include "../idlib/precompiled.h"
  22. #include "sys_lobby.h"
  23. idCVar net_snapshot_send_warntime( "net_snapshot_send_warntime", "500", CVAR_INTEGER, "Print warning messages if we take longer than this to send a client a snapshot." );
  24. idCVar net_queueSnapAcks( "net_queueSnapAcks", "1", CVAR_BOOL, "" );
  25. idCVar net_peer_throttle_mode( "net_peer_throttle_mode", "0", CVAR_INTEGER, "= 0 off, 1 = enable fixed, 2 = absolute, 3 = both" );
  26. idCVar net_peer_throttle_minSnapSeq( "net_peer_throttle_minSnapSeq", "150", CVAR_INTEGER, "Minumum number of snapshot exchanges before throttling can be triggered" );
  27. idCVar net_peer_throttle_bps_peer_threshold_pct( "net_peer_throttle_bps_peer_threshold_pct", "0.60", CVAR_FLOAT, "Min reported incoming bps % of sent from host that a peer must maintain before throttling kicks in" );
  28. idCVar net_peer_throttle_bps_host_threshold( "net_peer_throttle_bps_host_threshold", "1024", CVAR_FLOAT, "Min outgoing bps of host for bps based throttling to be considered" );
  29. idCVar net_peer_throttle_bps_decay( "net_peer_throttle_bps_decay", "0.25f", CVAR_FLOAT, "If peer exceeds this number of queued snap deltas, then throttle his effective snap rate" );
  30. idCVar net_peer_throttle_bps_duration( "net_peer_throttle_bps_duration", "3000", CVAR_INTEGER, "If peer exceeds this number of queued snap deltas, then throttle his effective snap rate" );
  31. idCVar net_peer_throttle_maxSnapRate( "net_peer_throttle_maxSnapRate", "4", CVAR_INTEGER, "Highest factor of server base snapRate that a client can be throttled" );
  32. idCVar net_snap_bw_test_throttle_max_scale( "net_snap_bw_test_throttle_max_scale", "0.80", CVAR_FLOAT, "When clamping bandwidth to reported values, scale reported value by this" );
  33. idCVar net_snap_redundant_resend_in_ms( "net_snap_redundant_resend_in_ms", "800", CVAR_INTEGER, "Delay between redundantly sending snaps during initial snap exchange" );
  34. idCVar net_min_ping_in_ms( "net_min_ping_in_ms", "1500", CVAR_INTEGER, "Ping has to be higher than this before we consider throttling to recover" );
  35. idCVar net_pingIncPercentBeforeRecover( "net_pingIncPercentBeforeRecover", "1.3", CVAR_FLOAT, "Percentage change increase of ping before we try to recover" );
  36. idCVar net_maxFailedPingRecoveries( "net_maxFailedPingRecoveries", "10", CVAR_INTEGER, "Max failed ping recoveries before we stop trying" );
  37. idCVar net_pingRecoveryThrottleTimeInSeconds( "net_pingRecoveryThrottleTimeInSeconds", "3", CVAR_INTEGER, "Throttle snaps for this amount of time in seconds to recover from ping spike" );
  38. idCVar net_peer_timeout_loading( "net_peer_timeout_loading", "90000", CVAR_INTEGER, "time in MS to disconnect clients during loading - production only" );
  39. /*
  40. ========================
  41. idLobby::UpdateSnaps
  42. ========================
  43. */
  44. void idLobby::UpdateSnaps() {
  45. assert( lobbyType == GetActingGameStateLobbyType() );
  46. SCOPED_PROFILE_EVENT( "UpdateSnaps" );
  47. #if 0
  48. uint64 startTimeMicroSec = Sys_Microseconds();
  49. #endif
  50. haveSubmittedSnaps = false;
  51. if ( !SendCompletedSnaps() ) {
  52. // If we weren't able to send all the submitted snaps, we need to wait till we can.
  53. // We can't start new jobs until they are all sent out.
  54. return;
  55. }
  56. for ( int p = 0; p < peers.Num(); p++ ) {
  57. peer_t & peer = peers[p];
  58. if ( !peer.IsConnected() ) {
  59. continue;
  60. }
  61. if ( peer.needToSubmitPendingSnap ) {
  62. // Submit the snap
  63. if ( SubmitPendingSnap( p ) ) {
  64. peer.needToSubmitPendingSnap = false; // only clear this if we actually submitted the snap
  65. }
  66. }
  67. }
  68. #if 0
  69. uint64 endTimeMicroSec = Sys_Microseconds();
  70. if ( endTimeMicroSec - startTimeMicroSec > 200 ) { // .2 ms
  71. idLib::Printf( "NET: UpdateSnaps time in ms: %f\n", (float)( endTimeMicroSec - startTimeMicroSec ) / 1000.0f);
  72. }
  73. #endif
  74. }
  75. /*
  76. ========================
  77. idLobby::SendCompletedSnaps
  78. This function will send send off any previously submitted pending snaps if they are ready
  79. ========================
  80. */
  81. bool idLobby::SendCompletedSnaps() {
  82. assert( lobbyType == GetActingGameStateLobbyType() );
  83. bool sentAllSubmitted = true;
  84. for ( int p = 0; p < peers.Num(); p++ ) {
  85. peer_t & peer = peers[p];
  86. if ( !peer.IsConnected() ) {
  87. continue;
  88. }
  89. if ( peer.snapProc->PendingSnapReadyToSend() ) {
  90. // Check to see if there are any snaps that were submitted that need to be sent out
  91. SendCompletedPendingSnap( p );
  92. } else if ( IsHost() ) {
  93. NET_VERBOSESNAPSHOT_PRINT_LEVEL( 7, va(" ^8Peer %d pendingSnap not ready to send\n", p) );
  94. }
  95. if ( !peer.IsConnected() ) { // peer may have been dropped in "SendCompletedPendingSnap". ugh.
  96. continue;
  97. }
  98. if ( peer.snapProc->PendingSnapReadyToSend() ) {
  99. // If we still have a submitted snap, we know we're not done
  100. sentAllSubmitted = false;
  101. if ( IsHost() ) {
  102. NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" ^2Peer %d did not send all submitted snapshots.\n", p) );
  103. }
  104. }
  105. }
  106. return sentAllSubmitted;
  107. }
  108. /*
  109. ========================
  110. idLobby::SendResources
  111. ========================
  112. */
  113. bool idLobby::SendResources( int p ) {
  114. assert( lobbyType == GetActingGameStateLobbyType() );
  115. return false;
  116. }
  117. /*
  118. ========================
  119. idLobby::SubmitPendingSnap
  120. ========================
  121. */
  122. bool idLobby::SubmitPendingSnap( int p ) {
  123. assert( lobbyType == GetActingGameStateLobbyType() );
  124. peer_t & peer = peers[p];
  125. if ( !peer.IsConnected() ) {
  126. return false;
  127. }
  128. // If the peer doesn't have the latest resource list, send it to him before sending any new snapshots
  129. if ( SendResources( p ) ) {
  130. return false;
  131. }
  132. if ( !peer.loaded ) {
  133. return false;
  134. }
  135. if ( !peer.snapProc->HasPendingSnap() ) {
  136. return false;
  137. }
  138. int time = Sys_Milliseconds();
  139. int timeFromLastSub = time - peer.lastSnapJobTime;
  140. int forceResendTime = session->GetTitleStorageInt( "net_snap_redundant_resend_in_ms", net_snap_redundant_resend_in_ms.GetInteger() );
  141. if ( timeFromLastSub < forceResendTime && peer.snapProc->IsBusyConfirmingPartialSnap() ) {
  142. return false;
  143. }
  144. peer.lastSnapJobTime = time;
  145. assert( !peer.snapProc->PendingSnapReadyToSend() );
  146. // Submit snapshot delta to jobs
  147. peer.snapProc->SubmitPendingSnap( p + 1, objMemory, SNAP_OBJ_JOB_MEMORY, lzwData );
  148. NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" Submitted snapshot to jobList for peer %d. Since last jobsub: %d\n", p, timeFromLastSub ) );
  149. return true;
  150. }
  151. /*
  152. ========================
  153. idLobby::SendCompletedPendingSnap
  154. ========================
  155. */
  156. void idLobby::SendCompletedPendingSnap( int p ) {
  157. assert( lobbyType == GetActingGameStateLobbyType() );
  158. int time = Sys_Milliseconds();
  159. peer_t & peer = peers[p];
  160. if ( !peer.IsConnected() ) {
  161. return;
  162. }
  163. if ( peer.snapProc == NULL || !peer.snapProc->PendingSnapReadyToSend() ) {
  164. return;
  165. }
  166. // If we have a pending snap ready to send, we better have a pending snap
  167. assert( peer.snapProc->HasPendingSnap() );
  168. // Get the snap data blob now, even if we don't send it.
  169. // This is somewhat wasteful, but we have to do this to keep the snap job pipe ready to keep doing work
  170. // If we don't do this, this peer will cause other peers to be starved of snapshots, when they may very well be ready to send a snap
  171. byte buffer[ MAX_SNAP_SIZE ];
  172. int maxLength = sizeof( buffer ) - peer.packetProc->GetReliableDataSize() - 128;
  173. int size = peer.snapProc->GetPendingSnapDelta( buffer, maxLength );
  174. if ( !CanSendMoreData( p ) ) {
  175. return;
  176. }
  177. // Can't send anymore snapshots until all fragments are sent
  178. if ( peer.packetProc->HasMoreFragments() ) {
  179. return;
  180. }
  181. // If the peer doesn't have the latest resource list, send it to him before sending any new snapshots
  182. if ( SendResources( p ) ) {
  183. return;
  184. }
  185. int timeFromJobSub = time - peer.lastSnapJobTime;
  186. int timeFromLastSend = time - peer.lastSnapTime;
  187. if ( timeFromLastSend > 0 ) {
  188. peer.snapHz = 1000.0f / (float)timeFromLastSend;
  189. } else {
  190. peer.snapHz = 0.0f;
  191. }
  192. if ( net_snapshot_send_warntime.GetInteger() > 0 && peer.lastSnapTime != 0 && net_snapshot_send_warntime.GetInteger() < timeFromLastSend ) {
  193. idLib::Printf( "NET: Took %d ms to send peer %d snapshot\n", timeFromLastSend, p );
  194. }
  195. if ( peer.throttleSnapsForXSeconds != 0 ) {
  196. if ( time < peer.throttleSnapsForXSeconds ) {
  197. return;
  198. }
  199. // If we were trying to recover ping, see if we succeeded
  200. if ( peer.recoverPing != 0 ) {
  201. if ( peer.lastPingRtt >= peer.recoverPing ) {
  202. peer.failedPingRecoveries++;
  203. } else {
  204. const int peer_throttle_minSnapSeq = session->GetTitleStorageInt( "net_peer_throttle_minSnapSeq", net_peer_throttle_minSnapSeq.GetInteger() );
  205. if ( peer.snapProc->GetFullSnapBaseSequence() > idSnapshotProcessor::INITIAL_SNAP_SEQUENCE + peer_throttle_minSnapSeq ) {
  206. // If throttling recovered the ping
  207. int maxRate = common->GetSnapRate() * session->GetTitleStorageInt( "net_peer_throttle_maxSnapRate", net_peer_throttle_maxSnapRate.GetInteger() );
  208. peer.throttledSnapRate = idMath::ClampInt( common->GetSnapRate(), maxRate, peer.throttledSnapRate + common->GetSnapRate() );
  209. }
  210. }
  211. }
  212. peer.throttleSnapsForXSeconds = 0;
  213. }
  214. peer.lastSnapTime = time;
  215. if ( size != 0 ) {
  216. if ( size > 0 ) {
  217. NET_VERBOSESNAPSHOT_PRINT_LEVEL( 3, va("NET: (peer %d) Sending snapshot %d delta'd against %d. Since JobSub: %d Since LastSend: %d. Size: %d\n", p, peer.snapProc->GetSnapSequence(), peer.snapProc->GetBaseSequence(), timeFromJobSub, timeFromLastSend, size ) );
  218. ProcessOutgoingMsg( p, buffer, size, false, 0 );
  219. } else if ( size < 0 ) { // Size < 0 indicates the delta buffer filled up
  220. // There used to be code here that would disconnect peers if they were in game and filled up the buffer
  221. // This was causing issues in the playtests we were running (Doom 4 MP) and after some conversation
  222. // determined that it was not needed since a timeout mechanism has been added since
  223. ProcessOutgoingMsg( p, buffer, -size, false, 0 );
  224. if ( peer.snapProc != NULL ) {
  225. NET_VERBOSESNAPSHOT_PRINT( "NET: (peerNum: %d - name: %s) Resending last snapshot delta %d because his delta list filled up. Since JobSub: %d Since LastSend: %d Delta Size: %d\n", p, GetPeerName( p ), peer.snapProc->GetSnapSequence(), timeFromJobSub, timeFromLastSend, size );
  226. }
  227. }
  228. }
  229. // We calculate what our outgoing rate was for each sequence, so we can have a relative comparison
  230. // for when the client reports what his downstream was in the same timeframe
  231. if ( IsHost() && peer.snapProc != NULL && peer.snapProc->GetSnapSequence() > 0 ) {
  232. //NET_VERBOSE_PRINT("^8 %i Rate: %.2f SnapSeq: %d GetBaseSequence: %d\n", lastAppendedSequence, peer.packetProc->GetOutgoingRateBytes(), peer.snapProc->GetSnapSequence(), peer.snapProc->GetBaseSequence() );
  233. peer.sentBpsHistory[ peer.snapProc->GetSnapSequence() % MAX_BPS_HISTORY ] = peer.packetProc->GetOutgoingRateBytes();
  234. }
  235. }
  236. /*
  237. ========================
  238. idLobby::CheckPeerThrottle
  239. ========================
  240. */
  241. void idLobby::CheckPeerThrottle( int p ) {
  242. assert( lobbyType == GetActingGameStateLobbyType() );
  243. if ( !verify( p >= 0 && p < peers.Num() ) ) {
  244. return;
  245. }
  246. peer_t & peer = peers[p];
  247. if ( !peer.IsConnected() ) {
  248. return;
  249. }
  250. if ( !IsHost() ) {
  251. return;
  252. }
  253. if ( session->GetTitleStorageInt( "net_peer_throttle_mode", net_peer_throttle_mode.GetInteger() ) == 0 ) {
  254. return;
  255. }
  256. if ( peer.receivedBps < 0.0f ) {
  257. return;
  258. }
  259. int time = Sys_Milliseconds();
  260. if ( !AllPeersHaveBaseState() ) {
  261. return;
  262. }
  263. if ( verify( peer.snapProc != NULL ) ) {
  264. const int peer_throttle_minSnapSeq = session->GetTitleStorageInt( "net_peer_throttle_minSnapSeq", net_peer_throttle_minSnapSeq.GetInteger() );
  265. if ( peer.snapProc->GetFullSnapBaseSequence() <= idSnapshotProcessor::INITIAL_SNAP_SEQUENCE + peer_throttle_minSnapSeq ) {
  266. return;
  267. }
  268. }
  269. // This is bps throttling which compares the sent bytes per second to the reported received bps
  270. float peer_throttle_bps_host_threshold = session->GetTitleStorageFloat( "net_peer_throttle_bps_host_threshold", net_peer_throttle_bps_host_threshold.GetFloat() );
  271. if ( peer_throttle_bps_host_threshold > 0.0f ) {
  272. int deltaT = idMath::ClampInt( 0, 100, time - peer.receivedThrottleTime );
  273. if ( deltaT > 0 && peer.receivedThrottleTime > 0 && peer.receivedBpsIndex > 0 ) {
  274. bool throttled = false;
  275. float sentBps = peer.sentBpsHistory[ peer.receivedBpsIndex % MAX_BPS_HISTORY ];
  276. // Min outgoing rate from server (don't throttle if we are sending < 1k)
  277. if ( sentBps > peer_throttle_bps_host_threshold ) {
  278. float pct = peer.receivedBps / idMath::ClampFloat( 0.01f, static_cast<float>( BANDWIDTH_REPORTING_MAX ), sentBps ); // note the receivedBps is implicitly clamped on client end to 10k/sec
  279. /*
  280. static int lastSeq = 0;
  281. if ( peer.receivedBpsIndex != lastSeq ) {
  282. NET_VERBOSE_PRINT( "%ssentBpsHistory[%d] = %.2f received: %.2f PCT: %.2f \n", ( pct > 1.0f ? "^1" : "" ), peer.receivedBpsIndex, sentBps, peer.receivedBps, pct );
  283. }
  284. lastSeq = peer.receivedBpsIndex;
  285. */
  286. // Increase throttle time if peer is < % of what we are sending him
  287. if ( pct < session->GetTitleStorageFloat( "net_peer_throttle_bps_peer_threshold_pct", net_peer_throttle_bps_peer_threshold_pct.GetFloat() ) ) {
  288. peer.receivedThrottle += (float)deltaT;
  289. throttled = true;
  290. NET_VERBOSE_PRINT("NET: throttled... %.2f ....pct %.2f receivedBps %.2f outgoingBps %.2f, peer %i, seq %i\n", peer.receivedThrottle, pct, peer.receivedBps, sentBps, p, peer.snapProc->GetFullSnapBaseSequence() );
  291. }
  292. }
  293. if ( !throttled ) {
  294. float decayRate = session->GetTitleStorageFloat( "net_peer_throttle_bps_decay", net_peer_throttle_bps_decay.GetFloat() );
  295. peer.receivedThrottle = Max<float>( 0.0f, peer.receivedThrottle - ( ( (float)deltaT ) * decayRate ) );
  296. //NET_VERBOSE_PRINT("NET: !throttled... %.2f ....receivedBps %.2f outgoingBps %.2f\n", peer.receivedThrottle, peer.receivedBps, sentBps );
  297. }
  298. float duration = session->GetTitleStorageFloat( "net_peer_throttle_bps_duration", net_peer_throttle_bps_duration.GetFloat() );
  299. if ( peer.receivedThrottle > duration ) {
  300. peer.maxSnapBps = peer.receivedBps * session->GetTitleStorageFloat( "net_snap_bw_test_throttle_max_scale", net_snap_bw_test_throttle_max_scale.GetFloat() );
  301. int maxRate = common->GetSnapRate() * session->GetTitleStorageInt( "net_peer_throttle_maxSnapRate", net_peer_throttle_maxSnapRate.GetInteger() );
  302. if ( peer.throttledSnapRate == 0 ) {
  303. peer.throttledSnapRate = common->GetSnapRate() * 2;
  304. } else if ( peer.throttledSnapRate < maxRate ) {
  305. peer.throttledSnapRate = idMath::ClampInt( common->GetSnapRate(), maxRate, peer.throttledSnapRate + common->GetSnapRate() );
  306. }
  307. peer.receivedThrottle = 0.0f; // Start over, so we don't immediately throttle again
  308. }
  309. }
  310. peer.receivedThrottleTime = time;
  311. }
  312. }
  313. /*
  314. ========================
  315. idLobby::ApplySnapshotDelta
  316. ========================
  317. */
  318. void idLobby::ApplySnapshotDelta( int p, int snapshotNumber ) {
  319. assert( lobbyType == GetActingGameStateLobbyType() );
  320. if ( !verify( p >= 0 && p < peers.Num() ) ) {
  321. return;
  322. }
  323. peer_t & peer = peers[p];
  324. if ( !peer.IsConnected() ) {
  325. return;
  326. }
  327. if ( net_queueSnapAcks.GetBool() && AllPeersHaveBaseState() ) {
  328. // If we've reached our queue limit, force the oldest one out now
  329. if ( snapDeltaAckQueue.Num() == snapDeltaAckQueue.Max() ) {
  330. ApplySnapshotDeltaInternal( snapDeltaAckQueue[0].p, snapDeltaAckQueue[0].snapshotNumber );
  331. snapDeltaAckQueue.RemoveIndex( 0 );
  332. }
  333. // Queue up acks, so we can spread them out over frames to lighten the load when they all come in at once
  334. snapDeltaAck_t snapDeltaAck;
  335. snapDeltaAck.p = p;
  336. snapDeltaAck.snapshotNumber = snapshotNumber;
  337. snapDeltaAckQueue.Append( snapDeltaAck );
  338. } else {
  339. ApplySnapshotDeltaInternal( p, snapshotNumber );
  340. }
  341. }
  342. /*
  343. ========================
  344. idLobby::ApplySnapshotDeltaInternal
  345. ========================
  346. */
  347. bool idLobby::ApplySnapshotDeltaInternal( int p, int snapshotNumber ) {
  348. assert( lobbyType == GetActingGameStateLobbyType() );
  349. if ( !verify( p >= 0 && p < peers.Num() ) ) {
  350. return false;
  351. }
  352. peer_t & peer = peers[p];
  353. if ( !peer.IsConnected() ) {
  354. return false;
  355. }
  356. // on the server, player = peer number + 1, this only works as long as we don't support clients joining and leaving during game
  357. // on the client, always 0
  358. bool result = peer.snapProc->ApplySnapshotDelta( IsHost() ? p + 1 : 0, snapshotNumber );
  359. if ( result && IsHost() && peer.snapProc->HasPendingSnap() ) {
  360. // Send more of the pending snap if we have one for this peer.
  361. // The reason we can do this, is because we know more about this peers base state now.
  362. // And since we maxed out the optimal snap delta size, we'll now be able
  363. // to send more data, since we assume we'll get better and better delta compression as
  364. // our version of this peers base state approaches parity with the peers actual state.
  365. // We don't send immediately, since we have to coordinate sending snaps for all peers in same place considering jobs.
  366. peer.needToSubmitPendingSnap = true;
  367. NET_VERBOSESNAPSHOT_PRINT( "NET: Sent more unsent snapshot data to peer %d for snapshot %d\n", p, snapshotNumber );
  368. }
  369. return result;
  370. }
  371. /*
  372. ========================
  373. idLobby::SendSnapshotToPeer
  374. ========================
  375. */
  376. idCVar net_forceDropSnap( "net_forceDropSnap", "0", CVAR_BOOL, "wait on snaps" );
  377. void idLobby::SendSnapshotToPeer( idSnapShot & ss, int p ) {
  378. assert( lobbyType == GetActingGameStateLobbyType() );
  379. peer_t & peer = peers[p];
  380. if ( net_forceDropSnap.GetBool() ) {
  381. net_forceDropSnap.SetBool( false );
  382. return;
  383. }
  384. if ( peer.pauseSnapshots ) {
  385. return;
  386. }
  387. int time = Sys_Milliseconds();
  388. const int throttleMode = session->GetTitleStorageInt( "net_peer_throttle_mode", net_peer_throttle_mode.GetInteger() );
  389. // Real peer throttling based on performance
  390. // -We throttle before sending to jobs rather than before sending
  391. if ( ( throttleMode == 1 || throttleMode == 3 ) && peer.throttledSnapRate > 0 ) {
  392. if ( time - peer.lastSnapJobTime < peer.throttledSnapRate / 1000 ) { // fixme /1000
  393. // This peer is throttled, skip his snap shot
  394. NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va( "NET: Throttling peer %d.Skipping snapshot. Time elapsed: %d peer snap rate: %d\n", p, ( time - peer.lastSnapJobTime ), peer.throttledSnapRate ) );
  395. return;
  396. }
  397. }
  398. if ( throttleMode != 0 ) {
  399. DetectSaturation( p );
  400. }
  401. if ( peer.maxSnapBps >= 0.0f && ( throttleMode == 2 || throttleMode == 3 ) ) {
  402. if ( peer.packetProc->GetOutgoingRateBytes() > peer.maxSnapBps ) {
  403. return;
  404. }
  405. }
  406. // TrySetPendingSnapshot will try to set the new pending snap.
  407. // TrySetPendingSnapshot won't do anything until the last snap set was fully sent out.
  408. if ( peer.snapProc->TrySetPendingSnapshot( ss ) ) {
  409. NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" ^8Set next pending snapshot peer %d\n", 0 ) );
  410. peer.numSnapsSent++;
  411. idSnapShot * baseState = peers[p].snapProc->GetBaseState();
  412. if ( verify( baseState != NULL ) ) {
  413. baseState->UpdateExpectedSeq( peers[p].snapProc->GetSnapSequence() );
  414. }
  415. } else {
  416. NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" ^2FAILED Set next pending snapshot peer %d\n", 0 ) );
  417. }
  418. // We send out the pending snap, which could be the most recent, or an old one that hasn't fully been sent
  419. // We don't send immediately, since we have to coordinate sending snaps for all peers in same place considering jobs.
  420. peer.needToSubmitPendingSnap = true;
  421. }
  422. /*
  423. ========================
  424. idLobby::AllPeersHaveBaseState
  425. ========================
  426. */
  427. bool idLobby::AllPeersHaveBaseState() {
  428. assert( lobbyType == GetActingGameStateLobbyType() );
  429. for ( int i = 0; i < peers.Num(); ++i ) {
  430. if ( !peers[i].IsConnected() ) {
  431. continue;
  432. }
  433. if ( peers[i].snapProc->GetFullSnapBaseSequence() < idSnapshotProcessor::INITIAL_SNAP_SEQUENCE ) {
  434. return false; // If a client hasn't ack'd his first full snap, then we are still sending base state to someone
  435. }
  436. }
  437. return true;
  438. }
  439. /*
  440. ========================
  441. idLobby::ThrottleSnapsForXSeconds
  442. ========================
  443. */
  444. void idLobby::ThrottleSnapsForXSeconds( int p, int seconds, bool recoverPing ) {
  445. assert( lobbyType == GetActingGameStateLobbyType() );
  446. if ( peers[p].throttleSnapsForXSeconds != 0 ) {
  447. return; // Already throttling snaps
  448. }
  449. idLib::Printf( "Throttling peer %i for %i seconds...\n", p, seconds );
  450. peers[p].throttleSnapsForXSeconds = Sys_Milliseconds() + seconds * 1000;
  451. peers[p].recoverPing = recoverPing ? peers[p].lastPingRtt : 0;
  452. }
  453. /*
  454. ========================
  455. idLobby::FirstSnapHasBeenSent
  456. ========================
  457. */
  458. bool idLobby::FirstSnapHasBeenSent( int p ) {
  459. assert( lobbyType == GetActingGameStateLobbyType() );
  460. if ( !verify( p >= 0 && p < peers.Num() ) ) {
  461. return false;
  462. }
  463. peer_t & peer = peers[p];
  464. if ( peer.numSnapsSent == 0 ) {
  465. return false;
  466. }
  467. if ( peer.snapProc == NULL ) {
  468. return false;
  469. }
  470. idSnapShot * ss = peer.snapProc->GetPendingSnap();
  471. if ( ss == NULL ) {
  472. return false;
  473. }
  474. if ( ss->NumObjects() == 0 ) {
  475. return false;
  476. }
  477. return true;
  478. }
  479. /*
  480. ========================
  481. idLobby::EnsureAllPeersHaveBaseState
  482. This function ensures all peers that started the match together (they were in the lobby when it started) start together.
  483. Join in progress peers will be handled as they join.
  484. ========================
  485. */
  486. bool idLobby::EnsureAllPeersHaveBaseState() {
  487. assert( lobbyType == GetActingGameStateLobbyType() );
  488. int time = Sys_Milliseconds();
  489. for ( int i = 0; i < peers.Num(); ++i ) {
  490. if ( !peers[i].IsConnected() ) {
  491. continue;
  492. }
  493. if ( !FirstSnapHasBeenSent( i ) ) {
  494. continue; // Must be join in progress peer
  495. }
  496. if ( peers[i].snapProc->GetFullSnapBaseSequence() < idSnapshotProcessor::INITIAL_SNAP_SEQUENCE ) {
  497. if ( time - peers[i].lastSnapTime > session->GetTitleStorageInt( "net_snap_redundant_resend_in_ms", net_snap_redundant_resend_in_ms.GetInteger() ) ) {
  498. SendSnapshotToPeer( *peers[i].snapProc->GetPendingSnap(), i );
  499. }
  500. return false;
  501. }
  502. }
  503. return true;
  504. }
  505. /*
  506. ========================
  507. idLobby::AllPeersHaveStaleSnapObj
  508. ========================
  509. */
  510. bool idLobby::AllPeersHaveStaleSnapObj( int objId ) {
  511. assert( lobbyType == GetActingGameStateLobbyType() );
  512. for ( int i = 0; i < peers.Num(); i++ ) {
  513. if ( !peers[i].IsConnected() ) {
  514. continue;
  515. }
  516. idSnapShot * baseState = peers[i].snapProc->GetBaseState();
  517. idSnapShot::objectState_t * state = baseState->FindObjectByID( objId );
  518. if ( state == NULL || !state->stale ) {
  519. return false;
  520. }
  521. }
  522. return true;
  523. }
  524. /*
  525. ========================
  526. idLobby::AllPeersHaveExpectedSnapObj
  527. ========================
  528. */
  529. bool idLobby::AllPeersHaveExpectedSnapObj( int objId ) {
  530. assert( lobbyType == GetActingGameStateLobbyType() );
  531. for ( int i = 0; i < peers.Num(); i++ ) {
  532. if ( !peers[i].IsConnected() ) {
  533. continue;
  534. }
  535. idSnapShot * baseState = peers[i].snapProc->GetBaseState();
  536. idSnapShot::objectState_t * state = baseState->FindObjectByID( objId );
  537. if ( state == NULL ) {
  538. return false;
  539. }
  540. if ( state->expectedSequence == -2 ) {
  541. return false;
  542. }
  543. if ( state->expectedSequence > 0 && peers[i].snapProc->GetFullSnapBaseSequence() <= state->expectedSequence ) {
  544. //idLib::Printf("^3Not ready to go stale. obj %d Base: %d expected: %d\n", objId, peers[i].snapProc->GetBaseSequence(), state->expectedSequence );
  545. return false;
  546. }
  547. }
  548. return true;
  549. }
  550. /*
  551. ========================
  552. idLobby::MarkSnapObjDeleted
  553. ========================
  554. */
  555. void idLobby::RefreshSnapObj( int objId ) {
  556. assert( lobbyType == GetActingGameStateLobbyType() );
  557. for ( int i = 0; i < peers.Num(); i++ ) {
  558. if ( !peers[i].IsConnected() ) {
  559. continue;
  560. }
  561. idSnapShot * baseState = peers[i].snapProc->GetBaseState();
  562. idSnapShot::objectState_t * state = baseState->FindObjectByID( objId );
  563. if ( state != NULL ) {
  564. // Setting to -2 will defer setting the expected sequence until the current snap is ready to send
  565. state->expectedSequence = -2;
  566. }
  567. }
  568. }
  569. /*
  570. ========================
  571. idLobby::MarkSnapObjDeleted
  572. ========================
  573. */
  574. void idLobby::MarkSnapObjDeleted( int objId ) {
  575. assert( lobbyType == GetActingGameStateLobbyType() );
  576. for ( int i = 0; i < peers.Num(); i++ ) {
  577. if ( !peers[i].IsConnected() ) {
  578. continue;
  579. }
  580. idSnapShot * baseState = peers[i].snapProc->GetBaseState();
  581. idSnapShot::objectState_t * state = baseState->FindObjectByID( objId );
  582. if ( state != NULL ) {
  583. state->deleted = true;
  584. }
  585. }
  586. }
  587. /*
  588. ========================
  589. idLobby::ResetBandwidthStats
  590. ========================
  591. */
  592. void idLobby::ResetBandwidthStats() {
  593. assert( lobbyType == GetActingGameStateLobbyType() );
  594. lastSnapBspHistoryUpdateSequence = -1;
  595. for ( int p = 0; p < peers.Num(); p++ ) {
  596. peers[p].maxSnapBps = -1.0f;
  597. peers[p].throttledSnapRate = 0;
  598. peers[p].rightBeforeSnapsPing = peers[p].lastPingRtt;
  599. peers[p].throttleSnapsForXSeconds = 0;
  600. peers[p].recoverPing = 0;
  601. peers[p].failedPingRecoveries = 0;
  602. peers[p].rightBeforeSnapsPing = 0;
  603. }
  604. }
  605. /*
  606. ========================
  607. idLobby::DetectSaturation
  608. See if the ping shot up, which indicates a previously saturated connection
  609. ========================
  610. */
  611. void idLobby::DetectSaturation( int p ) {
  612. assert( lobbyType == GetActingGameStateLobbyType() );
  613. peer_t & peer = peers[p];
  614. if ( !peer.IsConnected() ) {
  615. return;
  616. }
  617. const float pingIncPercentBeforeThottle = session->GetTitleStorageFloat( "net_pingIncPercentBeforeRecover", net_pingIncPercentBeforeRecover.GetFloat() );
  618. const int pingThreshold = session->GetTitleStorageInt( "net_min_ping_in_ms", net_min_ping_in_ms.GetInteger() );
  619. const int maxFailedPingRecoveries = session->GetTitleStorageInt( "net_maxFailedPingRecoveries", net_maxFailedPingRecoveries.GetInteger() );
  620. const int pingRecoveryThrottleTimeInSeconds = session->GetTitleStorageInt( "net_pingRecoveryThrottleTimeInSeconds", net_pingRecoveryThrottleTimeInSeconds.GetInteger() );
  621. if ( peer.lastPingRtt > peer.rightBeforeSnapsPing * pingIncPercentBeforeThottle && peer.lastPingRtt > pingThreshold ) {
  622. if ( peer.failedPingRecoveries < maxFailedPingRecoveries ) {
  623. ThrottleSnapsForXSeconds( p, pingRecoveryThrottleTimeInSeconds, true );
  624. }
  625. }
  626. }
  627. /*
  628. ========================
  629. idLobby::AddSnapObjTemplate
  630. ========================
  631. */
  632. void idLobby::AddSnapObjTemplate( int objID, idBitMsg & msg ) {
  633. assert( lobbyType == GetActingGameStateLobbyType() );
  634. // If we are in the middle of a SS read, apply this state to what we
  635. // just deserialized (the obj we just deserialized is a delta from the template object we are adding right now)
  636. if ( localReadSS != NULL ) {
  637. localReadSS->ApplyToExistingState( objID, msg );
  638. }
  639. // Add the template to the snapshot proc for future snapshot processing
  640. for ( int p = 0; p < peers.Num(); p++ ) {
  641. if ( !peers[p].IsConnected() || peers[p].snapProc == NULL ) {
  642. continue;
  643. }
  644. peers[p].snapProc->AddSnapObjTemplate( objID, msg );
  645. }
  646. }