Simulating network latency and jitter
Posted: Mon Nov 21, 2016 2:03 am
Over the years, I've changed Quetoo's network protocol many times -- sometimes for the better, sometimes not. It's often the case that I don't discover the true nature of my changes until I try the game over the Internet. Things will look fine on localhost, and go to hell in a hand basket once 80ms of latency and 15ms of jitter come into play.
To make this easier to test and debug, I came up with a quick little hack. My engine was based on Quake2, which had a separate network channel reserved for loopback communications. This channel had a fixed size buffer of 4 messages. This trick requires increasing that buffer size to a large enough figure to allow for, say, 1000ms worth of packets to accumulate. So if your engine runs at 10hz, like Quake2, you could get away with a packet buffer of 16 messages. Mine runs at 60hz, so my loop message buffer is 64 messages large.
I introduced two new cvars: net_loop_latency and net_loop_jitter. The former adds constant latency to your loopback channel, and the latter adds randomized jitter. Both are specified in milliseconds. And with that, here is my new Net_ReceiveDatagram_Loop:
Pretty simple. Until the configured latency threshold is met, the next message will not be returned. I multiply the latency by 0.5 because this function is applied by both the client and the server, and so if you specify 60ms of latency, I add 30ms on both ends. So far, this has proven very useful in debugging stair prediction and other more network-dependent physics interactions. And it's kinda cool to be able to shape your own netgraph
To make this easier to test and debug, I came up with a quick little hack. My engine was based on Quake2, which had a separate network channel reserved for loopback communications. This channel had a fixed size buffer of 4 messages. This trick requires increasing that buffer size to a large enough figure to allow for, say, 1000ms worth of packets to accumulate. So if your engine runs at 10hz, like Quake2, you could get away with a packet buffer of 16 messages. Mine runs at 60hz, so my loop message buffer is 64 messages large.
I introduced two new cvars: net_loop_latency and net_loop_jitter. The former adds constant latency to your loopback channel, and the latter adds randomized jitter. Both are specified in milliseconds. And with that, here is my new Net_ReceiveDatagram_Loop:
Code: Select all
/**
* @brief Reads a pending message, if available, from the loop buffer.
* @return True if a message was read, false otherwise.
*/
static _Bool Net_ReceiveDatagram_Loop(net_src_t source, net_addr_t *from, mem_buf_t *buf) {
net_udp_loop_t *loop = &net_udp_state.loops[source];
if (loop->send - loop->recv > MAX_NET_UDP_LOOPS) {
loop->recv = loop->send - MAX_NET_UDP_LOOPS;
}
if (loop->recv >= loop->send) {
return false;
}
const uint32_t i = loop->recv & (MAX_NET_UDP_LOOPS - 1);
const net_udp_loop_message_t *msg = &loop->messages[i];
// simulate network latency and jitter for debugging net protocol locally
const uint32_t delta = quetoo.time - msg->timestamp;
const uint32_t threshold = net_loop_latency->value * 0.5 + net_loop_jitter->value * Randomf();
if (delta < threshold) {
return false;
}
loop->recv++;
memcpy(buf->data, msg->data, msg->size);
buf->size = msg->size;
from->type = NA_LOOP;
from->addr = net_lo;
from->port = 0;
return true;
}