Purple Martians
Technical Code Descriptions
Netgame - State and Dif
The game state
Game state variables
Combining game state variables into 'state'
State variables
State compression methods
The differential method
The game state
The game state is all of the data required to replicate the game between server and client.
It consists of all the relevant variables for the level, players, enemies, items, bullets, etc.
The server's game state is periodically sent to clients to keep them in sync.
Game state variables
// Variables used for netgame state exchange
mPlayer.syn : 2304
mEnemy.Ei : 12800
mEnemy.Ef : 6400
mItem.item : 32000
mItem.itemf : 8000
mLift.cur : 4480
mLevel.l : 40000
mShot.p : 1200
mShot.e : 1200
mTriggerEvent.event : 4000
--------------------:------
total :112384 bytes
Combining game state variables into 'state'
The game state variables are copied into a character array with memcpy.
This is so that they can be processed as a single chunk of data, rather than individually.
I refer to these large chunks of data as 'states'.
They contain the entire game state for a specific frame.
This is how data is put into a 'state', and how it is retrieved:
#define STATE_SIZE 112384
char state[STATE_SIZE];
game_vars_to_state(state) // put game variables into 'state'
state_to_game_vars(state) // get game variables from 'state'
void mwNetgame::game_vars_to_state(char * b)
{
int sz = 0, offset = 0;
offset += sz; sz = sizeof(mPlayer.syn); memcpy(b+offset, mPlayer.syn, sz);
offset += sz; sz = sizeof(mEnemy.Ei); memcpy(b+offset, mEnemy.Ei, sz);
offset += sz; sz = sizeof(mEnemy.Ef); memcpy(b+offset, mEnemy.Ef, sz);
offset += sz; sz = sizeof(mItem.item); memcpy(b+offset, mItem.item, sz);
offset += sz; sz = sizeof(mItem.itemf); memcpy(b+offset, mItem.itemf, sz);
offset += sz; sz = sizeof(mLift.cur); memcpy(b+offset, mLift.cur, sz);
offset += sz; sz = sizeof(mLevel.l); memcpy(b+offset, mLevel.l, sz);
offset += sz; sz = sizeof(mShot.p); memcpy(b+offset, mShot.p, sz);
offset += sz; sz = sizeof(mShot.e); memcpy(b+offset, mShot.e, sz);
offset += sz; sz = sizeof(mTriggerEvent.event); memcpy(b+offset, mTriggerEvent.event, sz);
}
void mwNetgame::state_to_game_vars(char * b)
{
int sz = 0, offset = 0;
sz = sizeof(mPlayer.syn); memcpy(mPlayer.syn, b+offset, sz); offset += sz;
sz = sizeof(mEnemy.Ei); memcpy(mEnemy.Ei, b+offset, sz); offset += sz;
sz = sizeof(mEnemy.Ef); memcpy(mEnemy.Ef, b+offset, sz); offset += sz;
sz = sizeof(mItem.item); memcpy(mItem.item, b+offset, sz); offset += sz;
sz = sizeof(mItem.itemf); memcpy(mItem.itemf, b+offset, sz); offset += sz;
sz = sizeof(mLift.cur); memcpy(mLift.cur, b+offset, sz); offset += sz;
sz = sizeof(mLevel.l); memcpy(mLevel.l, b+offset, sz); offset += sz;
sz = sizeof(mShot.p); memcpy(mShot.p, b+offset, sz); offset += sz;
sz = sizeof(mShot.e); memcpy(mShot.e, b+offset, sz); offset += sz;
sz = sizeof(mTriggerEvent.event); memcpy(mTriggerEvent.event, b+offset, sz); offset += sz;
}
State variables
State History keeps a history of states for both server and clients.
Clients also use 2 local states for building and decompressing difs
#define STATE_SIZE 112384
// local client's buffer for building compressed dif from packets
char client_state_buffer[STATE_SIZE];
int client_state_buffer_pieces[16]; // to mark packet pieces as received
// local client's uncompressed dif
char client_state_dif[STATE_SIZE];
int client_state_dif_src; // src frame_num
int client_state_dif_dst; // dst frame_num
State compression methods
States are quite large, (over 100K) so two methods of compression are used before sending them over the network.
I use the amazing compression library 'zlib' for this.
zlib is: (from their own documentation at zlib.net)
A Massively Spiffy Yet Delicately Unobtrusive Compression Library (Also Free, Not to Mention Unencumbered by Patents)
zlib alone will compress the 100K to approximately 6-8K.
Using a differential method before compressing with zlib results in approximately 500-1100 bytes.
The differential method
The differential method subtracts one state from another, resulting in a state that only contains differences between the the two.
Whenever the two states have the same value, the dif will have a zero. Only changed values are non-zero.
This results in a dif that is mostly zero's and compresses much better.
Most of the time, the dif is less than 1K and can be sent in a single packet.
This method requires that both the server and the client keep a common previous state that the dif can be applied to.
void mwNetgame::get_state_dif(char *a, char *b, char *c, int size)
{
for (int i=0; i < size; i++) c[i] = a[i] - b[i];
}
void mwNetgame::apply_state_dif(char *a, char *c, int size)
{
for (int i=0; i < size; i++) a[i] -= c[i];
}