return;
cl.cmd.time = cl.mtime[0];
}
+
// don't let it fall behind if CL_SendMove hasn't been called recently
// (such is the case when framerate is too low for instance)
lastsendtime = bound(realtime, lastsendtime + packettime, realtime + packettime);
// set the flag indicating that we sent a packet recently
cl.movement_needupdate = false;
+ // don't send a new input packet if the connection is still saturated from
+ // the last one (or chat messages, etc)
+ // note: this behavior comes from QW
+ if (!NetConn_CanSend(cls.netcon))
+ return;
buf.maxsize = sizeof(data);
buf.cursize = 0;
checksumindex = buf.cursize;
MSG_WriteByte(&buf, 0);
// packet loss percentage
- for (j = 0, packetloss = 0;j < 100;j++)
- packetloss += cls.netcon->packetlost[j];
+ for (j = 0, packetloss = 0;j < NETGRAPH_PACKETS;j++)
+ if (cls.netcon->incoming_unreliablesize[i] == NETGRAPH_LOSTPACKET)
+ packetloss++;
+ packetloss = packetloss * 100 / NETGRAPH_PACKETS;
MSG_WriteByte(&buf, packetloss);
// write most recent 3 moves
QW_MSG_WriteDeltaUsercmd(&buf, &nullcmd, &cl.movecmd[2]);
{
if (cl.latestframenums[i] > 0)
{
- if (developer_networkentities.integer >= 1)
+ if (developer_networkentities.integer >= 10)
Con_Printf("send clc_ackframe %i\n", cl.latestframenums[i]);
MSG_WriteByte(&buf, clc_ackframe);
MSG_WriteLong(&buf, cl.latestframenums[i]);
}
// send the reliable message (forwarded commands) if there is one
- NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol);
+ NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, max(20*(buf.cursize+40), cl_rate.integer));
if (cls.netcon->message.overflowed)
{
Con_DPrint("Sending clc_disconnect\n");
MSG_WriteByte(&buf, clc_disconnect);
}
- NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol);
- NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol);
- NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol);
+ NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, 10000);
+ NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, 10000);
+ NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, 10000);
NetConn_Close(cls.netcon);
cls.netcon = NULL;
}
msg.data = buf;
msg.maxsize = sizeof(buf);
MSG_WriteChar(&msg, clc_nop);
- NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol);
+ NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol, 10000);
}
}
cvar_t scr_zoomwindow_fov = {CVAR_SAVE, "scr_zoomwindow_fov", "20", "fov of zoom window"};
cvar_t scr_stipple = {0, "scr_stipple", "0", "interlacing-like stippling of the display"};
cvar_t scr_refresh = {0, "scr_refresh", "1", "allows you to completely shut off rendering for benchmarking purposes"};
+cvar_t shownetgraph = {CVAR_SAVE, "shownetgraph", "0", "shows a graph of packet sizes and other information"};
int jpeg_supported = false;
SCR_DrawCenterString ();
}
+void SCR_DrawNetGraph_DrawGraph (int graphx, int graphy, int barwidth, int barheight, int bardivide, const char *label, float textsize, int packetcounter, int numparameters, const int **parameters, const float parametercolors[][4])
+{
+ int j, k, x, y, index, offset, height;
+ // dim background
+ DrawQ_Pic (graphx, graphy, NULL, barwidth * NETGRAPH_PACKETS, barheight + textsize, 0, 0, 0, 0.5, 0);
+ // draw a label
+ DrawQ_String (graphx, graphy + barheight, label, 0, textsize, textsize, 1, 1, 1, 1, 0);
+ // draw the bar graph itself
+ for (j = 0;j < NETGRAPH_PACKETS;j++)
+ {
+ x = graphx + j * barwidth;
+ y = graphy + barheight;
+ index = (packetcounter + j) % NETGRAPH_PACKETS;
+ if (parameters[0][index] == NETGRAPH_LOSTPACKET)
+ DrawQ_Pic(x, y - barheight, NULL, barwidth, barheight, 1, 0, 0, 1, 0);
+ else if (parameters[0][index] == NETGRAPH_CHOKEDPACKET)
+ DrawQ_Pic(x, y - min(2, barheight), NULL, barwidth, min(2, barheight), 1, 1, 0, 1, 0);
+ else
+ {
+ offset = 0;
+ for (k = 0;k < numparameters;k++)
+ {
+ height = (parameters[k][index] + bardivide - 1) / bardivide;
+ height = min(height, barheight - offset);
+ offset += height;
+ if (height)
+ DrawQ_Pic(x, y - offset, NULL, barwidth, height, parametercolors[k][0], parametercolors[k][1], parametercolors[k][2], parametercolors[k][3], 0);
+ }
+ }
+ }
+}
+
+const float netgraphcolors[3][4] =
+{
+ {1 , 0.5, 0 , 1},
+ {1 , 1 , 1 , 1},
+ {0 , 1 , 0 , 1},
+};
+
+void SCR_DrawNetGraph_DrawConnection (netconn_t *conn, int graphx, int graphy, int barwidth, int barheight, int bardivide, const char *labelincoming, int separator, const char *labeloutgoing, float textsize)
+{
+ int numparameters;
+ const int *parameters[3];
+ numparameters = 3;
+ parameters[0] = conn->incoming_unreliablesize;
+ parameters[1] = conn->incoming_reliablesize;
+ parameters[2] = conn->incoming_acksize;
+ SCR_DrawNetGraph_DrawGraph(graphx, graphy, barwidth, barheight, bardivide, labelincoming, textsize, conn->incoming_packetcounter, numparameters, parameters, netgraphcolors);
+ parameters[0] = conn->outgoing_unreliablesize;
+ parameters[1] = conn->outgoing_reliablesize;
+ parameters[2] = conn->outgoing_acksize;
+ SCR_DrawNetGraph_DrawGraph(graphx + barwidth * NETGRAPH_PACKETS + separator, graphy, barwidth, barheight, bardivide, labeloutgoing, textsize, conn->outgoing_packetcounter, numparameters, parameters, netgraphcolors);
+}
+
+/*
+==============
+SCR_DrawNetGraph
+==============
+*/
+void SCR_DrawNetGraph (void)
+{
+ int separator, barwidth, barheight, bardivide, netgraph_x, netgraph_y, textsize;
+
+ if (cls.state != ca_connected)
+ return;
+ if (!cls.netcon)
+ return;
+ if (!shownetgraph.integer)
+ return;
+
+ separator = 4;
+ textsize = 8;
+ barwidth = 1;
+ barheight = 50;
+ bardivide = 20;
+ netgraph_x = 0;
+ netgraph_y = vid_conheight.integer - 48 - barheight - textsize;
+ SCR_DrawNetGraph_DrawConnection(cls.netcon, netgraph_x, netgraph_y, barwidth, barheight, bardivide, "incoming", separator, "outgoing", textsize);
+}
+
/*
==============
SCR_DrawTurtle
Cvar_RegisterVariable(&scr_zoomwindow_fov);
Cvar_RegisterVariable(&scr_stipple);
Cvar_RegisterVariable(&scr_refresh);
+ Cvar_RegisterVariable(&shownetgraph);
Cmd_AddCommand ("sizeup",SCR_SizeUp_f, "increase view size (increases viewsize cvar)");
Cmd_AddCommand ("sizedown",SCR_SizeDown_f, "decrease view size (decreases viewsize cvar)");
SHOWLMP_drawall();
SCR_CheckDrawCenterString();
}
+ SCR_DrawNetGraph ();
MR_Draw();
CL_DrawVideo();
R_Shadow_EditLights_DrawSelectedLightProperties();
buf.data = bufdata;
buf.maxsize = sizeof(bufdata);
MSG_WriteByte(&buf, svc_disconnect);
- NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol);
- NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol);
- NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol);
+ NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000);
+ NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000);
+ NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000);
}
// break the net connection
NetConn_Close(host_client->netconnection);
{
packetloss = 0;
if (svs.clients[i].netconnection)
- for (j = 0;j < 100;j++)
- packetloss += svs.clients[i].netconnection->packetlost[j];
+ for (j = 0;j < NETGRAPH_PACKETS;j++)
+ if (svs.clients[i].netconnection->incoming_unreliablesize[j] == NETGRAPH_LOSTPACKET)
+ packetloss++;
+ packetloss = packetloss * 100 / NETGRAPH_PACKETS;
ping = (int)floor(svs.clients[i].ping*1000+0.5);
ping = bound(0, ping, 9999);
if (sv.protocol == PROTOCOL_QUAKEWORLD)
return NetConn_Write(mysocket, string, (int)strlen(string), peeraddress);
}
-int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolversion_t protocol)
+qboolean NetConn_CanSend(netconn_t *conn)
{
+ conn->outgoing_packetcounter = (conn->outgoing_packetcounter + 1) % NETGRAPH_PACKETS;
+ conn->outgoing_unreliablesize[conn->outgoing_packetcounter] = NETGRAPH_NOPACKET;
+ conn->outgoing_reliablesize[conn->outgoing_packetcounter] = NETGRAPH_NOPACKET;
+ conn->outgoing_acksize[conn->outgoing_packetcounter] = NETGRAPH_NOPACKET;
+ if (realtime > conn->cleartime)
+ return true;
+ else
+ {
+ conn->outgoing_unreliablesize[conn->outgoing_packetcounter] = NETGRAPH_CHOKEDPACKET;
+ return false;
+ }
+}
+
+int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolversion_t protocol, int rate)
+{
+ int totallen = 0;
+
+ // if this packet was supposedly choked, but we find ourselves sending one
+ // anyway, make sure the size counting starts at zero
+ // (this mostly happens on level changes and disconnects and such)
+ if (conn->outgoing_unreliablesize[conn->outgoing_packetcounter] == NETGRAPH_CHOKEDPACKET)
+ conn->outgoing_unreliablesize[conn->outgoing_packetcounter] = NETGRAPH_NOPACKET;
+
if (protocol == PROTOCOL_QUAKEWORLD)
{
int packetLen;
Con_Printf ("NetConn_SendUnreliableMessage: reliable message too big %u\n", data->cursize);
return -1;
}
+
+ conn->outgoing_unreliablesize[conn->outgoing_packetcounter] += packetLen;
+
// add the reliable message if there is one
if (sendreliable)
{
+ conn->outgoing_reliablesize[conn->outgoing_packetcounter] += conn->sendMessageLength;
memcpy(sendbuffer + packetLen, conn->sendMessage, conn->sendMessageLength);
packetLen += conn->sendMessageLength;
conn->qw.last_reliable_sequence = conn->qw.outgoing_sequence;
}
+
// add the unreliable message if possible
if (packetLen + data->cursize <= 1400)
{
+ conn->outgoing_unreliablesize[conn->outgoing_packetcounter] += data->cursize;
memcpy(sendbuffer + packetLen, data->data, data->cursize);
packetLen += data->cursize;
}
packetsSent++;
unreliableMessagesSent++;
- // delay later packets to obey rate limit
- conn->qw.cleartime = max(conn->qw.cleartime, realtime) + packetLen * conn->qw.rate;
-
- return 0;
+ totallen += packetLen + 18;
}
else
{
header[1] = BigLong(conn->nq.sendSequence - 1);
memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen);
+ conn->outgoing_reliablesize[conn->outgoing_packetcounter] += packetLen;
+
if (NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress) == (int)packetLen)
{
conn->lastSendTime = realtime;
packetsReSent++;
}
+
+ totallen += packetLen + 18;
}
// if we have a new reliable message to send, do so
conn->nq.sendSequence++;
+ conn->outgoing_reliablesize[conn->outgoing_packetcounter] += packetLen;
+
NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress);
conn->lastSendTime = realtime;
packetsSent++;
reliableMessagesSent++;
+
+ totallen += packetLen + 18;
}
// if we have an unreliable message to send, do so
- if (data->cursize)
+ //if (data->cursize)
{
packetLen = NET_HEADERSIZE + data->cursize;
conn->nq.unreliableSendSequence++;
+ conn->outgoing_unreliablesize[conn->outgoing_packetcounter] += packetLen;
+
NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress);
packetsSent++;
unreliableMessagesSent++;
+
+ if (data->cursize)
+ totallen += packetLen + 18;
}
- return 0;
}
+
+ // delay later packets to obey rate limit
+ if (conn->cleartime < realtime - 0.1)
+ conn->cleartime = realtime - 0.1;
+ conn->cleartime = conn->cleartime + (double)totallen / (double)rate;
+ if (conn->cleartime < realtime)
+ conn->cleartime = realtime;
+
+ return 0;
}
void NetConn_CloseClientPorts(void)
static int NetConn_ReceivedMessage(netconn_t *conn, unsigned char *data, int length, protocolversion_t protocol, double newtimeout)
{
+ int originallength = length;
if (length < 8)
return 0;
//Con_DPrintf("Dropped %u datagram(s)\n", count);
while (count--)
{
- conn->packetlost[conn->packetlostcounter] = true;
- conn->packetlostcounter = (conn->packetlostcounter + 1) % 100;
+ conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS;
+ conn->incoming_unreliablesize[conn->incoming_packetcounter] = NETGRAPH_LOSTPACKET;
+ conn->incoming_reliablesize[conn->incoming_packetcounter] = NETGRAPH_NOPACKET;
+ conn->incoming_acksize[conn->incoming_packetcounter] = NETGRAPH_NOPACKET;
}
}
- conn->packetlost[conn->packetlostcounter] = false;
- conn->packetlostcounter = (conn->packetlostcounter + 1) % 100;
+ conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS;
+ conn->incoming_unreliablesize[conn->incoming_packetcounter] = originallength;
+ conn->incoming_reliablesize[conn->incoming_packetcounter] = NETGRAPH_NOPACKET;
+ conn->incoming_acksize[conn->incoming_packetcounter] = NETGRAPH_NOPACKET;
if (reliable_ack == conn->qw.reliable_sequence)
{
// received, now we will be able to send another reliable message
//Con_DPrintf("Dropped %u datagram(s)\n", count);
while (count--)
{
- conn->packetlost[conn->packetlostcounter] = true;
- conn->packetlostcounter = (conn->packetlostcounter + 1) % 100;
+ conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS;
+ conn->incoming_unreliablesize[conn->incoming_packetcounter] = NETGRAPH_LOSTPACKET;
+ conn->incoming_reliablesize[conn->incoming_packetcounter] = NETGRAPH_NOPACKET;
+ conn->incoming_acksize[conn->incoming_packetcounter] = NETGRAPH_NOPACKET;
}
}
- conn->packetlost[conn->packetlostcounter] = false;
- conn->packetlostcounter = (conn->packetlostcounter + 1) % 100;
+ conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS;
+ conn->incoming_unreliablesize[conn->incoming_packetcounter] = originallength;
+ conn->incoming_reliablesize[conn->incoming_packetcounter] = NETGRAPH_NOPACKET;
+ conn->incoming_acksize[conn->incoming_packetcounter] = NETGRAPH_NOPACKET;
conn->nq.unreliableReceiveSequence = sequence + 1;
conn->lastMessageTime = realtime;
conn->timeout = realtime + newtimeout;
}
else if (flags & NETFLAG_ACK)
{
+ conn->incoming_acksize[conn->incoming_packetcounter] += originallength;
if (sequence == (conn->nq.sendSequence - 1))
{
if (sequence == conn->nq.ackSequence)
else if (flags & NETFLAG_DATA)
{
unsigned int temppacket[2];
+ conn->incoming_reliablesize[conn->incoming_packetcounter] += originallength;
+ conn->outgoing_acksize[conn->outgoing_packetcounter] += 8;
temppacket[0] = BigLong(8 | NETFLAG_ACK);
temppacket[1] = BigLong(sequence);
NetConn_Write(conn->mysocket, (unsigned char *)temppacket, 8, &conn->peeraddress);
msg.data = buf;
msg.maxsize = sizeof(buf);
MSG_WriteChar(&msg, clc_nop);
- NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol);
+ NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol, 10000);
}
}
int qport;
- // bandwidth estimator
- double cleartime; // if realtime > nc->cleartime, free to go
- double rate; // seconds / byte
-
// sequencing variables
int incoming_sequence;
int incoming_acknowledged;
}
qw;
- // this tracks which of the last 100 received packet sequence numbers were lost
- int packetlostcounter;
- unsigned char packetlost[100];
+ // bandwidth estimator
+ double cleartime; // if realtime > nc->cleartime, free to go
+
+ // this tracks packet loss and packet sizes on the most recent packets
+ // used by shownetgraph feature
+#define NETGRAPH_PACKETS 100
+#define NETGRAPH_NOPACKET 0
+#define NETGRAPH_LOSTPACKET -1
+#define NETGRAPH_CHOKEDPACKET -2
+ int incoming_packetcounter;
+ int incoming_reliablesize[NETGRAPH_PACKETS];
+ int incoming_unreliablesize[NETGRAPH_PACKETS];
+ int incoming_acksize[NETGRAPH_PACKETS];
+ int outgoing_packetcounter;
+ int outgoing_reliablesize[NETGRAPH_PACKETS];
+ int outgoing_unreliablesize[NETGRAPH_PACKETS];
+ int outgoing_acksize[NETGRAPH_PACKETS];
char address[128];
} netconn_t;
extern cvar_t net_address;
//extern cvar_t net_netaddress_ipv6;
-int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolversion_t protocol);
+qboolean NetConn_CanSend(netconn_t *conn);
+int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolversion_t protocol, int rate);
void NetConn_CloseClientPorts(void);
void NetConn_OpenClientPorts(void);
void NetConn_CloseServerPorts(void);
static unsigned char sv_sendclientdatagram_buf[NET_MAXMESSAGE]; // FIXME?
void SV_SendClientDatagram (client_t *client)
{
- int rate, maxrate, maxsize, maxsize2, downloadsize;
+ int clientrate, maxrate, maxsize, maxsize2, downloadsize;
sizebuf_t msg;
int stats[MAX_CL_STATS];
+ // PROTOCOL_DARKPLACES5 and later support packet size limiting of updates
+ maxrate = max(NET_MINRATE, sv_maxrate.integer);
+ if (sv_maxrate.integer != maxrate)
+ Cvar_SetValueQuick(&sv_maxrate, maxrate);
+ // clientrate determines the 'cleartime' of a packet
+ // (how long to wait before sending another, based on this packet's size)
+ clientrate = bound(NET_MINRATE, client->rate, maxrate);
+
if (LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) == LHNETADDRESSTYPE_LOOP && !sv_ratelimitlocalplayer.integer)
{
- // for good singleplayer, send huge packets
+ // for good singleplayer, send huge packets and never limit frequency
+ clientrate = 1000000000;
maxsize = sizeof(sv_sendclientdatagram_buf);
maxsize2 = sizeof(sv_sendclientdatagram_buf);
}
else if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4)
{
- // no rate limiting support on older protocols because dp protocols
- // 1-4 kick the client off if they overflow, and quake protocol shows
- // less than the full entity set if rate limited
+ // no packet size limit support on older protocols because DP1-4 kick
+ // the client off if they overflow, and quake protocol shows less than
+ // the full entity set if rate limited
maxsize = 1400;
maxsize2 = 1400;
}
else
{
- // PROTOCOL_DARKPLACES5 and later support packet size limiting of updates
- maxrate = max(NET_MINRATE, sv_maxrate.integer);
- if (sv_maxrate.integer != maxrate)
- Cvar_SetValueQuick(&sv_maxrate, maxrate);
-
+ // DP5 and later protocols support packet size limiting which is a
+ // better method than limiting packet frequency as QW does
+ //
// this rate limiting does not understand sys_ticrate 0
// (but no one should be running that on a server!)
- rate = bound(NET_MINRATE, client->rate, maxrate);
- rate = (int)(rate * sys_ticrate.value);
- maxsize = bound(50, rate, 1400);
+ maxsize = (int)(clientrate * sys_ticrate.value);
+ maxsize = bound(100, maxsize, 1400);
maxsize2 = 1400;
}
msg.maxsize = maxsize;
msg.cursize = 0;
- if (host_client->spawned)
+ // obey rate limit by limiting packet frequency if the packet size
+ // limiting fails
+ // (usually this is caused by reliable messages)
+ if (!NetConn_CanSend(client->netconnection))
+ {
+ // send the datagram
+ NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol, clientrate);
+ return;
+ }
+ else if (host_client->spawned)
{
MSG_WriteByte (&msg, svc_time);
MSG_WriteFloat (&msg, sv.time);
}
// send the datagram
- NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol);
+ NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol, clientrate);
}
/*