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;
}