DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

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 }