Each reliable message channel needs its own sequence number, in addition to the overall datagram sequence number. This lets you know which messages have already been delivered or not.
Yeah I suppose this is the key, right now I was only giving separate sequence numbers to messages in the reliable-in-order channel, suppose I need to give that too reliable-out-of-order also.
You can also say that any datagram that arrives late is always entirely dropped, even if it contains "reliable" data. Instead, use your reliability mechanism to re-transmit that data. This will let the other end know for sure that the problem you're talking about will never happen.
Yes, but this would still not fix the very very slim chance edge case I described in my original post (I re-send something which I think is lost, but in fact is not, and the two datagrams with the same message in it ends up arriving in order).
However; I already do this as I drop all the unreliable data that allows in late packets, and just grab the reliable in-order and reliable out-of-order messages in the packet if it's late (based on datagram sequence #'s)
- A "package" is something you mail at the post office, or put under the Christmas tree. A "packet" is something that travels through network wires.
Yeah, that's me not being native English ;P
- If you never let the set of outstanding messages in a squence be bigger than 127 items,
Are you talking about datagram sequences or message sequences now, or both? I suppose it does not matter, I already do this for datagram packet sequences and reliable-in-order messages, however it does slightly worry me for doing it to messages themselves as messages are a lot more frequent then packets (about 15-20 packets/second is common, and I have a window size of 64, which gives me about ~3-4 seconds of leeway), but messages can be many and frequent - but then again I suppose if you're sending more then 20-30 reliable messages per second you have another problem on your hands.
then you can use a single byte for each sequence number, and just use the appropriate wrapping semantics to map it back to a real sequence number.
I assume you mean something like this:
Expected Sequence: 5
Arrived Sequence: 10
Relative Sequence Number +5 (5 a-head of the expected)
And yes, I already do this for my reliable-in-order messages. Though I am not 100% on what you mean with
"real" sequence number? If it matters the calculation looks like this:
// Calculate distance for 10 bit sequence numbers
int sequence_distance(int sequence, int expected)
{
ushort unsignedSequence = (ushort)(sequence << 6);
ushort unsignedExpected = (ushort)(expected << 6;
return ((short)(unsignedSequence - unsignedExpected)) >> 6;
}
This will let you always encode sequence numbers as a byte, rather than using variable-size encoding. If you want to ack multiple packets, I've found that a single byte for seq number, and another byte for the 8 previous numbers, works pretty well, too, and avoids bit-slicing bytes.
My packet sequence numbers are 10 bit, My message sequence numbers are 8 bit (might increase it to I get more leeway if I send a lot of small messages), but the messages are encoded like this (pseudo code):
// Start at -2 because -1 + 1 == 0 which is a valid seq #
int prev_msg_seq_nr = -2;
for each message in reliable_queue do
bool is_right_after_prev = (prev_msg_seq_nr + 1) == message->seq_nr;
write_flag(is_right_after_prev);
if(!is_right_after_prev)
write_int(message->seq_nr, 8);
prev_msg_seq_nr = message->seq_nr:
message->pack_into_bitstream();
/// etc ...
This ends up working out so that in most cases the seq_nr only takes up one bit per message except for the first one where it takes 9 bits.I use a window size of 64 for data-gram packets currently, so maybe my sequence range for datagrams (10 bits) is to big, and I could do with 8-9 bits instead.
Edit
I suppose my whole problem can be avoided if you just never send any packets you are "uncertain" about, basically always drop late packages on the client, never re-send a packet from the server until the client has ACK:ed a packet which comes after it in the sequence. While this can cause some extra delay for reliable data, you should not really depend on reliable data anyway for game critical things. And just make sure to always exchange packets between peers every N:th millisecond, no matter if you have data or not to keep the data flowing (which deals with a bunch of routing related problems anyway, like getting dropped or NAT tables not staying recent, etc.)
One of the reasons I was asking this is because if you have a small window size for datagrams, if you have high enough packet loss it's possible to have all packets dropped or all acks dropped, but then again trying to salvage that situation is just going to degrade the system with a lot bloat for super high packet loss situations anyway so might aswell just drop the client.