sys_lobby_snapshot.cpp (28451B)
1 /* 2 =========================================================================== 3 4 Doom 3 BFG Edition GPL Source Code 5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 6 7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). 8 9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation, either version 3 of the License, or 12 (at your option) any later version. 13 14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>. 21 22 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. 23 24 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. 25 26 =========================================================================== 27 */ 28 #pragma hdrstop 29 #include "../idlib/precompiled.h" 30 #include "sys_lobby.h" 31 32 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." ); 33 34 idCVar net_queueSnapAcks( "net_queueSnapAcks", "1", CVAR_BOOL, "" ); 35 36 idCVar net_peer_throttle_mode( "net_peer_throttle_mode", "0", CVAR_INTEGER, "= 0 off, 1 = enable fixed, 2 = absolute, 3 = both" ); 37 38 idCVar net_peer_throttle_minSnapSeq( "net_peer_throttle_minSnapSeq", "150", CVAR_INTEGER, "Minumum number of snapshot exchanges before throttling can be triggered" ); 39 40 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" ); 41 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" ); 42 43 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" ); 44 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" ); 45 46 idCVar net_peer_throttle_maxSnapRate( "net_peer_throttle_maxSnapRate", "4", CVAR_INTEGER, "Highest factor of server base snapRate that a client can be throttled" ); 47 48 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" ); 49 50 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" ); 51 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" ); 52 idCVar net_pingIncPercentBeforeRecover( "net_pingIncPercentBeforeRecover", "1.3", CVAR_FLOAT, "Percentage change increase of ping before we try to recover" ); 53 idCVar net_maxFailedPingRecoveries( "net_maxFailedPingRecoveries", "10", CVAR_INTEGER, "Max failed ping recoveries before we stop trying" ); 54 idCVar net_pingRecoveryThrottleTimeInSeconds( "net_pingRecoveryThrottleTimeInSeconds", "3", CVAR_INTEGER, "Throttle snaps for this amount of time in seconds to recover from ping spike" ); 55 56 idCVar net_peer_timeout_loading( "net_peer_timeout_loading", "90000", CVAR_INTEGER, "time in MS to disconnect clients during loading - production only" ); 57 58 59 /* 60 ======================== 61 idLobby::UpdateSnaps 62 ======================== 63 */ 64 void idLobby::UpdateSnaps() { 65 66 assert( lobbyType == GetActingGameStateLobbyType() ); 67 68 SCOPED_PROFILE_EVENT( "UpdateSnaps" ); 69 70 #if 0 71 uint64 startTimeMicroSec = Sys_Microseconds(); 72 #endif 73 74 haveSubmittedSnaps = false; 75 76 if ( !SendCompletedSnaps() ) { 77 // If we weren't able to send all the submitted snaps, we need to wait till we can. 78 // We can't start new jobs until they are all sent out. 79 return; 80 } 81 82 for ( int p = 0; p < peers.Num(); p++ ) { 83 peer_t & peer = peers[p]; 84 85 if ( !peer.IsConnected() ) { 86 continue; 87 } 88 89 if ( peer.needToSubmitPendingSnap ) { 90 // Submit the snap 91 if ( SubmitPendingSnap( p ) ) { 92 peer.needToSubmitPendingSnap = false; // only clear this if we actually submitted the snap 93 } 94 95 } 96 } 97 98 #if 0 99 uint64 endTimeMicroSec = Sys_Microseconds(); 100 101 if ( endTimeMicroSec - startTimeMicroSec > 200 ) { // .2 ms 102 idLib::Printf( "NET: UpdateSnaps time in ms: %f\n", (float)( endTimeMicroSec - startTimeMicroSec ) / 1000.0f); 103 } 104 #endif 105 } 106 107 /* 108 ======================== 109 idLobby::SendCompletedSnaps 110 This function will send send off any previously submitted pending snaps if they are ready 111 ======================== 112 */ 113 bool idLobby::SendCompletedSnaps() { 114 assert( lobbyType == GetActingGameStateLobbyType() ); 115 116 bool sentAllSubmitted = true; 117 118 for ( int p = 0; p < peers.Num(); p++ ) { 119 peer_t & peer = peers[p]; 120 121 if ( !peer.IsConnected() ) { 122 continue; 123 } 124 125 if ( peer.snapProc->PendingSnapReadyToSend() ) { 126 // Check to see if there are any snaps that were submitted that need to be sent out 127 SendCompletedPendingSnap( p ); 128 } else if ( IsHost() ) { 129 NET_VERBOSESNAPSHOT_PRINT_LEVEL( 7, va(" ^8Peer %d pendingSnap not ready to send\n", p) ); 130 } 131 132 if ( !peer.IsConnected() ) { // peer may have been dropped in "SendCompletedPendingSnap". ugh. 133 continue; 134 } 135 136 if ( peer.snapProc->PendingSnapReadyToSend() ) { 137 // If we still have a submitted snap, we know we're not done 138 sentAllSubmitted = false; 139 if ( IsHost() ) { 140 NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" ^2Peer %d did not send all submitted snapshots.\n", p) ); 141 } 142 } 143 } 144 145 return sentAllSubmitted; 146 } 147 148 /* 149 ======================== 150 idLobby::SendResources 151 ======================== 152 */ 153 bool idLobby::SendResources( int p ) { 154 assert( lobbyType == GetActingGameStateLobbyType() ); 155 156 return false; 157 } 158 159 /* 160 ======================== 161 idLobby::SubmitPendingSnap 162 ======================== 163 */ 164 bool idLobby::SubmitPendingSnap( int p ) { 165 166 assert( lobbyType == GetActingGameStateLobbyType() ); 167 168 peer_t & peer = peers[p]; 169 170 if ( !peer.IsConnected() ) { 171 return false; 172 } 173 174 // If the peer doesn't have the latest resource list, send it to him before sending any new snapshots 175 if ( SendResources( p ) ) { 176 return false; 177 } 178 179 if ( !peer.loaded ) { 180 return false; 181 } 182 183 if ( !peer.snapProc->HasPendingSnap() ) { 184 return false; 185 } 186 187 int time = Sys_Milliseconds(); 188 189 int timeFromLastSub = time - peer.lastSnapJobTime; 190 191 int forceResendTime = session->GetTitleStorageInt( "net_snap_redundant_resend_in_ms", net_snap_redundant_resend_in_ms.GetInteger() ); 192 193 if ( timeFromLastSub < forceResendTime && peer.snapProc->IsBusyConfirmingPartialSnap() ) { 194 return false; 195 } 196 197 peer.lastSnapJobTime = time; 198 assert( !peer.snapProc->PendingSnapReadyToSend() ); 199 200 // Submit snapshot delta to jobs 201 peer.snapProc->SubmitPendingSnap( p + 1, objMemory, SNAP_OBJ_JOB_MEMORY, lzwData ); 202 203 NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" Submitted snapshot to jobList for peer %d. Since last jobsub: %d\n", p, timeFromLastSub ) ); 204 205 return true; 206 } 207 208 /* 209 ======================== 210 idLobby::SendCompletedPendingSnap 211 ======================== 212 */ 213 void idLobby::SendCompletedPendingSnap( int p ) { 214 215 assert( lobbyType == GetActingGameStateLobbyType() ); 216 217 int time = Sys_Milliseconds(); 218 219 peer_t & peer = peers[p]; 220 221 if ( !peer.IsConnected() ) { 222 return; 223 } 224 225 if ( peer.snapProc == NULL || !peer.snapProc->PendingSnapReadyToSend() ) { 226 return; 227 } 228 229 // If we have a pending snap ready to send, we better have a pending snap 230 assert( peer.snapProc->HasPendingSnap() ); 231 232 // Get the snap data blob now, even if we don't send it. 233 // This is somewhat wasteful, but we have to do this to keep the snap job pipe ready to keep doing work 234 // 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 235 byte buffer[ MAX_SNAP_SIZE ]; 236 int maxLength = sizeof( buffer ) - peer.packetProc->GetReliableDataSize() - 128; 237 238 int size = peer.snapProc->GetPendingSnapDelta( buffer, maxLength ); 239 240 if ( !CanSendMoreData( p ) ) { 241 return; 242 } 243 244 // Can't send anymore snapshots until all fragments are sent 245 if ( peer.packetProc->HasMoreFragments() ) { 246 return; 247 } 248 249 // If the peer doesn't have the latest resource list, send it to him before sending any new snapshots 250 if ( SendResources( p ) ) { 251 return; 252 } 253 254 int timeFromJobSub = time - peer.lastSnapJobTime; 255 int timeFromLastSend = time - peer.lastSnapTime; 256 257 if ( timeFromLastSend > 0 ) { 258 peer.snapHz = 1000.0f / (float)timeFromLastSend; 259 } else { 260 peer.snapHz = 0.0f; 261 } 262 263 if ( net_snapshot_send_warntime.GetInteger() > 0 && peer.lastSnapTime != 0 && net_snapshot_send_warntime.GetInteger() < timeFromLastSend ) { 264 idLib::Printf( "NET: Took %d ms to send peer %d snapshot\n", timeFromLastSend, p ); 265 } 266 267 if ( peer.throttleSnapsForXSeconds != 0 ) { 268 if ( time < peer.throttleSnapsForXSeconds ) { 269 return; 270 } 271 272 // If we were trying to recover ping, see if we succeeded 273 if ( peer.recoverPing != 0 ) { 274 if ( peer.lastPingRtt >= peer.recoverPing ) { 275 peer.failedPingRecoveries++; 276 } else { 277 const int peer_throttle_minSnapSeq = session->GetTitleStorageInt( "net_peer_throttle_minSnapSeq", net_peer_throttle_minSnapSeq.GetInteger() ); 278 if ( peer.snapProc->GetFullSnapBaseSequence() > idSnapshotProcessor::INITIAL_SNAP_SEQUENCE + peer_throttle_minSnapSeq ) { 279 // If throttling recovered the ping 280 int maxRate = common->GetSnapRate() * session->GetTitleStorageInt( "net_peer_throttle_maxSnapRate", net_peer_throttle_maxSnapRate.GetInteger() ); 281 peer.throttledSnapRate = idMath::ClampInt( common->GetSnapRate(), maxRate, peer.throttledSnapRate + common->GetSnapRate() ); 282 } 283 } 284 } 285 286 peer.throttleSnapsForXSeconds = 0; 287 } 288 289 peer.lastSnapTime = time; 290 291 if ( size != 0 ) { 292 if ( size > 0 ) { 293 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 ) ); 294 ProcessOutgoingMsg( p, buffer, size, false, 0 ); 295 } else if ( size < 0 ) { // Size < 0 indicates the delta buffer filled up 296 // There used to be code here that would disconnect peers if they were in game and filled up the buffer 297 // This was causing issues in the playtests we were running (Doom 4 MP) and after some conversation 298 // determined that it was not needed since a timeout mechanism has been added since 299 ProcessOutgoingMsg( p, buffer, -size, false, 0 ); 300 if ( peer.snapProc != NULL ) { 301 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 ); 302 } 303 } 304 } 305 306 // We calculate what our outgoing rate was for each sequence, so we can have a relative comparison 307 // for when the client reports what his downstream was in the same timeframe 308 if ( IsHost() && peer.snapProc != NULL && peer.snapProc->GetSnapSequence() > 0 ) { 309 //NET_VERBOSE_PRINT("^8 %i Rate: %.2f SnapSeq: %d GetBaseSequence: %d\n", lastAppendedSequence, peer.packetProc->GetOutgoingRateBytes(), peer.snapProc->GetSnapSequence(), peer.snapProc->GetBaseSequence() ); 310 peer.sentBpsHistory[ peer.snapProc->GetSnapSequence() % MAX_BPS_HISTORY ] = peer.packetProc->GetOutgoingRateBytes(); 311 } 312 } 313 314 /* 315 ======================== 316 idLobby::CheckPeerThrottle 317 ======================== 318 */ 319 void idLobby::CheckPeerThrottle( int p ) { 320 assert( lobbyType == GetActingGameStateLobbyType() ); 321 322 if ( !verify( p >= 0 && p < peers.Num() ) ) { 323 return; 324 } 325 326 peer_t & peer = peers[p]; 327 328 if ( !peer.IsConnected() ) { 329 return; 330 } 331 332 if ( !IsHost() ) { 333 return; 334 } 335 336 if ( session->GetTitleStorageInt( "net_peer_throttle_mode", net_peer_throttle_mode.GetInteger() ) == 0 ) { 337 return; 338 } 339 340 if ( peer.receivedBps < 0.0f ) { 341 return; 342 } 343 344 int time = Sys_Milliseconds(); 345 346 if ( !AllPeersHaveBaseState() ) { 347 return; 348 } 349 350 if ( verify( peer.snapProc != NULL ) ) { 351 const int peer_throttle_minSnapSeq = session->GetTitleStorageInt( "net_peer_throttle_minSnapSeq", net_peer_throttle_minSnapSeq.GetInteger() ); 352 if ( peer.snapProc->GetFullSnapBaseSequence() <= idSnapshotProcessor::INITIAL_SNAP_SEQUENCE + peer_throttle_minSnapSeq ) { 353 return; 354 } 355 } 356 357 // This is bps throttling which compares the sent bytes per second to the reported received bps 358 float peer_throttle_bps_host_threshold = session->GetTitleStorageFloat( "net_peer_throttle_bps_host_threshold", net_peer_throttle_bps_host_threshold.GetFloat() ); 359 360 if ( peer_throttle_bps_host_threshold > 0.0f ) { 361 int deltaT = idMath::ClampInt( 0, 100, time - peer.receivedThrottleTime ); 362 if ( deltaT > 0 && peer.receivedThrottleTime > 0 && peer.receivedBpsIndex > 0 ) { 363 364 bool throttled = false; 365 float sentBps = peer.sentBpsHistory[ peer.receivedBpsIndex % MAX_BPS_HISTORY ]; 366 367 // Min outgoing rate from server (don't throttle if we are sending < 1k) 368 if ( sentBps > peer_throttle_bps_host_threshold ) { 369 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 370 371 /* 372 static int lastSeq = 0; 373 if ( peer.receivedBpsIndex != lastSeq ) { 374 NET_VERBOSE_PRINT( "%ssentBpsHistory[%d] = %.2f received: %.2f PCT: %.2f \n", ( pct > 1.0f ? "^1" : "" ), peer.receivedBpsIndex, sentBps, peer.receivedBps, pct ); 375 } 376 lastSeq = peer.receivedBpsIndex; 377 */ 378 379 // Increase throttle time if peer is < % of what we are sending him 380 if ( pct < session->GetTitleStorageFloat( "net_peer_throttle_bps_peer_threshold_pct", net_peer_throttle_bps_peer_threshold_pct.GetFloat() ) ) { 381 peer.receivedThrottle += (float)deltaT; 382 throttled = true; 383 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() ); 384 } 385 } 386 387 if ( !throttled ) { 388 float decayRate = session->GetTitleStorageFloat( "net_peer_throttle_bps_decay", net_peer_throttle_bps_decay.GetFloat() ); 389 390 peer.receivedThrottle = Max<float>( 0.0f, peer.receivedThrottle - ( ( (float)deltaT ) * decayRate ) ); 391 //NET_VERBOSE_PRINT("NET: !throttled... %.2f ....receivedBps %.2f outgoingBps %.2f\n", peer.receivedThrottle, peer.receivedBps, sentBps ); 392 } 393 394 float duration = session->GetTitleStorageFloat( "net_peer_throttle_bps_duration", net_peer_throttle_bps_duration.GetFloat() ); 395 396 if ( peer.receivedThrottle > duration ) { 397 peer.maxSnapBps = peer.receivedBps * session->GetTitleStorageFloat( "net_snap_bw_test_throttle_max_scale", net_snap_bw_test_throttle_max_scale.GetFloat() ); 398 399 int maxRate = common->GetSnapRate() * session->GetTitleStorageInt( "net_peer_throttle_maxSnapRate", net_peer_throttle_maxSnapRate.GetInteger() ); 400 401 if ( peer.throttledSnapRate == 0 ) { 402 peer.throttledSnapRate = common->GetSnapRate() * 2; 403 } else if ( peer.throttledSnapRate < maxRate ) { 404 peer.throttledSnapRate = idMath::ClampInt( common->GetSnapRate(), maxRate, peer.throttledSnapRate + common->GetSnapRate() ); 405 } 406 407 peer.receivedThrottle = 0.0f; // Start over, so we don't immediately throttle again 408 } 409 } 410 peer.receivedThrottleTime = time; 411 } 412 } 413 414 /* 415 ======================== 416 idLobby::ApplySnapshotDelta 417 ======================== 418 */ 419 void idLobby::ApplySnapshotDelta( int p, int snapshotNumber ) { 420 assert( lobbyType == GetActingGameStateLobbyType() ); 421 422 if ( !verify( p >= 0 && p < peers.Num() ) ) { 423 return; 424 } 425 peer_t & peer = peers[p]; 426 if ( !peer.IsConnected() ) { 427 return; 428 } 429 430 if ( net_queueSnapAcks.GetBool() && AllPeersHaveBaseState() ) { 431 // If we've reached our queue limit, force the oldest one out now 432 if ( snapDeltaAckQueue.Num() == snapDeltaAckQueue.Max() ) { 433 ApplySnapshotDeltaInternal( snapDeltaAckQueue[0].p, snapDeltaAckQueue[0].snapshotNumber ); 434 snapDeltaAckQueue.RemoveIndex( 0 ); 435 } 436 437 // Queue up acks, so we can spread them out over frames to lighten the load when they all come in at once 438 snapDeltaAck_t snapDeltaAck; 439 440 snapDeltaAck.p = p; 441 snapDeltaAck.snapshotNumber = snapshotNumber; 442 443 snapDeltaAckQueue.Append( snapDeltaAck ); 444 } else { 445 ApplySnapshotDeltaInternal( p, snapshotNumber ); 446 } 447 } 448 449 /* 450 ======================== 451 idLobby::ApplySnapshotDeltaInternal 452 ======================== 453 */ 454 bool idLobby::ApplySnapshotDeltaInternal( int p, int snapshotNumber ) { 455 assert( lobbyType == GetActingGameStateLobbyType() ); 456 457 if ( !verify( p >= 0 && p < peers.Num() ) ) { 458 return false; 459 } 460 461 peer_t & peer = peers[p]; 462 463 if ( !peer.IsConnected() ) { 464 return false; 465 } 466 467 // on the server, player = peer number + 1, this only works as long as we don't support clients joining and leaving during game 468 // on the client, always 0 469 bool result = peer.snapProc->ApplySnapshotDelta( IsHost() ? p + 1 : 0, snapshotNumber ); 470 471 if ( result && IsHost() && peer.snapProc->HasPendingSnap() ) { 472 // Send more of the pending snap if we have one for this peer. 473 // The reason we can do this, is because we know more about this peers base state now. 474 // And since we maxed out the optimal snap delta size, we'll now be able 475 // to send more data, since we assume we'll get better and better delta compression as 476 // our version of this peers base state approaches parity with the peers actual state. 477 478 // We don't send immediately, since we have to coordinate sending snaps for all peers in same place considering jobs. 479 peer.needToSubmitPendingSnap = true; 480 NET_VERBOSESNAPSHOT_PRINT( "NET: Sent more unsent snapshot data to peer %d for snapshot %d\n", p, snapshotNumber ); 481 } 482 483 return result; 484 } 485 486 /* 487 ======================== 488 idLobby::SendSnapshotToPeer 489 ======================== 490 */ 491 idCVar net_forceDropSnap( "net_forceDropSnap", "0", CVAR_BOOL, "wait on snaps" ); 492 void idLobby::SendSnapshotToPeer( idSnapShot & ss, int p ) { 493 assert( lobbyType == GetActingGameStateLobbyType() ); 494 495 peer_t & peer = peers[p]; 496 497 if ( net_forceDropSnap.GetBool() ) { 498 net_forceDropSnap.SetBool( false ); 499 return; 500 } 501 502 if ( peer.pauseSnapshots ) { 503 return; 504 } 505 506 int time = Sys_Milliseconds(); 507 508 const int throttleMode = session->GetTitleStorageInt( "net_peer_throttle_mode", net_peer_throttle_mode.GetInteger() ); 509 510 // Real peer throttling based on performance 511 // -We throttle before sending to jobs rather than before sending 512 513 if ( ( throttleMode == 1 || throttleMode == 3 ) && peer.throttledSnapRate > 0 ) { 514 if ( time - peer.lastSnapJobTime < peer.throttledSnapRate / 1000 ) { // fixme /1000 515 // This peer is throttled, skip his snap shot 516 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 ) ); 517 return; 518 } 519 } 520 521 if ( throttleMode != 0 ) { 522 DetectSaturation( p ); 523 } 524 525 if ( peer.maxSnapBps >= 0.0f && ( throttleMode == 2 || throttleMode == 3 ) ) { 526 if ( peer.packetProc->GetOutgoingRateBytes() > peer.maxSnapBps ) { 527 return; 528 } 529 } 530 531 // TrySetPendingSnapshot will try to set the new pending snap. 532 // TrySetPendingSnapshot won't do anything until the last snap set was fully sent out. 533 534 if ( peer.snapProc->TrySetPendingSnapshot( ss ) ) { 535 NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" ^8Set next pending snapshot peer %d\n", 0 ) ); 536 537 peer.numSnapsSent++; 538 539 idSnapShot * baseState = peers[p].snapProc->GetBaseState(); 540 if ( verify( baseState != NULL ) ) { 541 baseState->UpdateExpectedSeq( peers[p].snapProc->GetSnapSequence() ); 542 } 543 544 } else { 545 NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" ^2FAILED Set next pending snapshot peer %d\n", 0 ) ); 546 } 547 548 // We send out the pending snap, which could be the most recent, or an old one that hasn't fully been sent 549 // We don't send immediately, since we have to coordinate sending snaps for all peers in same place considering jobs. 550 peer.needToSubmitPendingSnap = true; 551 } 552 553 /* 554 ======================== 555 idLobby::AllPeersHaveBaseState 556 ======================== 557 */ 558 bool idLobby::AllPeersHaveBaseState() { 559 assert( lobbyType == GetActingGameStateLobbyType() ); 560 561 for ( int i = 0; i < peers.Num(); ++i ) { 562 563 if ( !peers[i].IsConnected() ) { 564 continue; 565 } 566 567 if ( peers[i].snapProc->GetFullSnapBaseSequence() < idSnapshotProcessor::INITIAL_SNAP_SEQUENCE ) { 568 return false; // If a client hasn't ack'd his first full snap, then we are still sending base state to someone 569 } 570 } 571 572 return true; 573 } 574 575 /* 576 ======================== 577 idLobby::ThrottleSnapsForXSeconds 578 ======================== 579 */ 580 void idLobby::ThrottleSnapsForXSeconds( int p, int seconds, bool recoverPing ) { 581 assert( lobbyType == GetActingGameStateLobbyType() ); 582 583 if ( peers[p].throttleSnapsForXSeconds != 0 ) { 584 return; // Already throttling snaps 585 } 586 587 idLib::Printf( "Throttling peer %i for %i seconds...\n", p, seconds ); 588 589 peers[p].throttleSnapsForXSeconds = Sys_Milliseconds() + seconds * 1000; 590 peers[p].recoverPing = recoverPing ? peers[p].lastPingRtt : 0; 591 } 592 593 /* 594 ======================== 595 idLobby::FirstSnapHasBeenSent 596 ======================== 597 */ 598 bool idLobby::FirstSnapHasBeenSent( int p ) { 599 assert( lobbyType == GetActingGameStateLobbyType() ); 600 601 if ( !verify( p >= 0 && p < peers.Num() ) ) { 602 return false; 603 } 604 605 peer_t & peer = peers[p]; 606 607 if ( peer.numSnapsSent == 0 ) { 608 return false; 609 } 610 611 if ( peer.snapProc == NULL ) { 612 return false; 613 } 614 615 idSnapShot * ss = peer.snapProc->GetPendingSnap(); 616 617 if ( ss == NULL ) { 618 return false; 619 } 620 621 if ( ss->NumObjects() == 0 ) { 622 return false; 623 } 624 625 return true; 626 } 627 628 /* 629 ======================== 630 idLobby::EnsureAllPeersHaveBaseState 631 This function ensures all peers that started the match together (they were in the lobby when it started) start together. 632 Join in progress peers will be handled as they join. 633 ======================== 634 */ 635 bool idLobby::EnsureAllPeersHaveBaseState() { 636 637 assert( lobbyType == GetActingGameStateLobbyType() ); 638 639 int time = Sys_Milliseconds(); 640 641 642 for ( int i = 0; i < peers.Num(); ++i ) { 643 if ( !peers[i].IsConnected() ) { 644 continue; 645 } 646 647 if ( !FirstSnapHasBeenSent( i ) ) { 648 continue; // Must be join in progress peer 649 } 650 651 if ( peers[i].snapProc->GetFullSnapBaseSequence() < idSnapshotProcessor::INITIAL_SNAP_SEQUENCE ) { 652 if ( time - peers[i].lastSnapTime > session->GetTitleStorageInt( "net_snap_redundant_resend_in_ms", net_snap_redundant_resend_in_ms.GetInteger() ) ) { 653 SendSnapshotToPeer( *peers[i].snapProc->GetPendingSnap(), i ); 654 } 655 return false; 656 } 657 } 658 659 return true; 660 } 661 662 /* 663 ======================== 664 idLobby::AllPeersHaveStaleSnapObj 665 ======================== 666 */ 667 bool idLobby::AllPeersHaveStaleSnapObj( int objId ) { 668 assert( lobbyType == GetActingGameStateLobbyType() ); 669 670 for ( int i = 0; i < peers.Num(); i++ ) { 671 if ( !peers[i].IsConnected() ) { 672 continue; 673 } 674 675 idSnapShot * baseState = peers[i].snapProc->GetBaseState(); 676 677 idSnapShot::objectState_t * state = baseState->FindObjectByID( objId ); 678 679 if ( state == NULL || !state->stale ) { 680 return false; 681 } 682 } 683 return true; 684 } 685 686 /* 687 ======================== 688 idLobby::AllPeersHaveExpectedSnapObj 689 ======================== 690 */ 691 bool idLobby::AllPeersHaveExpectedSnapObj( int objId ) { 692 assert( lobbyType == GetActingGameStateLobbyType() ); 693 694 for ( int i = 0; i < peers.Num(); i++ ) { 695 if ( !peers[i].IsConnected() ) { 696 continue; 697 } 698 699 idSnapShot * baseState = peers[i].snapProc->GetBaseState(); 700 idSnapShot::objectState_t * state = baseState->FindObjectByID( objId ); 701 if ( state == NULL ) { 702 return false; 703 } 704 705 if ( state->expectedSequence == -2 ) { 706 return false; 707 } 708 709 if ( state->expectedSequence > 0 && peers[i].snapProc->GetFullSnapBaseSequence() <= state->expectedSequence ) { 710 //idLib::Printf("^3Not ready to go stale. obj %d Base: %d expected: %d\n", objId, peers[i].snapProc->GetBaseSequence(), state->expectedSequence ); 711 return false; 712 } 713 } 714 715 return true; 716 } 717 718 /* 719 ======================== 720 idLobby::MarkSnapObjDeleted 721 ======================== 722 */ 723 void idLobby::RefreshSnapObj( int objId ) { 724 assert( lobbyType == GetActingGameStateLobbyType() ); 725 726 for ( int i = 0; i < peers.Num(); i++ ) { 727 if ( !peers[i].IsConnected() ) { 728 continue; 729 } 730 731 idSnapShot * baseState = peers[i].snapProc->GetBaseState(); 732 idSnapShot::objectState_t * state = baseState->FindObjectByID( objId ); 733 if ( state != NULL ) { 734 // Setting to -2 will defer setting the expected sequence until the current snap is ready to send 735 state->expectedSequence = -2; 736 } 737 } 738 } 739 740 /* 741 ======================== 742 idLobby::MarkSnapObjDeleted 743 ======================== 744 */ 745 void idLobby::MarkSnapObjDeleted( int objId ) { 746 assert( lobbyType == GetActingGameStateLobbyType() ); 747 748 for ( int i = 0; i < peers.Num(); i++ ) { 749 if ( !peers[i].IsConnected() ) { 750 continue; 751 } 752 753 idSnapShot * baseState = peers[i].snapProc->GetBaseState(); 754 755 idSnapShot::objectState_t * state = baseState->FindObjectByID( objId ); 756 757 if ( state != NULL ) { 758 state->deleted = true; 759 } 760 } 761 } 762 763 /* 764 ======================== 765 idLobby::ResetBandwidthStats 766 ======================== 767 */ 768 void idLobby::ResetBandwidthStats() { 769 assert( lobbyType == GetActingGameStateLobbyType() ); 770 771 lastSnapBspHistoryUpdateSequence = -1; 772 773 for ( int p = 0; p < peers.Num(); p++ ) { 774 peers[p].maxSnapBps = -1.0f; 775 peers[p].throttledSnapRate = 0; 776 peers[p].rightBeforeSnapsPing = peers[p].lastPingRtt; 777 peers[p].throttleSnapsForXSeconds = 0; 778 peers[p].recoverPing = 0; 779 peers[p].failedPingRecoveries = 0; 780 peers[p].rightBeforeSnapsPing = 0; 781 782 } 783 } 784 785 /* 786 ======================== 787 idLobby::DetectSaturation 788 See if the ping shot up, which indicates a previously saturated connection 789 ======================== 790 */ 791 void idLobby::DetectSaturation( int p ) { 792 assert( lobbyType == GetActingGameStateLobbyType() ); 793 794 peer_t & peer = peers[p]; 795 796 if ( !peer.IsConnected() ) { 797 return; 798 } 799 800 const float pingIncPercentBeforeThottle = session->GetTitleStorageFloat( "net_pingIncPercentBeforeRecover", net_pingIncPercentBeforeRecover.GetFloat() ); 801 const int pingThreshold = session->GetTitleStorageInt( "net_min_ping_in_ms", net_min_ping_in_ms.GetInteger() ); 802 const int maxFailedPingRecoveries = session->GetTitleStorageInt( "net_maxFailedPingRecoveries", net_maxFailedPingRecoveries.GetInteger() ); 803 const int pingRecoveryThrottleTimeInSeconds = session->GetTitleStorageInt( "net_pingRecoveryThrottleTimeInSeconds", net_pingRecoveryThrottleTimeInSeconds.GetInteger() ); 804 805 if ( peer.lastPingRtt > peer.rightBeforeSnapsPing * pingIncPercentBeforeThottle && peer.lastPingRtt > pingThreshold ) { 806 if ( peer.failedPingRecoveries < maxFailedPingRecoveries ) { 807 ThrottleSnapsForXSeconds( p, pingRecoveryThrottleTimeInSeconds, true ); 808 } 809 } 810 } 811 812 /* 813 ======================== 814 idLobby::AddSnapObjTemplate 815 ======================== 816 */ 817 void idLobby::AddSnapObjTemplate( int objID, idBitMsg & msg ) { 818 assert( lobbyType == GetActingGameStateLobbyType() ); 819 820 // If we are in the middle of a SS read, apply this state to what we 821 // just deserialized (the obj we just deserialized is a delta from the template object we are adding right now) 822 if ( localReadSS != NULL ) { 823 localReadSS->ApplyToExistingState( objID, msg ); 824 } 825 826 // Add the template to the snapshot proc for future snapshot processing 827 for ( int p = 0; p < peers.Num(); p++ ) { 828 if ( !peers[p].IsConnected() || peers[p].snapProc == NULL ) { 829 continue; 830 } 831 peers[p].snapProc->AddSnapObjTemplate( objID, msg ); 832 } 833 }