123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834 |
- /*
- ===========================================================================
- Doom 3 BFG Edition GPL Source Code
- Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
- This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
- Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
- 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.
- 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.
- ===========================================================================
- */
- #pragma hdrstop
- #include "../idlib/precompiled.h"
- #include "sys_lobby.h"
- 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." );
- idCVar net_queueSnapAcks( "net_queueSnapAcks", "1", CVAR_BOOL, "" );
- idCVar net_peer_throttle_mode( "net_peer_throttle_mode", "0", CVAR_INTEGER, "= 0 off, 1 = enable fixed, 2 = absolute, 3 = both" );
- idCVar net_peer_throttle_minSnapSeq( "net_peer_throttle_minSnapSeq", "150", CVAR_INTEGER, "Minumum number of snapshot exchanges before throttling can be triggered" );
- 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" );
- 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" );
- 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" );
- 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" );
- idCVar net_peer_throttle_maxSnapRate( "net_peer_throttle_maxSnapRate", "4", CVAR_INTEGER, "Highest factor of server base snapRate that a client can be throttled" );
- 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" );
- 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" );
- 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" );
- idCVar net_pingIncPercentBeforeRecover( "net_pingIncPercentBeforeRecover", "1.3", CVAR_FLOAT, "Percentage change increase of ping before we try to recover" );
- idCVar net_maxFailedPingRecoveries( "net_maxFailedPingRecoveries", "10", CVAR_INTEGER, "Max failed ping recoveries before we stop trying" );
- idCVar net_pingRecoveryThrottleTimeInSeconds( "net_pingRecoveryThrottleTimeInSeconds", "3", CVAR_INTEGER, "Throttle snaps for this amount of time in seconds to recover from ping spike" );
- idCVar net_peer_timeout_loading( "net_peer_timeout_loading", "90000", CVAR_INTEGER, "time in MS to disconnect clients during loading - production only" );
- /*
- ========================
- idLobby::UpdateSnaps
- ========================
- */
- void idLobby::UpdateSnaps() {
-
- assert( lobbyType == GetActingGameStateLobbyType() );
- SCOPED_PROFILE_EVENT( "UpdateSnaps" );
- #if 0
- uint64 startTimeMicroSec = Sys_Microseconds();
- #endif
-
- haveSubmittedSnaps = false;
- if ( !SendCompletedSnaps() ) {
- // If we weren't able to send all the submitted snaps, we need to wait till we can.
- // We can't start new jobs until they are all sent out.
- return;
- }
- for ( int p = 0; p < peers.Num(); p++ ) {
- peer_t & peer = peers[p];
-
- if ( !peer.IsConnected() ) {
- continue;
- }
- if ( peer.needToSubmitPendingSnap ) {
- // Submit the snap
- if ( SubmitPendingSnap( p ) ) {
- peer.needToSubmitPendingSnap = false; // only clear this if we actually submitted the snap
- }
-
- }
- }
- #if 0
- uint64 endTimeMicroSec = Sys_Microseconds();
- if ( endTimeMicroSec - startTimeMicroSec > 200 ) { // .2 ms
- idLib::Printf( "NET: UpdateSnaps time in ms: %f\n", (float)( endTimeMicroSec - startTimeMicroSec ) / 1000.0f);
- }
- #endif
- }
- /*
- ========================
- idLobby::SendCompletedSnaps
- This function will send send off any previously submitted pending snaps if they are ready
- ========================
- */
- bool idLobby::SendCompletedSnaps() {
- assert( lobbyType == GetActingGameStateLobbyType() );
- bool sentAllSubmitted = true;
-
- for ( int p = 0; p < peers.Num(); p++ ) {
- peer_t & peer = peers[p];
-
- if ( !peer.IsConnected() ) {
- continue;
- }
-
- if ( peer.snapProc->PendingSnapReadyToSend() ) {
- // Check to see if there are any snaps that were submitted that need to be sent out
- SendCompletedPendingSnap( p );
- } else if ( IsHost() ) {
- NET_VERBOSESNAPSHOT_PRINT_LEVEL( 7, va(" ^8Peer %d pendingSnap not ready to send\n", p) );
- }
- if ( !peer.IsConnected() ) { // peer may have been dropped in "SendCompletedPendingSnap". ugh.
- continue;
- }
-
- if ( peer.snapProc->PendingSnapReadyToSend() ) {
- // If we still have a submitted snap, we know we're not done
- sentAllSubmitted = false;
- if ( IsHost() ) {
- NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" ^2Peer %d did not send all submitted snapshots.\n", p) );
- }
- }
- }
-
- return sentAllSubmitted;
- }
- /*
- ========================
- idLobby::SendResources
- ========================
- */
- bool idLobby::SendResources( int p ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- return false;
- }
- /*
- ========================
- idLobby::SubmitPendingSnap
- ========================
- */
- bool idLobby::SubmitPendingSnap( int p ) {
-
- assert( lobbyType == GetActingGameStateLobbyType() );
- peer_t & peer = peers[p];
- if ( !peer.IsConnected() ) {
- return false;
- }
- // If the peer doesn't have the latest resource list, send it to him before sending any new snapshots
- if ( SendResources( p ) ) {
- return false;
- }
- if ( !peer.loaded ) {
- return false;
- }
- if ( !peer.snapProc->HasPendingSnap() ) {
- return false;
- }
- int time = Sys_Milliseconds();
- int timeFromLastSub = time - peer.lastSnapJobTime;
- int forceResendTime = session->GetTitleStorageInt( "net_snap_redundant_resend_in_ms", net_snap_redundant_resend_in_ms.GetInteger() );
- if ( timeFromLastSub < forceResendTime && peer.snapProc->IsBusyConfirmingPartialSnap() ) {
- return false;
- }
- peer.lastSnapJobTime = time;
- assert( !peer.snapProc->PendingSnapReadyToSend() );
-
- // Submit snapshot delta to jobs
- peer.snapProc->SubmitPendingSnap( p + 1, objMemory, SNAP_OBJ_JOB_MEMORY, lzwData );
- NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" Submitted snapshot to jobList for peer %d. Since last jobsub: %d\n", p, timeFromLastSub ) );
-
- return true;
- }
- /*
- ========================
- idLobby::SendCompletedPendingSnap
- ========================
- */
- void idLobby::SendCompletedPendingSnap( int p ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- int time = Sys_Milliseconds();
-
- peer_t & peer = peers[p];
-
- if ( !peer.IsConnected() ) {
- return;
- }
-
- if ( peer.snapProc == NULL || !peer.snapProc->PendingSnapReadyToSend() ) {
- return;
- }
- // If we have a pending snap ready to send, we better have a pending snap
- assert( peer.snapProc->HasPendingSnap() );
-
- // Get the snap data blob now, even if we don't send it.
- // This is somewhat wasteful, but we have to do this to keep the snap job pipe ready to keep doing work
- // 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
- byte buffer[ MAX_SNAP_SIZE ];
- int maxLength = sizeof( buffer ) - peer.packetProc->GetReliableDataSize() - 128;
- int size = peer.snapProc->GetPendingSnapDelta( buffer, maxLength );
- if ( !CanSendMoreData( p ) ) {
- return;
- }
- // Can't send anymore snapshots until all fragments are sent
- if ( peer.packetProc->HasMoreFragments() ) {
- return;
- }
- // If the peer doesn't have the latest resource list, send it to him before sending any new snapshots
- if ( SendResources( p ) ) {
- return;
- }
- int timeFromJobSub = time - peer.lastSnapJobTime;
- int timeFromLastSend = time - peer.lastSnapTime;
- if ( timeFromLastSend > 0 ) {
- peer.snapHz = 1000.0f / (float)timeFromLastSend;
- } else {
- peer.snapHz = 0.0f;
- }
- if ( net_snapshot_send_warntime.GetInteger() > 0 && peer.lastSnapTime != 0 && net_snapshot_send_warntime.GetInteger() < timeFromLastSend ) {
- idLib::Printf( "NET: Took %d ms to send peer %d snapshot\n", timeFromLastSend, p );
- }
- if ( peer.throttleSnapsForXSeconds != 0 ) {
- if ( time < peer.throttleSnapsForXSeconds ) {
- return;
- }
- // If we were trying to recover ping, see if we succeeded
- if ( peer.recoverPing != 0 ) {
- if ( peer.lastPingRtt >= peer.recoverPing ) {
- peer.failedPingRecoveries++;
- } else {
- const int peer_throttle_minSnapSeq = session->GetTitleStorageInt( "net_peer_throttle_minSnapSeq", net_peer_throttle_minSnapSeq.GetInteger() );
- if ( peer.snapProc->GetFullSnapBaseSequence() > idSnapshotProcessor::INITIAL_SNAP_SEQUENCE + peer_throttle_minSnapSeq ) {
- // If throttling recovered the ping
- int maxRate = common->GetSnapRate() * session->GetTitleStorageInt( "net_peer_throttle_maxSnapRate", net_peer_throttle_maxSnapRate.GetInteger() );
- peer.throttledSnapRate = idMath::ClampInt( common->GetSnapRate(), maxRate, peer.throttledSnapRate + common->GetSnapRate() );
- }
- }
- }
- peer.throttleSnapsForXSeconds = 0;
- }
- peer.lastSnapTime = time;
- if ( size != 0 ) {
- if ( size > 0 ) {
- 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 ) );
- ProcessOutgoingMsg( p, buffer, size, false, 0 );
- } else if ( size < 0 ) { // Size < 0 indicates the delta buffer filled up
- // There used to be code here that would disconnect peers if they were in game and filled up the buffer
- // This was causing issues in the playtests we were running (Doom 4 MP) and after some conversation
- // determined that it was not needed since a timeout mechanism has been added since
- ProcessOutgoingMsg( p, buffer, -size, false, 0 );
- if ( peer.snapProc != NULL ) {
- 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 );
- }
- }
- }
- // We calculate what our outgoing rate was for each sequence, so we can have a relative comparison
- // for when the client reports what his downstream was in the same timeframe
- if ( IsHost() && peer.snapProc != NULL && peer.snapProc->GetSnapSequence() > 0 ) {
- //NET_VERBOSE_PRINT("^8 %i Rate: %.2f SnapSeq: %d GetBaseSequence: %d\n", lastAppendedSequence, peer.packetProc->GetOutgoingRateBytes(), peer.snapProc->GetSnapSequence(), peer.snapProc->GetBaseSequence() );
- peer.sentBpsHistory[ peer.snapProc->GetSnapSequence() % MAX_BPS_HISTORY ] = peer.packetProc->GetOutgoingRateBytes();
- }
- }
- /*
- ========================
- idLobby::CheckPeerThrottle
- ========================
- */
- void idLobby::CheckPeerThrottle( int p ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- if ( !verify( p >= 0 && p < peers.Num() ) ) {
- return;
- }
- peer_t & peer = peers[p];
- if ( !peer.IsConnected() ) {
- return;
- }
- if ( !IsHost() ) {
- return;
- }
- if ( session->GetTitleStorageInt( "net_peer_throttle_mode", net_peer_throttle_mode.GetInteger() ) == 0 ) {
- return;
- }
-
- if ( peer.receivedBps < 0.0f ) {
- return;
- }
- int time = Sys_Milliseconds();
- if ( !AllPeersHaveBaseState() ) {
- return;
- }
- if ( verify( peer.snapProc != NULL ) ) {
- const int peer_throttle_minSnapSeq = session->GetTitleStorageInt( "net_peer_throttle_minSnapSeq", net_peer_throttle_minSnapSeq.GetInteger() );
- if ( peer.snapProc->GetFullSnapBaseSequence() <= idSnapshotProcessor::INITIAL_SNAP_SEQUENCE + peer_throttle_minSnapSeq ) {
- return;
- }
- }
- // This is bps throttling which compares the sent bytes per second to the reported received bps
- float peer_throttle_bps_host_threshold = session->GetTitleStorageFloat( "net_peer_throttle_bps_host_threshold", net_peer_throttle_bps_host_threshold.GetFloat() );
- if ( peer_throttle_bps_host_threshold > 0.0f ) {
- int deltaT = idMath::ClampInt( 0, 100, time - peer.receivedThrottleTime );
- if ( deltaT > 0 && peer.receivedThrottleTime > 0 && peer.receivedBpsIndex > 0 ) {
- bool throttled = false;
- float sentBps = peer.sentBpsHistory[ peer.receivedBpsIndex % MAX_BPS_HISTORY ];
-
- // Min outgoing rate from server (don't throttle if we are sending < 1k)
- if ( sentBps > peer_throttle_bps_host_threshold ) {
- 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
- /*
- static int lastSeq = 0;
- if ( peer.receivedBpsIndex != lastSeq ) {
- NET_VERBOSE_PRINT( "%ssentBpsHistory[%d] = %.2f received: %.2f PCT: %.2f \n", ( pct > 1.0f ? "^1" : "" ), peer.receivedBpsIndex, sentBps, peer.receivedBps, pct );
- }
- lastSeq = peer.receivedBpsIndex;
- */
- // Increase throttle time if peer is < % of what we are sending him
- if ( pct < session->GetTitleStorageFloat( "net_peer_throttle_bps_peer_threshold_pct", net_peer_throttle_bps_peer_threshold_pct.GetFloat() ) ) {
- peer.receivedThrottle += (float)deltaT;
- throttled = true;
- 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() );
- }
- }
- if ( !throttled ) {
- float decayRate = session->GetTitleStorageFloat( "net_peer_throttle_bps_decay", net_peer_throttle_bps_decay.GetFloat() );
- peer.receivedThrottle = Max<float>( 0.0f, peer.receivedThrottle - ( ( (float)deltaT ) * decayRate ) );
- //NET_VERBOSE_PRINT("NET: !throttled... %.2f ....receivedBps %.2f outgoingBps %.2f\n", peer.receivedThrottle, peer.receivedBps, sentBps );
- }
- float duration = session->GetTitleStorageFloat( "net_peer_throttle_bps_duration", net_peer_throttle_bps_duration.GetFloat() );
- if ( peer.receivedThrottle > duration ) {
- peer.maxSnapBps = peer.receivedBps * session->GetTitleStorageFloat( "net_snap_bw_test_throttle_max_scale", net_snap_bw_test_throttle_max_scale.GetFloat() );
- int maxRate = common->GetSnapRate() * session->GetTitleStorageInt( "net_peer_throttle_maxSnapRate", net_peer_throttle_maxSnapRate.GetInteger() );
- if ( peer.throttledSnapRate == 0 ) {
- peer.throttledSnapRate = common->GetSnapRate() * 2;
- } else if ( peer.throttledSnapRate < maxRate ) {
- peer.throttledSnapRate = idMath::ClampInt( common->GetSnapRate(), maxRate, peer.throttledSnapRate + common->GetSnapRate() );
- }
- peer.receivedThrottle = 0.0f; // Start over, so we don't immediately throttle again
- }
- }
- peer.receivedThrottleTime = time;
- }
- }
- /*
- ========================
- idLobby::ApplySnapshotDelta
- ========================
- */
- void idLobby::ApplySnapshotDelta( int p, int snapshotNumber ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- if ( !verify( p >= 0 && p < peers.Num() ) ) {
- return;
- }
- peer_t & peer = peers[p];
- if ( !peer.IsConnected() ) {
- return;
- }
- if ( net_queueSnapAcks.GetBool() && AllPeersHaveBaseState() ) {
- // If we've reached our queue limit, force the oldest one out now
- if ( snapDeltaAckQueue.Num() == snapDeltaAckQueue.Max() ) {
- ApplySnapshotDeltaInternal( snapDeltaAckQueue[0].p, snapDeltaAckQueue[0].snapshotNumber );
- snapDeltaAckQueue.RemoveIndex( 0 );
- }
- // Queue up acks, so we can spread them out over frames to lighten the load when they all come in at once
- snapDeltaAck_t snapDeltaAck;
- snapDeltaAck.p = p;
- snapDeltaAck.snapshotNumber = snapshotNumber;
- snapDeltaAckQueue.Append( snapDeltaAck );
- } else {
- ApplySnapshotDeltaInternal( p, snapshotNumber );
- }
- }
- /*
- ========================
- idLobby::ApplySnapshotDeltaInternal
- ========================
- */
- bool idLobby::ApplySnapshotDeltaInternal( int p, int snapshotNumber ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- if ( !verify( p >= 0 && p < peers.Num() ) ) {
- return false;
- }
- peer_t & peer = peers[p];
- if ( !peer.IsConnected() ) {
- return false;
- }
- // on the server, player = peer number + 1, this only works as long as we don't support clients joining and leaving during game
- // on the client, always 0
- bool result = peer.snapProc->ApplySnapshotDelta( IsHost() ? p + 1 : 0, snapshotNumber );
-
- if ( result && IsHost() && peer.snapProc->HasPendingSnap() ) {
- // Send more of the pending snap if we have one for this peer.
- // The reason we can do this, is because we know more about this peers base state now.
- // And since we maxed out the optimal snap delta size, we'll now be able
- // to send more data, since we assume we'll get better and better delta compression as
- // our version of this peers base state approaches parity with the peers actual state.
-
- // We don't send immediately, since we have to coordinate sending snaps for all peers in same place considering jobs.
- peer.needToSubmitPendingSnap = true;
- NET_VERBOSESNAPSHOT_PRINT( "NET: Sent more unsent snapshot data to peer %d for snapshot %d\n", p, snapshotNumber );
- }
- return result;
- }
- /*
- ========================
- idLobby::SendSnapshotToPeer
- ========================
- */
- idCVar net_forceDropSnap( "net_forceDropSnap", "0", CVAR_BOOL, "wait on snaps" );
- void idLobby::SendSnapshotToPeer( idSnapShot & ss, int p ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- peer_t & peer = peers[p];
- if ( net_forceDropSnap.GetBool() ) {
- net_forceDropSnap.SetBool( false );
- return;
- }
- if ( peer.pauseSnapshots ) {
- return;
- }
- int time = Sys_Milliseconds();
- const int throttleMode = session->GetTitleStorageInt( "net_peer_throttle_mode", net_peer_throttle_mode.GetInteger() );
- // Real peer throttling based on performance
- // -We throttle before sending to jobs rather than before sending
- if ( ( throttleMode == 1 || throttleMode == 3 ) && peer.throttledSnapRate > 0 ) {
- if ( time - peer.lastSnapJobTime < peer.throttledSnapRate / 1000 ) { // fixme /1000
- // This peer is throttled, skip his snap shot
- 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 ) );
- return;
- }
- }
- if ( throttleMode != 0 ) {
- DetectSaturation( p );
- }
- if ( peer.maxSnapBps >= 0.0f && ( throttleMode == 2 || throttleMode == 3 ) ) {
- if ( peer.packetProc->GetOutgoingRateBytes() > peer.maxSnapBps ) {
- return;
- }
- }
- // TrySetPendingSnapshot will try to set the new pending snap.
- // TrySetPendingSnapshot won't do anything until the last snap set was fully sent out.
- if ( peer.snapProc->TrySetPendingSnapshot( ss ) ) {
- NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" ^8Set next pending snapshot peer %d\n", 0 ) );
- peer.numSnapsSent++;
- idSnapShot * baseState = peers[p].snapProc->GetBaseState();
- if ( verify( baseState != NULL ) ) {
- baseState->UpdateExpectedSeq( peers[p].snapProc->GetSnapSequence() );
- }
- } else {
- NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" ^2FAILED Set next pending snapshot peer %d\n", 0 ) );
- }
- // We send out the pending snap, which could be the most recent, or an old one that hasn't fully been sent
- // We don't send immediately, since we have to coordinate sending snaps for all peers in same place considering jobs.
- peer.needToSubmitPendingSnap = true;
- }
- /*
- ========================
- idLobby::AllPeersHaveBaseState
- ========================
- */
- bool idLobby::AllPeersHaveBaseState() {
- assert( lobbyType == GetActingGameStateLobbyType() );
- for ( int i = 0; i < peers.Num(); ++i ) {
-
- if ( !peers[i].IsConnected() ) {
- continue;
- }
- if ( peers[i].snapProc->GetFullSnapBaseSequence() < idSnapshotProcessor::INITIAL_SNAP_SEQUENCE ) {
- return false; // If a client hasn't ack'd his first full snap, then we are still sending base state to someone
- }
- }
- return true;
- }
- /*
- ========================
- idLobby::ThrottleSnapsForXSeconds
- ========================
- */
- void idLobby::ThrottleSnapsForXSeconds( int p, int seconds, bool recoverPing ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- if ( peers[p].throttleSnapsForXSeconds != 0 ) {
- return; // Already throttling snaps
- }
- idLib::Printf( "Throttling peer %i for %i seconds...\n", p, seconds );
- peers[p].throttleSnapsForXSeconds = Sys_Milliseconds() + seconds * 1000;
- peers[p].recoverPing = recoverPing ? peers[p].lastPingRtt : 0;
- }
- /*
- ========================
- idLobby::FirstSnapHasBeenSent
- ========================
- */
- bool idLobby::FirstSnapHasBeenSent( int p ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- if ( !verify( p >= 0 && p < peers.Num() ) ) {
- return false;
- }
- peer_t & peer = peers[p];
- if ( peer.numSnapsSent == 0 ) {
- return false;
- }
- if ( peer.snapProc == NULL ) {
- return false;
- }
- idSnapShot * ss = peer.snapProc->GetPendingSnap();
- if ( ss == NULL ) {
- return false;
- }
- if ( ss->NumObjects() == 0 ) {
- return false;
- }
- return true;
- }
- /*
- ========================
- idLobby::EnsureAllPeersHaveBaseState
- This function ensures all peers that started the match together (they were in the lobby when it started) start together.
- Join in progress peers will be handled as they join.
- ========================
- */
- bool idLobby::EnsureAllPeersHaveBaseState() {
- assert( lobbyType == GetActingGameStateLobbyType() );
- int time = Sys_Milliseconds();
- for ( int i = 0; i < peers.Num(); ++i ) {
- if ( !peers[i].IsConnected() ) {
- continue;
- }
- if ( !FirstSnapHasBeenSent( i ) ) {
- continue; // Must be join in progress peer
- }
- if ( peers[i].snapProc->GetFullSnapBaseSequence() < idSnapshotProcessor::INITIAL_SNAP_SEQUENCE ) {
- if ( time - peers[i].lastSnapTime > session->GetTitleStorageInt( "net_snap_redundant_resend_in_ms", net_snap_redundant_resend_in_ms.GetInteger() ) ) {
- SendSnapshotToPeer( *peers[i].snapProc->GetPendingSnap(), i );
- }
- return false;
- }
- }
- return true;
- }
- /*
- ========================
- idLobby::AllPeersHaveStaleSnapObj
- ========================
- */
- bool idLobby::AllPeersHaveStaleSnapObj( int objId ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- for ( int i = 0; i < peers.Num(); i++ ) {
- if ( !peers[i].IsConnected() ) {
- continue;
- }
- idSnapShot * baseState = peers[i].snapProc->GetBaseState();
- idSnapShot::objectState_t * state = baseState->FindObjectByID( objId );
- if ( state == NULL || !state->stale ) {
- return false;
- }
- }
- return true;
- }
- /*
- ========================
- idLobby::AllPeersHaveExpectedSnapObj
- ========================
- */
- bool idLobby::AllPeersHaveExpectedSnapObj( int objId ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- for ( int i = 0; i < peers.Num(); i++ ) {
- if ( !peers[i].IsConnected() ) {
- continue;
- }
- idSnapShot * baseState = peers[i].snapProc->GetBaseState();
- idSnapShot::objectState_t * state = baseState->FindObjectByID( objId );
- if ( state == NULL ) {
- return false;
- }
- if ( state->expectedSequence == -2 ) {
- return false;
- }
- if ( state->expectedSequence > 0 && peers[i].snapProc->GetFullSnapBaseSequence() <= state->expectedSequence ) {
- //idLib::Printf("^3Not ready to go stale. obj %d Base: %d expected: %d\n", objId, peers[i].snapProc->GetBaseSequence(), state->expectedSequence );
- return false;
- }
- }
- return true;
- }
- /*
- ========================
- idLobby::MarkSnapObjDeleted
- ========================
- */
- void idLobby::RefreshSnapObj( int objId ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- for ( int i = 0; i < peers.Num(); i++ ) {
- if ( !peers[i].IsConnected() ) {
- continue;
- }
-
- idSnapShot * baseState = peers[i].snapProc->GetBaseState();
- idSnapShot::objectState_t * state = baseState->FindObjectByID( objId );
- if ( state != NULL ) {
- // Setting to -2 will defer setting the expected sequence until the current snap is ready to send
- state->expectedSequence = -2;
- }
- }
- }
- /*
- ========================
- idLobby::MarkSnapObjDeleted
- ========================
- */
- void idLobby::MarkSnapObjDeleted( int objId ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- for ( int i = 0; i < peers.Num(); i++ ) {
- if ( !peers[i].IsConnected() ) {
- continue;
- }
- idSnapShot * baseState = peers[i].snapProc->GetBaseState();
- idSnapShot::objectState_t * state = baseState->FindObjectByID( objId );
- if ( state != NULL ) {
- state->deleted = true;
- }
- }
- }
- /*
- ========================
- idLobby::ResetBandwidthStats
- ========================
- */
- void idLobby::ResetBandwidthStats() {
- assert( lobbyType == GetActingGameStateLobbyType() );
- lastSnapBspHistoryUpdateSequence = -1;
- for ( int p = 0; p < peers.Num(); p++ ) {
- peers[p].maxSnapBps = -1.0f;
- peers[p].throttledSnapRate = 0;
- peers[p].rightBeforeSnapsPing = peers[p].lastPingRtt;
- peers[p].throttleSnapsForXSeconds = 0;
- peers[p].recoverPing = 0;
- peers[p].failedPingRecoveries = 0;
- peers[p].rightBeforeSnapsPing = 0;
- }
- }
- /*
- ========================
- idLobby::DetectSaturation
- See if the ping shot up, which indicates a previously saturated connection
- ========================
- */
- void idLobby::DetectSaturation( int p ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- peer_t & peer = peers[p];
- if ( !peer.IsConnected() ) {
- return;
- }
- const float pingIncPercentBeforeThottle = session->GetTitleStorageFloat( "net_pingIncPercentBeforeRecover", net_pingIncPercentBeforeRecover.GetFloat() );
- const int pingThreshold = session->GetTitleStorageInt( "net_min_ping_in_ms", net_min_ping_in_ms.GetInteger() );
- const int maxFailedPingRecoveries = session->GetTitleStorageInt( "net_maxFailedPingRecoveries", net_maxFailedPingRecoveries.GetInteger() );
- const int pingRecoveryThrottleTimeInSeconds = session->GetTitleStorageInt( "net_pingRecoveryThrottleTimeInSeconds", net_pingRecoveryThrottleTimeInSeconds.GetInteger() );
- if ( peer.lastPingRtt > peer.rightBeforeSnapsPing * pingIncPercentBeforeThottle && peer.lastPingRtt > pingThreshold ) {
- if ( peer.failedPingRecoveries < maxFailedPingRecoveries ) {
- ThrottleSnapsForXSeconds( p, pingRecoveryThrottleTimeInSeconds, true );
- }
- }
- }
- /*
- ========================
- idLobby::AddSnapObjTemplate
- ========================
- */
- void idLobby::AddSnapObjTemplate( int objID, idBitMsg & msg ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- // If we are in the middle of a SS read, apply this state to what we
- // just deserialized (the obj we just deserialized is a delta from the template object we are adding right now)
- if ( localReadSS != NULL ) {
- localReadSS->ApplyToExistingState( objID, msg );
- }
- // Add the template to the snapshot proc for future snapshot processing
- for ( int p = 0; p < peers.Num(); p++ ) {
- if ( !peers[p].IsConnected() || peers[p].snapProc == NULL ) {
- continue;
- }
- peers[p].snapProc->AddSnapObjTemplate( objID, msg );
- }
- }
|