/* Netchan_CanReliable Returns true if the bandwidth choke isn't */ qboolean Netchan_CanReliable (netchan_t *chan) { if (chan->reliable_length) return false; // waiting for ack return Netchan_CanPacket (chan); }
/* =================== CL_WritePacket Create and send the command packet to the server Including both the reliable commands and the usercmds =================== */ void CL_WritePacket( void ) { sizebuf_t buf; qboolean send_command = false; byte data[MAX_CMD_BUFFER]; int i, from, to, key, size; int numbackup = 2; int numcmds; int newcmds; int cmdnumber; // don't send anything if playing back a demo if( cls.demoplayback || cls.state == ca_cinematic ) return; if( cls.state == ca_disconnected || cls.state == ca_connecting ) return; CL_ComputePacketLoss (); #ifndef _DEBUG if( cl_cmdrate->value < MIN_CMD_RATE ) { Cvar_SetFloat( "cl_cmdrate", MIN_CMD_RATE ); } #endif Q_memset( data, 0, MAX_CMD_BUFFER ); BF_Init( &buf, "ClientData", data, sizeof( data )); // Determine number of backup commands to send along numbackup = bound( 0, cl_cmdbackup->integer, MAX_BACKUP_COMMANDS ); if( cls.state == ca_connected ) numbackup = 0; // Check to see if we can actually send this command // In single player, send commands as fast as possible // Otherwise, only send when ready and when not choking bandwidth if(( cl.maxclients == 1 ) || ( NET_IsLocalAddress( cls.netchan.remote_address ) && !host_limitlocal->integer )) send_command = true; else if(( host.realtime >= cls.nextcmdtime ) && Netchan_CanPacket( &cls.netchan )) send_command = true; if( cl.force_send_usercmd ) { send_command = true; cl.force_send_usercmd = false; } if(( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged ) >= CL_UPDATE_MASK ) { if(( host.realtime - cls.netchan.last_received ) > CONNECTION_PROBLEM_TIME ) { Con_NPrintf( 1, "^3Warning:^1 Connection Problem^7\n" ); cl.validsequence = 0; } } if( cl_nodelta->integer ) { cl.validsequence = 0; } // send a userinfo update if needed if( userinfo->modified ) { BF_WriteByte( &cls.netchan.message, clc_userinfo ); BF_WriteString( &cls.netchan.message, Cvar_Userinfo( )); } if( send_command ) { int outgoing_sequence; if( cl_cmdrate->integer > 0 ) cls.nextcmdtime = host.realtime + ( 1.0f / cl_cmdrate->value ); else cls.nextcmdtime = host.realtime; // always able to send right away if( cls.lastoutgoingcommand == -1 ) { outgoing_sequence = cls.netchan.outgoing_sequence; cls.lastoutgoingcommand = cls.netchan.outgoing_sequence; } else outgoing_sequence = cls.lastoutgoingcommand + 1; // begin a client move command BF_WriteByte( &buf, clc_move ); // save the position for a checksum byte key = BF_GetRealBytesWritten( &buf ); BF_WriteByte( &buf, 0 ); // write packet lossage percentation BF_WriteByte( &buf, cls.packet_loss ); // say how many backups we'll be sending BF_WriteByte( &buf, numbackup ); // how many real commands have queued up newcmds = ( cls.netchan.outgoing_sequence - cls.lastoutgoingcommand ); // put an upper/lower bound on this newcmds = bound( 0, newcmds, MAX_TOTAL_CMDS ); if( cls.state == ca_connected ) newcmds = 0; BF_WriteByte( &buf, newcmds ); numcmds = newcmds + numbackup; from = -1; for( i = numcmds - 1; i >= 0; i-- ) { cmdnumber = ( cls.netchan.outgoing_sequence - i ) & CL_UPDATE_MASK; to = cmdnumber; CL_WriteUsercmd( &buf, from, to ); from = to; if( BF_CheckOverflow( &buf )) Host_Error( "CL_Move, overflowed command buffer (%i bytes)\n", MAX_CMD_BUFFER ); } // calculate a checksum over the move commands size = BF_GetRealBytesWritten( &buf ) - key - 1; buf.pData[key] = CRC32_BlockSequence( buf.pData + key + 1, size, cls.netchan.outgoing_sequence ); // message we are constructing. i = cls.netchan.outgoing_sequence & CL_UPDATE_MASK; // determine if we need to ask for a new set of delta's. if( cl.validsequence && (cls.state == ca_active) && !( cls.demorecording && cls.demowaiting )) { cl.delta_sequence = cl.validsequence; BF_WriteByte( &buf, clc_delta ); BF_WriteByte( &buf, cl.validsequence & 0xFF ); } else { // request delta compression of entities cl.delta_sequence = -1; } if( BF_CheckOverflow( &buf )) { Host_Error( "CL_Move, overflowed command buffer (%i bytes)\n", MAX_CMD_BUFFER ); } // remember outgoing command that we are sending cls.lastoutgoingcommand = cls.netchan.outgoing_sequence; // composite the rest of the datagram.. if( BF_GetNumBitsWritten( &cls.datagram ) <= BF_GetNumBitsLeft( &buf )) BF_WriteBits( &buf, BF_GetData( &cls.datagram ), BF_GetNumBitsWritten( &cls.datagram )); BF_Clear( &cls.datagram ); // deliver the message (or update reliable) Netchan_Transmit( &cls.netchan, BF_GetNumBytesWritten( &buf ), BF_GetData( &buf )); } else { // increment sequence number so we can detect that we've held back packets. cls.netchan.outgoing_sequence++; } if( cls.demorecording ) { // Back up one because we've incremented outgoing_sequence each frame by 1 unit cmdnumber = ( cls.netchan.outgoing_sequence - 1 ) & CL_UPDATE_MASK; CL_WriteDemoUserCmd( cmdnumber ); } // update download/upload slider. Netchan_UpdateProgress( &cls.netchan ); }
/* ======================= SV_SendClientMessages ======================= */ void SV_SendClientMessages( void ) { sv_client_t *cl; int i; svs.currentPlayer = NULL; svs.currentPlayerNum = 0; if( sv.state == ss_dead ) return; SV_UpdateToReliableMessages (); // send a message to each connected client for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( !cl->state || cl->fakeclient ) continue; if( cl->skip_message ) { cl->skip_message = false; continue; } if( !host_limitlocal->integer && NET_IsLocalAddress( cl->netchan.remote_address )) cl->send_message = true; if( cl->state == cs_spawned ) { // Try to send a message as soon as we can. // If the target time for sending is within the next frame interval ( based on last frame ), // trigger the send now. Note that in single player, // send_message is also set to true any time a packet arrives from the client. float time_unti_next_message = cl->next_messagetime - (host.realtime + host.frametime); if( time_unti_next_message <= 0.0f ) cl->send_message = true; // something got hosed if( time_unti_next_message > 2.0f ) cl->send_message = true; } // if the reliable message overflowed, drop the client if( BF_CheckOverflow( &cl->netchan.message )) { BF_Clear( &cl->netchan.message ); BF_Clear( &cl->datagram ); SV_BroadcastPrintf( PRINT_HIGH, "%s overflowed\n", cl->name ); MsgDev( D_WARN, "reliable overflow for %s\n", cl->name ); SV_DropClient( cl ); cl->send_message = true; cl->netchan.cleartime = 0; // don't choke this message } else if( cl->send_message ) { // If we haven't gotten a message in sv_failuretime seconds, then stop sending messages to this client // until we get another packet in from the client. This prevents crash/drop and reconnect where they are // being hosed with "sequenced packet without connection" packets. if(( host.realtime - cl->netchan.last_received ) > sv_failuretime->value ) cl->send_message = false; } // only send messages if the client has sent one // and the bandwidth is not choked if( !cl->send_message ) continue; // Bandwidth choke active? if( !Netchan_CanPacket( &cl->netchan )) { cl->chokecount++; continue; } cl->send_message = false; // Now that we were able to send, reset timer to point to next possible send time. cl->next_messagetime = host.realtime + host.frametime + cl->cl_updaterate; if( cl->state == cs_spawned ) { SV_SendClientDatagram( cl ); } else { // just update reliable Netchan_Transmit( &cl->netchan, 0, NULL ); } } // reset current client svs.currentPlayer = NULL; svs.currentPlayerNum = 0; }