diff --git a/code/qcommon/common.c b/code/qcommon/common.c index 7a57504d5..af49b9117 100644 --- a/code/qcommon/common.c +++ b/code/qcommon/common.c @@ -2692,6 +2692,27 @@ int Com_ModifyMsec( int msec ) { return msec; } +/* +================= +Com_TimeVal +================= +*/ + +int Com_TimeVal(int minMsec) +{ + int timeVal; + + timeVal = Sys_Milliseconds() - com_frameTime; + + if(timeVal >= minMsec) + timeVal = 0; + else + timeVal = minMsec - timeVal; + + return timeVal; +} + + /* ================= Com_Frame @@ -2700,7 +2721,8 @@ Com_Frame void Com_Frame( void ) { int msec, minMsec; - static int lastTime; + int timeVal, timeValSV; + static int lastTime = 0, bias = 0; int timeBeforeFirstEvents; int timeBeforeServer; @@ -2746,19 +2768,57 @@ void Com_Frame( void ) { timeBeforeFirstEvents = Sys_Milliseconds (); } - // we may want to spin here if things are going too fast - if ( !com_dedicated->integer && com_maxfps->integer > 0 && !com_timedemo->integer && !CL_IsDownloading()) { - minMsec = 1000 / com_maxfps->integer; - } else { - minMsec = 1; +// Figure out how much time we have + if(!com_timedemo->integer) + { + if(com_dedicated->integer) + minMsec = SV_FrameMsec(); + else + { + if(com_maxfps->integer > 0 && !com_timedemo->integer && !CL_IsDownloading()) + minMsec = 1000 / com_maxfps->integer; + else + minMsec = 1; + + timeVal = com_frameTime - lastTime; + bias += timeVal - minMsec; + + if(bias > minMsec) + bias = minMsec; + + // Adjust minMsec if previous frame took too long to render so + // that framerate is stable at the requested value. + minMsec -= bias; + } } - do { - com_frameTime = Com_EventLoop(); - if ( lastTime > com_frameTime ) { - lastTime = com_frameTime; // possible on first frame + else + minMsec = 1; + + do + { + if(com_sv_running->integer) + { + timeValSV = SV_SendQueuedPackets(); + + timeVal = Com_TimeVal(minMsec); + + if(timeValSV < timeVal) + timeVal = timeValSV; } - msec = com_frameTime - lastTime; - } while ( msec < minMsec ); + else + timeVal = Com_TimeVal(minMsec); + + if(timeVal < 1) + NET_Sleep(0); + else + NET_Sleep(timeVal - 1); + } while(Com_TimeVal(minMsec)); + + lastTime = com_frameTime; + com_frameTime = Com_EventLoop(); + + msec = com_frameTime - lastTime; + Cbuf_Execute (); if (com_altivec->modified) @@ -2767,10 +2827,7 @@ void Com_Frame( void ) { com_altivec->modified = qfalse; } - lastTime = com_frameTime; - // mess with msec if needed - com_frameMsec = msec; msec = Com_ModifyMsec( msec ); // @@ -2828,6 +2885,8 @@ void Com_Frame( void ) { } } + NET_FlushPacketQueue(); + // // report timing information // diff --git a/code/qcommon/net_chan.c b/code/qcommon/net_chan.c index 31c57393c..a4499b6ec 100644 --- a/code/qcommon/net_chan.c +++ b/code/qcommon/net_chan.c @@ -214,6 +214,10 @@ void Netchan_TransmitNextFragment( netchan_t *chan ) { // send the datagram NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); + // Store send time and size of this packet for rate control + chan->lastSentTime = Sys_Milliseconds(); + chan->lastSentSize = send.cursize; + if ( showpackets->integer ) { Com_Printf ("%s send %4i : s=%i fragment=%i,%i\n" , netsrcString[ chan->sock ] diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h index 7ecd2c7b4..62c1e3db6 100644 --- a/code/qcommon/qcommon.h +++ b/code/qcommon/qcommon.h @@ -205,6 +205,9 @@ typedef struct { int unsentFragmentStart; int unsentLength; byte unsentBuffer[MAX_MSGLEN]; + + int lastSentTime; + int lastSentSize; } netchan_t; void Netchan_Init( int qport ); @@ -950,7 +953,9 @@ void SV_Init( void ); void SV_Shutdown( char *finalmsg ); void SV_Frame( int msec ); void SV_PacketEvent( netadr_t from, msg_t *msg ); +int SV_FrameMsec(void); qboolean SV_GameCommand( void ); +int SV_SendQueuedPackets(void); // diff --git a/code/server/server.h b/code/server/server.h index 79c76beb9..3dbb0f686 100644 --- a/code/server/server.h +++ b/code/server/server.h @@ -326,6 +326,7 @@ void SV_RemoveOperatorCommands (void); void SV_MasterHeartbeat (void); void SV_MasterShutdown (void); +int SV_RateMsec(client_t *client); @@ -368,6 +369,8 @@ void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ); void SV_ClientThink (client_t *cl, usercmd_t *cmd); void SV_WriteDownloadToClient( client_t *cl , msg_t *msg ); +int SV_SendDownloadMessages(void); +int SV_SendQueuedMessages(void); // // sv_ccmds.c @@ -474,6 +477,6 @@ void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, con // sv_net_chan.c // void SV_Netchan_Transmit( client_t *client, msg_t *msg); -void SV_Netchan_TransmitNextFragment( client_t *client ); +int SV_Netchan_TransmitNextFragment( client_t *client ); qboolean SV_Netchan_Process( client_t *client, msg_t *msg ); diff --git a/code/server/sv_client.c b/code/server/sv_client.c index fadb8f921..9c3571380 100644 --- a/code/server/sv_client.c +++ b/code/server/sv_client.c @@ -1059,6 +1059,40 @@ void SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) } } + +/* +================== +SV_SendQueuedMessages + +Send one round of fragments, or queued messages to all clients that have data pending. +Return the shortest time interval for sending next packet to client +================== +*/ + +int SV_SendQueuedMessages(void) +{ + int i, retval = -1, nextFragT; + client_t *cl; + + for(i=0; i < sv_maxclients->integer; i++) + { + cl = &svs.clients[i]; + + if(cl->state) + { + nextFragT = SV_RateMsec(cl); + + if(!nextFragT) + nextFragT = SV_Netchan_TransmitNextFragment(cl); + + if(nextFragT >= 0 && (retval == -1 || retval > nextFragT)) + retval = nextFragT; + } + } + + return retval; +} + /* ================= SV_Disconnect_f diff --git a/code/server/sv_main.c b/code/server/sv_main.c index 4d540ee2e..30d454059 100644 --- a/code/server/sv_main.c +++ b/code/server/sv_main.c @@ -974,6 +974,30 @@ qboolean SV_CheckPaused( void ) { return qtrue; } + +/* +================== +SV_FrameMsec +Return time in millseconds until processing of the next server frame. +================== +*/ +int SV_FrameMsec() +{ + if(sv_fps) + { + int frameMsec; + + frameMsec = 1000.0f / sv_fps->value; + + if(frameMsec < sv.timeResidual) + return 0; + else + return frameMsec - sv.timeResidual; + } + else + return 1; +} + /* ================== SV_Frame @@ -1104,5 +1128,74 @@ void SV_Frame( int msec ) { SV_MasterHeartbeat(); } +/* +==================== +SV_RateMsec + +Return the number of msec until another message can be sent to +a client based on its rate settings +==================== +*/ + +#define UDPIP6_HEADER_SIZE 48 + +int SV_RateMsec(client_t *client) +{ + int rate, rateMsec; + int messageSize; + + messageSize = client->netchan.lastSentSize; + rate = client->rate; + + if(sv_maxRate->integer) + { + if(sv_maxRate->integer < 1000) + Cvar_Set( "sv_MaxRate", "1000" ); + if(sv_maxRate->integer < rate) + rate = sv_maxRate->integer; + } + + if(sv_minRate->integer) + { + if(sv_minRate->integer < 1000) + Cvar_Set("sv_minRate", "1000"); + if(sv_minRate->integer > rate) + rate = sv_minRate->integer; + } + + messageSize += UDPIP6_HEADER_SIZE; + + rateMsec = messageSize * 1000 / ((int) (rate * com_timescale->value)); + rate = Sys_Milliseconds() - client->netchan.lastSentTime; + + if(rate > rateMsec) + return 0; + else + return rateMsec - rate; +} + +/* +==================== +SV_SendQueuedPackets + +Send download messages and queued packets in the time that we're idle, i.e. +not computing a server frame or sending client snapshots. +Return the time in msec until we expect to be called next +==================== +*/ + +int SV_SendQueuedPackets() +{ + int delayT; + int timeVal = INT_MAX; + + // Send out fragmented packets now that we're idle + delayT = SV_SendQueuedMessages(); + if(delayT >= 0) + timeVal = delayT; + + return timeVal; +} + //============================================================================ diff --git a/code/server/sv_net_chan.c b/code/server/sv_net_chan.c index 0ad6eef80..9f1d0c3cf 100644 --- a/code/server/sv_net_chan.c +++ b/code/server/sv_net_chan.c @@ -127,37 +127,58 @@ static void SV_Netchan_Decode( client_t *client, msg_t *msg ) { } } +/* +================= +SV_Netchan_TransmitNextInQueue +================= +*/ +void SV_Netchan_TransmitNextInQueue(client_t *client) +{ + netchan_buffer_t *netbuf; + + Com_DPrintf("#462 Netchan_TransmitNextFragment: popping a queued message for transmit\n"); + netbuf = client->netchan_start_queue; + + SV_Netchan_Encode(client, &netbuf->msg); + + Netchan_Transmit(&client->netchan, netbuf->msg.cursize, netbuf->msg.data); + + // pop from queue + client->netchan_start_queue = netbuf->next; + if(!client->netchan_start_queue) + { + Com_DPrintf("#462 Netchan_TransmitNextFragment: emptied queue\n"); + client->netchan_end_queue = &client->netchan_start_queue; + } + else + Com_DPrintf("#462 Netchan_TransmitNextFragment: remaining queued message\n"); + + Z_Free(netbuf); +} + /* ================= SV_Netchan_TransmitNextFragment +Transmit the next fragment and the next queued packet +Return number of ms until next message can be sent based on throughput given by client rate, +-1 if no packet was sent. ================= */ -void SV_Netchan_TransmitNextFragment( client_t *client ) { - Netchan_TransmitNextFragment( &client->netchan ); - if (!client->netchan.unsentFragments) + +int SV_Netchan_TransmitNextFragment(client_t *client) +{ + if(client->netchan.unsentFragments) { - // make sure the netchan queue has been properly initialized (you never know) - if ((!client->netchan_end_queue) && (client->state >= CS_CONNECTED)) { - Com_Error(ERR_DROP, "netchan queue is not properly initialized in SV_Netchan_TransmitNextFragment\n"); - } - // the last fragment was transmitted, check wether we have queued messages - if (client->netchan_start_queue) { - netchan_buffer_t *netbuf; - Com_DPrintf("#462 Netchan_TransmitNextFragment: popping a queued message for transmit\n"); - netbuf = client->netchan_start_queue; - SV_Netchan_Encode( client, &netbuf->msg ); - Netchan_Transmit( &client->netchan, netbuf->msg.cursize, netbuf->msg.data ); - // pop from queue - client->netchan_start_queue = netbuf->next; - if (!client->netchan_start_queue) { - Com_DPrintf("#462 Netchan_TransmitNextFragment: emptied queue\n"); - client->netchan_end_queue = &client->netchan_start_queue; - } - else - Com_DPrintf("#462 Netchan_TransmitNextFragment: remaining queued message\n"); - Z_Free(netbuf); - } - } + Netchan_TransmitNextFragment(&client->netchan); + return SV_RateMsec(client); + } + else if(client->netchan_start_queue) + { + SV_Netchan_TransmitNextInQueue(client); + return SV_RateMsec(client); + } + + return -1; } diff --git a/code/server/sv_snapshot.c b/code/server/sv_snapshot.c index 81ab55d19..2deff1ba9 100644 --- a/code/server/sv_snapshot.c +++ b/code/server/sv_snapshot.c @@ -546,44 +546,6 @@ static void SV_BuildClientSnapshot( client_t *client ) { } -/* -==================== -SV_RateMsec - -Return the number of msec a given size message is supposed -to take to clear, based on the current rate -==================== -*/ -#define HEADER_RATE_BYTES 48 // include our header, IP header, and some overhead -static int SV_RateMsec( client_t *client, int messageSize ) { - int rate; - int rateMsec; - - // individual messages will never be larger than fragment size - if ( messageSize > 1500 ) { - messageSize = 1500; - } - rate = client->rate; - if ( sv_maxRate->integer ) { - if ( sv_maxRate->integer < 1000 ) { - Cvar_Set( "sv_MaxRate", "1000" ); - } - if ( sv_maxRate->integer < rate ) { - rate = sv_maxRate->integer; - } - } - if ( sv_minRate->integer ) { - if ( sv_minRate->integer < 1000 ) - Cvar_Set( "sv_minRate", "1000" ); - if ( sv_minRate->integer > rate ) - rate = sv_minRate->integer; - } - - rateMsec = ( messageSize + HEADER_RATE_BYTES ) * 1000 / ((int) (rate * com_timescale->value)); - - return rateMsec; -} - /* ======================= SV_SendMessageToClient @@ -618,7 +580,7 @@ void SV_SendMessageToClient( msg_t *msg, client_t *client ) { } // normal rate / snapshotMsec calculation - rateMsec = SV_RateMsec(client, msg->cursize); + rateMsec = SV_RateMsec(client); if ( rateMsec < client->snapshotMsec * com_timescale->value) { // never send more packets than this, no matter what the rate is at @@ -712,7 +674,7 @@ void SV_SendClientMessages( void ) { // was too large to send at once if ( c->netchan.unsentFragments ) { c->nextSnapshotTime = svs.time + - SV_RateMsec( c, c->netchan.unsentLength - c->netchan.unsentFragmentStart ); + SV_RateMsec( c); SV_Netchan_TransmitNextFragment( c ); continue; }