See extra corrections: http://brain.wireos.com/?p=2197
When game is running, all data is sent between the two sides using two network messages: FRAME and USERFRAME (or I might call it COMMAND instead..?). FRAME is delta-compressed entire state of the game world, and USERFRAME is delta-compressed entire state of the client machine.
Delta-compression means that only changes are sent. These packets are unreliable, and one or more of them may be dropped from the stream. For this case, all FRAME packets are numbered. They have a sequence number on each of them. Right now it is the same thing as frame counter in server.
Every server frame is numbered with sequence number it was created for. The USERFRAME packet carries the last received sequence number from the client. Server will always delta-compress packets against last received (acknowledged) sequence number from the client. Typical transmission looks like this:

The server keeps sending messages at rate of 15 messages per second, compressed against a last known acknowledged frame. The client sends out USERFRAMEs 15 times a second, and every time you press a key. Packet data itself isn’t compressed yet. Typical packet size for FRAME packet – anywhere between 30 and 700 bytes (depends on amount of players, and how many updates there are), and for the USERFRAME it’s about 10 bytes.
When client receives a message, it checks whether it is out of order (if so, it drops the message), and alters frame history to account for new incoming data. This means that all frames between last reliable packet from server and the new received one will be interpolated. All frames which follow reliable packet are automatically extrapolated.
Additionaly local client will keep extrapolating new frames at the same rate as server runs. This assures that even if there is a data loss, packets will keep arriving for the rendering system. I think I’ll detail frames system a bit more in next post.
The related code looks like this. I’ll coment parts of it. Here is the header, where it checks whether frame is not out of order (the frame must be useful to the game state):
if (newSequence > Frames.LastSequence) {
The game decompresses frame as the newest one based on last acknowledged frame:
Frames.deltaDecompress(Frames.LastFrame,Frames.Temporary,Msg);
Frames.setFrame(Frames.LastFrame,Frames.Temporary);
Frames.LastSequence = newSequence;
This code finds index of frame that corresponds to sequence we just received:
int frameIndex = -1;
for (uint i = 1; i < historyLength; i++) {
if (Frames.History[i]->Sequence == newSequence) {
frameIndex = i;
break;
}
}
If we found the frame, then extrapolate all frames that precede it (the ones which are newer, and yet unknown). Otherwise just make it newest one:
if (frameIndex >= 0) {
Frames.setFrame(Frames.History[frameIndex],Frames.LastFrame);
for (int j = frameIndex; j > 0; j--) {
Frames.extrapolateFrame(Frames.History[j-1],Frames.History[j]);
Frames.History[j-1]->Sequence = Frames.History[j]->Sequence+1;
}
} else {
frameIndex = 0;
Frames.setFrame(Frames.History[0],Frames.LastFrame);
}
Find reliable frame preceding this one:
int reliableFrame = 0;
for (uint j = frameIndex+1; j < historyLength; j++) {
if (Frames.History[j]->reliableFrame) {
reliableFrame = j;
break;
}
}
Interpolate all frames between previous reliable one and new reliable one:
if (reliableFrame) {
for (int j = frameIndex+1; j < reliableFrame; j++) {
int firstIndex = frameIndex;
int lastIndex = reliableFrame;
float T = ((float)(j - firstIndex)) / ((float)(lastIndex-firstIndex));
Frames.interpolateFrame(
Frames.History[reliableFrame],
Frames.History[frameIndex],
Frames.History[j],
T);
Frames.History[j]->Sequence =
Frames.History[reliableFrame]->Sequence+(j-lastIndex);
}
}
}
The related code is in server/frame.cpp. Search for these functions:
Frame_Manager::Frame() is the server frame sending loop.
netCallback_FRAME is the client message handling callback.
Recent comments