From 125d04e51f4e444a38cf038d3ea095d92d3c6dbb Mon Sep 17 00:00:00 2001 From: Magnus Auvinen Date: Fri, 13 Jul 2007 13:40:04 +0000 Subject: large rewrite and code cleanup --- src/engine/client/client.cpp | 514 +++++++++++++++---------------- src/engine/compression.cpp | 370 +++++++++++++++++++++++ src/engine/compression.h | 12 + src/engine/config.cpp | 2 +- src/engine/config_variables.h | 7 + src/engine/interface.h | 49 ++- src/engine/lzw.cpp | 223 -------------- src/engine/lzw.h | 2 - src/engine/msg.cpp | 43 +++ src/engine/network.cpp | 686 ++++++++++++++++++++++++++++++++++++++++++ src/engine/network.h | 104 +++++++ src/engine/packet.h | 626 +++++++++++++++++--------------------- src/engine/ringbuffer.h | 84 ++++++ src/engine/server/server.cpp | 571 ++++++++++++----------------------- src/engine/snapshot.cpp | 235 +++++++++++++++ src/engine/snapshot.h | 40 ++- src/engine/versions.h | 2 +- 17 files changed, 2329 insertions(+), 1241 deletions(-) create mode 100644 src/engine/compression.cpp create mode 100644 src/engine/compression.h delete mode 100644 src/engine/lzw.cpp delete mode 100644 src/engine/lzw.h create mode 100644 src/engine/msg.cpp create mode 100644 src/engine/network.cpp create mode 100644 src/engine/network.h create mode 100644 src/engine/ringbuffer.h create mode 100644 src/engine/snapshot.cpp (limited to 'src/engine') diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 86cc044d..4fb869cd 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -13,59 +14,14 @@ #include #include "ui.h" -#include +#include #include #include +#include using namespace baselib; -// --- string handling (MOVE THESE!!) --- -void snap_encode_string(const char *src, int *dst, int length, int max_length) -{ - const unsigned char *p = (const unsigned char *)src; - - // handle whole int - for(int i = 0; i < length/4; i++) - { - *dst = (p[0]<<24|p[1]<<16|p[2]<<8|p[3]); - p += 4; - dst++; - } - - // take care of the left overs - int left = length%4; - if(left) - { - unsigned last = 0; - switch(left) - { - case 3: last |= p[2]<<8; - case 2: last |= p[1]<<16; - case 1: last |= p[0]<<24; - } - *dst = last; - } -} - -void snap_decode_string(const int *src, char *dst, int max_length) -{ - dbg_assert((max_length%4) == 0, "length must be power of 4"); - for(int i = 0; i < max_length; i++) - dst[0] = 0; - - for(int i = 0; i < max_length/4; i++) - { - dst[0] = (*src>>24)&0xff; - dst[1] = (*src>>16)&0xff; - dst[2] = (*src>>8)&0xff; - dst[3] = (*src)&0xff; - src++; - dst+=4; - } - dst[-1] = 0; // make sure to zero terminate -} - // --- input wrappers --- static int keyboard_state[2][input::last]; static int keyboard_current = 0; @@ -92,7 +48,7 @@ void inp_update() } // --- input snapping --- -static int input_data[MAX_INPUT_SIZE]; +static int input_data[MAX_INPUT_SIZE] = {0}; static int input_data_size; static int input_is_changed = 1; void snap_input(void *data, int size) @@ -110,6 +66,8 @@ enum NUM_SNAPSHOT_TYPES=3, }; +static snapshot_storage snapshots_new; +static int current_tick; static snapshot *snapshots[NUM_SNAPSHOT_TYPES]; static char snapshot_data[NUM_SNAPSHOT_TYPES][MAX_SNAPSHOT_SIZE]; static int recived_snapshots; @@ -127,7 +85,7 @@ void *snap_get_item(int snapid, int index, snap_item *item) snapshot::item *i = snapshots[snapid]->get_item(index); item->type = i->type(); item->id = i->id(); - return (void *)i->data; + return (void *)i->data(); } int snap_num_items(int snapid) @@ -145,11 +103,16 @@ static void snap_init() recived_snapshots = 0; } -float snap_intratick() +float client_intratick() { return (time_get() - snapshot_start_time)/(float)(time_freq()/SERVER_TICK_SPEED); } +int client_tick() +{ + return current_tick; +} + void *snap_find_item(int snapid, int type, int id) { // TODO: linear search. should be fixed. @@ -157,123 +120,45 @@ void *snap_find_item(int snapid, int type, int id) { snapshot::item *itm = snapshots[snapid]->get_item(i); if(itm->type() == type && itm->id() == id) - return (void *)itm->data; + return (void *)itm->data(); } return 0x0; } -int menu_loop(); -float frametime = 0.0001f; +int menu_loop(); // TODO: what is this? +static float frametime = 0.0001f; float client_frametime() { return frametime; } -void unpack(const char *src, const char *fmt, ...) -{ -} +static net_client net; -/*int modc_onmsg(int msg) -{ - msg_get("iis") -}*/ - -/* - i = int (int i) - s = string (const char *str) - r = raw data (int size, void *data) -*/ - -/* -class packet2 -{ -private: - // packet data - struct header - { - unsigned msg; - unsigned ack; - unsigned seq; - }; - - unsigned char packet_data[MAX_PACKET_SIZE]; - unsigned char *current; - - enum - { - MAX_PACKET_SIZE = 1024, - }; - -public: - packet2() - { - current = packet_data; - current += sizeof(header); - } - - int pack(char *dst, const char *fmt, ...) - { - va_list arg_list; - va_start(arg_list, fmt); - while(*fmt) - { - if(*fmt == 's') - { - // pack string - const char *s = va_arg(arg_list, const char*); - *dst++ = 2; - while(*s) - { - *dst = *s; - dst++; - s++; - } - *dst = 0; // null terminate - dst++; - fmt++; - } - else if(*fmt == 'i') - { - // pack int - int i = va_arg(arg_list, int); - *dst++ = 1; - *dst++ = (i>>24)&0xff; - *dst++ = (i>>16)&0xff; - *dst++ = (i>>8)&0xff; - *dst++ = i&0xff; - fmt++; - } - else - { - dbg_break(); // error - break; - } - } - va_end(arg_list); - } -}; -*/ -/* -int msg_get(const char *fmt) +int client_send_msg() { + const msg_info *info = msg_get_info(); + NETPACKET packet; + packet.client_id = 0; + packet.data = info->data; + packet.data_size = info->size; + + if(info->flags&MSGFLAG_VITAL) + packet.flags = PACKETFLAG_VITAL; + net.send(&packet); + return 0; } -int client_msg_send(int msg, const char *fmt, ...) - -int server_msg_send(int msg, const char *fmt, ...) -{ - -}*/ - // --- client --- +// TODO: remove this class class client { public: - socket_udp4 socket; - connection conn; + + //socket_udp4 socket; + //connection conn; int64 reconnect_timer; int snapshot_part; @@ -305,37 +190,24 @@ public: void set_fullscreen(bool flag) { fullscreen = flag; } - void send_packet(packet *p) - { - conn.send(p); - } - - void send_connect() + void send_info() { recived_snapshots = 0; - - /* - pack(NETMSG_CLIENT_CONNECT, "sssss", - TEEWARS_NETVERSION, - name, - "no clan", - "password", - "myskin"); - */ - - packet p(NETMSG_CLIENT_CONNECT); - p.write_str(TEEWARS_VERSION); // payload - p.write_str(config.player_name); - p.write_str("no clan"); - p.write_str("password"); - p.write_str("myskin"); - send_packet(&p); + + msg_pack_start(NETMSG_INFO, MSGFLAG_VITAL); + msg_pack_string(config.player_name, 128); + msg_pack_string(config.clan_name, 128); + msg_pack_string(config.password, 128); + msg_pack_string("myskin", 128); + msg_pack_end(); + client_send_msg(); } - void send_done() + void send_entergame() { - packet p(NETMSG_CLIENT_DONE); - send_packet(&p); + msg_pack_start(NETMSG_ENTERGAME, MSGFLAG_VITAL); + msg_pack_end(); + client_send_msg(); } void send_error(const char *error) @@ -343,40 +215,38 @@ public: /* pack(NETMSG_CLIENT_ERROR, "s", error); */ + /* packet p(NETMSG_CLIENT_ERROR); p.write_str(error); send_packet(&p); //send_packet(&p); //send_packet(&p); + */ } void send_input() { - /* - pack(NETMSG_CLIENT_ERROR, "s", error); - */ - packet p(NETMSG_CLIENT_INPUT); - p.write_int(input_data_size); + msg_pack_start(NETMSG_INPUT, 0); + msg_pack_int(input_data_size); for(int i = 0; i < input_data_size/4; i++) - p.write_int(input_data[i]); - send_packet(&p); + msg_pack_int(input_data[i]); + msg_pack_end(); + client_send_msg(); } void disconnect() { + /* send_error("disconnected"); set_state(STATE_OFFLINE); map_unload(); + */ } void connect(netaddr4 *server_address) { - conn.init(&socket, server_address); - - // start by sending connect - send_connect(); + net.connect(server_address); set_state(STATE_CONNECTING); - reconnect_timer = time_get()+time_freq(); } bool load_data() @@ -385,14 +255,42 @@ public: return true; } + void debug_render() + { + gfx_blend_normal(); + gfx_texture_set(debug_font); + gfx_mapscreen(0,0,gfx_screenwidth(),gfx_screenheight()); + + static NETSTATS prev, current; + static int64 last_snap = 0; + if(time_get()-last_snap > time_freq()/10) + { + last_snap = time_get(); + prev = current; + net.stats(¤t); + } + + char buffer[512]; + sprintf(buffer, "send: %8d recv: %8d", + (current.send_bytes-prev.send_bytes)*10, + (current.recv_bytes-prev.recv_bytes)*10); + gfx_quads_text(10, 10, 16, buffer); + + } + void render() { gfx_clear(0.0f,0.0f,0.0f); // this should be moved around abit + // TODO: clean this shit up! if(get_state() == STATE_ONLINE) { modc_render(); + + // debug render stuff + debug_render(); + } else if (get_state() != STATE_CONNECTING && get_state() != STATE_LOADING) { @@ -404,7 +302,7 @@ public: else if (status) connect(&server_address); } - else if (get_state() == STATE_CONNECTING) + else if (get_state() == STATE_CONNECTING || get_state() == STATE_LOADING) { static int64 start = time_get(); static int tee_texture; @@ -458,15 +356,18 @@ public: // init menu modmenu_init(); + net.open(0); + // open socket + /* if(!socket.open(0)) { dbg_msg("network/client", "failed to open socket"); return; - } + }*/ // connect to the server if wanted - if (server_address) + if(server_address) connect(server_address); //int64 inputs_per_second = 50; @@ -504,6 +405,9 @@ public: input::set_mouse_mode(input::mode_absolute); if(input::pressed(input::f2)) input::set_mouse_mode(input::mode_relative); + + if(input::pressed(input::lctrl) && input::pressed('Q')) + break; // pump the network pump_network(); @@ -523,13 +427,13 @@ public: if(reporttime < time_get()) { - unsigned sent, recved; - conn.counter_get(&sent, &recved); - dbg_msg("client/report", "fps=%.02f", - frames/(float)(reportinterval/time_freq())); + //unsigned sent, recved; + //conn.counter_get(&sent, &recved); + dbg_msg("client/report", "fps=%.02f netstate=%d", + frames/(float)(reportinterval/time_freq()), net.state()); frames = 0; reporttime += reportinterval; - conn.counter_reset(); + //conn.counter_reset(); } if (input::pressed(input::esc)) @@ -556,117 +460,171 @@ public: set_state(STATE_BROKEN); } - void process_packet(packet *p) + void process_packet(NETPACKET *packet) { - if(p->version() != TEEWARS_NETVERSION) + int msg = msg_unpack_start(packet->data, packet->data_size); + if(msg == NETMSG_MAP) { - error("wrong version"); - } - else if(p->msg() == NETMSG_SERVER_ACCEPT) - { - const char *map; - map = p->read_str(); + const char *map = msg_unpack_string(); + dbg_msg("client/network", "connection accepted, map=%s", map); + set_state(STATE_LOADING); - if(p->is_good()) + if(map_load(map)) { - dbg_msg("client/network", "connection accepted, map=%s", map); - set_state(STATE_LOADING); - - if(map_load(map)) - { - modc_entergame(); - send_done(); - dbg_msg("client/network", "loading done"); - // now we will wait for two snapshots - // to finish the connection - } - else - { - error("failure to load map"); - } + modc_entergame(); + send_entergame(); + dbg_msg("client/network", "loading done"); + // now we will wait for two snapshots + // to finish the connection + } + else + { + error("failure to load map"); } } - else if(p->msg() == NETMSG_SERVER_SNAP) + else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSMALL || msg == NETMSG_SNAPEMPTY) { //dbg_msg("client/network", "got snapshot"); - int num_parts = p->read_int(); - int part = p->read_int(); - int part_size = p->read_int(); + int game_tick = msg_unpack_int(); + int delta_tick = game_tick-msg_unpack_int(); + int num_parts = 1; + int part = 0; + int part_size = 0; - if(p->is_good()) + if(msg == NETMSG_SNAP) { - if(snapshot_part == part) + num_parts = msg_unpack_int(); + part = msg_unpack_int(); + } + + if(msg != NETMSG_SNAPEMPTY) + part_size = msg_unpack_int(); + + if(snapshot_part == part) + { + // TODO: clean this up abit + const char *d = (const char *)msg_unpack_raw(part_size); + mem_copy((char*)snapshots[SNAP_INCOMMING] + part*MAX_SNAPSHOT_PACKSIZE, d, part_size); + snapshot_part++; + + if(snapshot_part == num_parts) { - const char *d = p->read_raw(part_size); - mem_copy((char*)snapshots[SNAP_INCOMMING] + part*MAX_SNAPSHOT_PACKSIZE, d, part_size); - snapshot_part++; - - if(snapshot_part == num_parts) + snapshot *tmp = snapshots[SNAP_PREV]; + snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; + snapshots[SNAP_CURRENT] = tmp; + current_tick = game_tick; + + // decompress snapshot + void *deltadata = snapshot_empty_delta(); + int deltasize = sizeof(int)*3; + + unsigned char tmpbuffer[MAX_SNAPSHOT_SIZE]; + unsigned char tmpbuffer2[MAX_SNAPSHOT_SIZE]; + if(part_size) { - snapshot *tmp = snapshots[SNAP_PREV]; - snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; - snapshots[SNAP_CURRENT] = tmp; - - // decompress snapshot - lzw_decompress(snapshots[SNAP_INCOMMING], snapshots[SNAP_CURRENT]); - - // apply snapshot, cycle pointers - recived_snapshots++; - snapshot_start_time = time_get(); - - // we got two snapshots until we see us self as connected - if(recived_snapshots == 2) + //int snapsize = lzw_decompress(snapshots[SNAP_INCOMMING], snapshots[SNAP_CURRENT]); + int compsize = zerobit_decompress(snapshots[SNAP_INCOMMING], part_size, tmpbuffer); + //int compsize = lzw_decompress(snapshots[SNAP_INCOMMING],tmpbuffer); + int intsize = intpack_decompress(tmpbuffer, compsize, tmpbuffer2); + deltadata = tmpbuffer2; + deltasize = intsize; + } + + // find snapshot that we should use as delta + static snapshot emptysnap; + emptysnap.data_size = 0; + emptysnap.num_items = 0; + + snapshot *deltashot = &emptysnap; + int deltashot_size; + + if(delta_tick >= 0) + { + void *delta_data; + deltashot_size = snapshots_new.get(delta_tick, &delta_data); + if(deltashot_size >= 0) { - local_start_time = time_get(); - set_state(STATE_ONLINE); + deltashot = (snapshot *)delta_data; + } + else + { + // TODO: handle this + dbg_msg("client", "error, couldn't find the delta snapshot"); } - - if(recived_snapshots > 2) - modc_newsnapshot(); - - snapshot_part = 0; } - } - else - { - dbg_msg("client", "snapshot reset!"); + int snapsize = snapshot_unpack_delta(deltashot, (snapshot*)snapshots[SNAP_CURRENT], deltadata, deltasize); + //snapshot *shot = (snapshot *)snapshots[SNAP_CURRENT]; + + // purge old snapshots + snapshots_new.purge_until(delta_tick); + snapshots_new.purge_until(game_tick-50); // TODO: change this to server tickrate + + // add new + snapshots_new.add(game_tick, snapsize, snapshots[SNAP_CURRENT]); + + // apply snapshot, cycle pointers + recived_snapshots++; + snapshot_start_time = time_get(); + + // we got two snapshots until we see us self as connected + if(recived_snapshots == 2) + { + local_start_time = time_get(); + set_state(STATE_ONLINE); + } + + if(recived_snapshots > 2) + modc_newsnapshot(); + snapshot_part = 0; + + // ack snapshot + msg_pack_start(NETMSG_SNAPACK, 0); + msg_pack_int(game_tick); + msg_pack_end(); + client_send_msg(); } } - } - else - { - dbg_msg("server/client", "unknown packet %x", p->msg()); + else + { + dbg_msg("client", "snapshot reset!"); + snapshot_part = 0; + } } } void pump_network() { - while(1) + net.update(); + + // check for errors + if(get_state() != STATE_OFFLINE && net.state() == NETSTATE_OFFLINE) { - packet p; - netaddr4 from; - int bytes = socket.recv(&from, p.data(), p.max_size()); - - if(bytes <= 0) - break; - - process_packet(&p); + // TODO: add message to the user there + set_state(STATE_OFFLINE); } - + // - if(get_state() == STATE_CONNECTING && time_get() > reconnect_timer) + if(get_state() == STATE_CONNECTING && net.state() == NETSTATE_ONLINE) { - send_connect(); - reconnect_timer = time_get() + time_freq(); + // we switched to online + dbg_msg("client", "connected, sending info"); + set_state(STATE_LOADING); + send_info(); } + + // process packets + NETPACKET packet; + while(net.recv(&packet)) + process_packet(&packet); } }; int main(int argc, char **argv) { dbg_msg("client", "starting..."); + config_reset(); config_load("teewars.cfg"); @@ -683,9 +641,23 @@ int main(int argc, char **argv) { if(argv[i][0] == '-' && argv[i][1] == 'c' && argv[i][2] == 0 && argc - i > 1) { - // -c SERVER + // -c SERVER:PORT i++; - if(net_host_lookup(argv[i], 8303, &server_address) != 0) + const char *port_str = 0; + for(int k = 0; argv[i][k]; k++) + { + if(argv[i][k] == ':') + { + port_str = &(argv[i][k+1]); + argv[i][k] = 0; + break; + } + } + int port = 8303; + if(port_str) + port = atoi(port_str); + + if(net_host_lookup(argv[i], port, &server_address) != 0) dbg_msg("main", "could not find the address of %s, connecting to localhost", argv[i]); else connect_at_once = true; diff --git a/src/engine/compression.cpp b/src/engine/compression.cpp new file mode 100644 index 00000000..c878b15b --- /dev/null +++ b/src/engine/compression.cpp @@ -0,0 +1,370 @@ +#include +#include + +// LZW Compressor +struct SYM +{ + unsigned char *data; + int size; + int next; +}; + +struct SYMBOLS +{ + SYM syms[512]; + int jumptable[256]; + int currentsym; +}; + +static SYMBOLS symbols; + +// symbol info +inline int sym_size(int i) { return symbols.syms[i].size; } +inline unsigned char *sym_data(int i) { return symbols.syms[i].data; } + +static void sym_index(int sym) +{ + int table = symbols.syms[sym].data[0]; + symbols.syms[sym].next = symbols.jumptable[table]; + symbols.jumptable[table] = sym; +} + +static void sym_unindex(int sym) +{ + int table = symbols.syms[sym].data[0]; + int prev = -1; + int current = symbols.jumptable[table]; + + while(current != -1) + { + if(current == sym) + { + if(prev != -1) + symbols.syms[prev].next = symbols.syms[current].next; + else + symbols.jumptable[table] = symbols.syms[current].next; + break; + } + + prev = current; + current = symbols.syms[current].next; + } +} + +static int sym_add(unsigned char *sym, long len) +{ + int i = 256+symbols.currentsym; + symbols.syms[i].data = sym; + symbols.syms[i].size = len; + symbols.currentsym = (symbols.currentsym+1)%255; + return i; +} + +static int sym_add_and_index(unsigned char *sym, long len) +{ + if(symbols.syms[256+symbols.currentsym].size) + sym_unindex(256+symbols.currentsym); + int s = sym_add(sym, len); + sym_index( s); + return s; +} + +static void sym_init() +{ + static unsigned char table[256]; + for(int i = 0; i < 256; i++) + { + table[i] = i; + symbols.syms[i].data = &table[i]; + symbols.syms[i].size = 1; + symbols.jumptable[i] = -1; + } + + for(int i = 0; i < 512; i++) + symbols.syms[i].next = -1; + + /* + // insert some symbols to start with + static unsigned char zeros[8] = {0,0,0,0,0,0,0,0}; + //static unsigned char one1[4] = {0,0,0,1}; + //static unsigned char one2[4] = {1,0,0,0}; + sym_add_and_index(zeros, 2); + sym_add_and_index(zeros, 3); + sym_add_and_index(zeros, 4); + sym_add_and_index(zeros, 5); + sym_add_and_index(zeros, 6); + sym_add_and_index(zeros, 7); + sym_add_and_index(zeros, 8); + + //sym_add_and_index(one1, 4); + //sym_add_and_index(one2, 4);*/ + + symbols.currentsym = 0; +} + +static int sym_find(unsigned char *data, int size, int avoid) +{ + int best = data[0]; + int bestlen = 1; + int current = symbols.jumptable[data[0]]; + + while(current != -1) + { + if(current != avoid && symbols.syms[current].size <= size && memcmp(data, symbols.syms[current].data, symbols.syms[current].size) == 0) + { + if(bestlen < symbols.syms[current].size) + { + bestlen = symbols.syms[current].size; + best = current; + } + } + + current = symbols.syms[current].next; + } + + return best; +} + +// +// compress +// +long lzw_compress(const void *src_, int size, void *dst_) +{ + unsigned char *src = (unsigned char *)src_; + unsigned char *end = (unsigned char *)src_+size; + unsigned char *dst = (unsigned char *)dst_; + long left = (end-src); + int lastsym = -1; + + // init symboltable + sym_init(); + + bool done = false; + while(!done) + { + unsigned char *flagptr = dst; + unsigned char flagbits = 0; + int b = 0; + + dst++; // skip a byte where the flags are + + for(; b < 8; b++) + { + if(left <= 0) // check for EOF + { + // write EOF symbol + flagbits |= 1< symsize+1) // create new symbol + lastsym = sym_add_and_index(src, symsize+1); + + src += symsize; // advance src + left -= symsize; + } + + // write the flags + *flagptr = flagbits; + } + + return (long)(dst-(unsigned char*)dst_); +} + +// +// decompress +// +long lzw_decompress(const void *src_, void *dst_) +{ + unsigned char *src = (unsigned char *)src_; + unsigned char *dst = (unsigned char *)dst_; + unsigned char *prevdst = 0; + int prevsize = -1; + int item; + + sym_init(); + + while(1) + { + unsigned char flagbits = 0; + flagbits = *src++; // read flags + + int b = 0; + for(; b < 8; b++) + { + item = *src++; + if(flagbits&(1<>25)&0x40; // set sign bit if i<0 + i = i^(i>>31); // if(i<0) i = ~i + + *dst |= i&0x3F; // pack 6bit into dst + i >>= 6; // discard 6 bits + if(i) + { + *dst |= 0x80; // set extend bit + while(1) + { + dst++; + *dst = i&(0x7F); // pack 7bit + i >>= 7; // discard 7 bits + *dst |= (i!=0)<<7; // set extend bit (may branch) + if(!i) + break; + } + } + + dst++; + return dst; +} + +const unsigned char *vint_unpack(const unsigned char *src, int *i) +{ + int sign = (*src>>6)&1; + *i = *src&0x3F; + + while(1) + { + if(!(*src&0x80)) break; + src++; + *i |= (*src&(0x7F))<<(6); + + if(!(*src&0x80)) break; + src++; + *i |= (*src&(0x7F))<<(6+7); + + if(!(*src&0x80)) break; + src++; + *i |= (*src&(0x7F))<<(6+7+7); + + if(!(*src&0x80)) break; + src++; + *i |= (*src&(0x7F))<<(6+7+7+7); + } + + src++; + *i ^= -sign; // if(sign) *i = ~(*i) + return src; +} + + +long intpack_decompress(const void *src_, int size, void *dst_) +{ + const unsigned char *src = (unsigned char *)src_; + const unsigned char *end = src + size; + int *dst = (int *)dst_; + while(src < end) + { + src = vint_unpack(src, dst); + dst++; + } + return (long)((unsigned char *)dst-(unsigned char *)dst_); +} + +long intpack_compress(const void *src_, int size, void *dst_) +{ + int *src = (int *)src_; + unsigned char *dst = (unsigned char *)dst_; + size /= 4; + while(size) + { + dst = vint_pack(dst, *src); + size--; + src++; + } + return (long)(dst-(unsigned char *)dst_); +} + + +long zerobit_compress(const void *src_, int size, void *dst_) +{ + unsigned char *src = (unsigned char *)src_; + unsigned char *dst = (unsigned char *)dst_; + + //int zerocount = 0 ; + while(size) + { + unsigned char bit = 0x80; + unsigned char mask = 0; + int dst_move = 1; + int chunk = size < 8 ? size : 8; + size -= chunk; + + for(int b = 0; b < chunk; b++, bit>>=1) + { + if(*src) + { + dst[dst_move] = *src; + mask |= bit; + dst_move++; + } + + src++; + } + + *dst = mask; + dst += dst_move; + } + + long l = (long)(dst-(unsigned char *)dst_); + //dbg_msg("zerobit", "%d", (int)l); + return l; +} + +long zerobit_decompress(const void *src_, int size, void *dst_) +{ + unsigned char *src = (unsigned char *)src_; + unsigned char *dst = (unsigned char *)dst_; + unsigned char *end = src + size; + + //int zerocount = 0 ; + while(src != end) + { + unsigned char bit = 0x80; + unsigned char mask = *src++; + + for(int b = 0; b < 8; b++, bit>>=1) + { + if(mask&bit) + *dst++ = *src++; + else + *dst++ = 0; + } + } + + long l = (long)(dst-(unsigned char *)dst_); + //dbg_msg("zerobit", "%d", (int)l); + return l; +} + diff --git a/src/engine/compression.h b/src/engine/compression.h new file mode 100644 index 00000000..d1f13d48 --- /dev/null +++ b/src/engine/compression.h @@ -0,0 +1,12 @@ +// lzw is no longer in use +long lzw_compress(const void *src, int size, void *dst); +long lzw_decompress(const void *src, void *dst); + +unsigned char *vint_pack(unsigned char *dst, int i); +const unsigned char *vint_unpack(const unsigned char *src, int *inout); + +long intpack_compress(const void *src, int size, void *dst); +long intpack_decompress(const void *src, int size, void *dst); + +long zerobit_compress(const void *src, int size, void *dst); +long zerobit_decompress(const void *src, int size, void *dst); diff --git a/src/engine/config.cpp b/src/engine/config.cpp index bedeb4d4..7694b159 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -29,7 +29,7 @@ void config_set(const char *line) const char *val_str = strchr(line, '='); if (val_str) { - memcpy(var_str, line, val_str - line); + mem_copy(var_str, line, val_str - line); var_str[val_str - line] = 0; ++val_str; diff --git a/src/engine/config_variables.h b/src/engine/config_variables.h index 852d26a9..27ca1931 100644 --- a/src/engine/config_variables.h +++ b/src/engine/config_variables.h @@ -1,2 +1,9 @@ #include "../game/game_variables.h" +MACRO_CONFIG_INT(screen_width, 800, 0, 0) +MACRO_CONFIG_INT(screen_height, 600, 0, 0) +MACRO_CONFIG_STR(player_name, 32, "nameless tee") +MACRO_CONFIG_STR(clan_name, 32, "") +MACRO_CONFIG_STR(password, 32, "") + +MACRO_CONFIG_INT(sv_port, 8303, 0, 0) diff --git a/src/engine/interface.h b/src/engine/interface.h index c8099247..27f73e17 100644 --- a/src/engine/interface.h +++ b/src/engine/interface.h @@ -520,7 +520,7 @@ void snap_input(void *data, int size); Remarks: DOCTODO: Explain how to use it. */ -float snap_intratick(); +//float snap_intratick(); /* Group: Server Callbacks @@ -681,8 +681,13 @@ void modmenu_shutdown(); */ int modmenu_render(void *server_address); -void snap_encode_string(const char *src, int *dst, int length, int max_length); -void snap_decode_string(const int *src, char *dst, int length); + + + + + +//void snap_encode_string(const char *src, int *dst, int length, int max_length); +//void snap_decode_string(const int *src, char *dst, int length); int server_getclientinfo(int client_id, client_info *info); int server_tick(); @@ -694,6 +699,44 @@ void inp_update(); float client_frametime(); float client_localtime(); +// message packing +enum +{ + MSGFLAG_VITAL=1, +}; + +void msg_pack_start(int msg, int flags); +void msg_pack_int(int i); +void msg_pack_string(const char *p, int limit); +void msg_pack_raw(const void *data, int size); +void msg_pack_end(); + +struct msg_info +{ + int msg; + int flags; + const unsigned char *data; + int size; +}; + +const msg_info *msg_get_info(); + +// message unpacking +int msg_unpack_start(const void *data, int data_size); +int msg_unpack_int(); +const char *msg_unpack_string(); +const unsigned char *msg_unpack_raw(int size); + +// message sending +int server_send_msg(int client_id); +int client_send_msg(); + +int client_tick(); +float client_intratick(); + + +int modc_message(); + #define MASTER_SERVER_ADDRESS "master.teewars.com" #define MASTER_SERVER_PORT 8300 diff --git a/src/engine/lzw.cpp b/src/engine/lzw.cpp deleted file mode 100644 index 80dd1c22..00000000 --- a/src/engine/lzw.cpp +++ /dev/null @@ -1,223 +0,0 @@ -#include - -// LZW Compressor -struct SYM -{ - unsigned char *data; - int size; - int next; -}; - -struct SYMBOLS -{ - SYM syms[512]; - int jumptable[256]; - int currentsym; -}; - -static SYMBOLS symbols; - -// symbol info -inline int sym_size(int i) { return symbols.syms[i].size; } -inline unsigned char *sym_data(int i) { return symbols.syms[i].data; } - -static void sym_index(int sym) -{ - int table = symbols.syms[sym].data[0]; - symbols.syms[sym].next = symbols.jumptable[table]; - symbols.jumptable[table] = sym; -} - -static void sym_unindex(int sym) -{ - int table = symbols.syms[sym].data[0]; - int prev = -1; - int current = symbols.jumptable[table]; - - while(current != -1) - { - if(current == sym) - { - if(prev != -1) - symbols.syms[prev].next = symbols.syms[current].next; - else - symbols.jumptable[table] = symbols.syms[current].next; - break; - } - - prev = current; - current = symbols.syms[current].next; - } -} - -static int sym_add(unsigned char *sym, long len) -{ - int i = 256+symbols.currentsym; - symbols.syms[i].data = sym; - symbols.syms[i].size = len; - symbols.currentsym = (symbols.currentsym+1)%255; - return i; -} - -static int sym_add_and_index(unsigned char *sym, long len) -{ - if(symbols.syms[256+symbols.currentsym].size) - sym_unindex(256+symbols.currentsym); - int s = sym_add(sym, len); - sym_index( s); - return s; -} - -static void sym_init() -{ - static unsigned char table[256]; - for(int i = 0; i < 256; i++) - { - table[i] = i; - symbols.syms[i].data = &table[i]; - symbols.syms[i].size = 1; - symbols.jumptable[i] = -1; - } - - for(int i = 0; i < 512; i++) - symbols.syms[i].next = -1; - - /* - // insert some symbols to start with - static unsigned char zeros[8] = {0,0,0,0,0,0,0,0}; - //static unsigned char one1[4] = {0,0,0,1}; - //static unsigned char one2[4] = {1,0,0,0}; - sym_add_and_index(zeros, 2); - sym_add_and_index(zeros, 3); - sym_add_and_index(zeros, 4); - sym_add_and_index(zeros, 5); - sym_add_and_index(zeros, 6); - sym_add_and_index(zeros, 7); - sym_add_and_index(zeros, 8); - - //sym_add_and_index(one1, 4); - //sym_add_and_index(one2, 4);*/ - - symbols.currentsym = 0; -} - -static int sym_find(unsigned char *data, int size, int avoid) -{ - int best = data[0]; - int bestlen = 1; - int current = symbols.jumptable[data[0]]; - - while(current != -1) - { - if(current != avoid && symbols.syms[current].size <= size && memcmp(data, symbols.syms[current].data, symbols.syms[current].size) == 0) - { - if(bestlen < symbols.syms[current].size) - { - bestlen = symbols.syms[current].size; - best = current; - } - } - - current = symbols.syms[current].next; - } - - return best; -} - -// -// compress -// -long lzw_compress(const void *src_, int size, void *dst_) -{ - unsigned char *src = (unsigned char *)src_; - unsigned char *end = (unsigned char *)src_+size; - unsigned char *dst = (unsigned char *)dst_; - long left = (end-src); - int lastsym = -1; - - // init symboltable - sym_init(); - - bool done = false; - while(!done) - { - unsigned char *flagptr = dst; - unsigned char flagbits = 0; - int b = 0; - - dst++; // skip a byte where the flags are - - for(; b < 8; b++) - { - if(left <= 0) // check for EOF - { - // write EOF symbol - flagbits |= 1< symsize+1) // create new symbol - lastsym = sym_add_and_index(src, symsize+1); - - src += symsize; // advance src - left -= symsize; - } - - // write the flags - *flagptr = flagbits; - } - - return (long)(dst-(unsigned char*)dst_); -} - -// -// decompress -// -long lzw_decompress(const void *src_, void *dst_) -{ - unsigned char *src = (unsigned char *)src_; - unsigned char *dst = (unsigned char *)dst_; - unsigned char *prevdst = 0; - int prevsize = -1; - int item; - - sym_init(); - - while(1) - { - unsigned char flagbits = 0; - flagbits = *src++; // read flags - - int b = 0; - for(; b < 8; b++) - { - item = *src++; - if(flagbits&(1< + +#include "network.h" +#include "ringbuffer.h" + +/* + header: + unsigned char ID[2]; 2 'T' 'W' + unsigned char version; 3 + unsigned char flags; 4 + unsigned short seq; 6 + unsigned short ack; 8 + unsigned crc; 12 bytes + + header v2: + unsigned char flags; 1 + unsigned char seq_ack[3]; 4 + unsigned char crc[2]; 6 +*/ + + +#define NETWORK_HEADER_V2 + +// move +static int net_addr4_cmp(const NETADDR4 *a, const NETADDR4 *b) +{ + if( a->ip[0] != b->ip[0] || + a->ip[1] != b->ip[1] || + a->ip[2] != b->ip[2] || + a->ip[3] != b->ip[3] || + a->port != b->port + ) + return 1; + return 0; +} + +enum +{ + NETWORK_VERSION = 1, + +#ifdef NETWORK_HEADER_V2 + NETWORK_HEADER_SIZE = 6, +#else + NETWORK_HEADER_SIZE = 12, +#endif + NETWORK_MAX_PACKET_SIZE = 1024, + NETWORK_MAX_CLIENTS = 16, + + NETWORK_CONNSTATE_OFFLINE=0, + NETWORK_CONNSTATE_CONNECT=1, + NETWORK_CONNSTATE_CONNECTACCEPTED=2, + NETWORK_CONNSTATE_ONLINE=3, + NETWORK_CONNSTATE_ERROR=4, + + NETWORK_PACKETFLAG_CONNECT=0x01, + NETWORK_PACKETFLAG_ACCEPT=0x02, + NETWORK_PACKETFLAG_CLOSE=0x04, + NETWORK_PACKETFLAG_VITAL=0x08, + NETWORK_PACKETFLAG_RESEND=0x10, + //NETWORK_PACKETFLAG_STATELESS=0x20, +}; + +struct NETPACKETDATA +{ + unsigned char ID[2]; + unsigned char version; + unsigned char flags; + unsigned short seq; + unsigned short ack; + unsigned crc; + unsigned data_size; + int64 first_send_time; + unsigned char *data; +}; + + +static void send_packet(NETSOCKET socket, NETADDR4 *addr, NETPACKETDATA *packet) +{ + unsigned char buffer[NETWORK_MAX_PACKET_SIZE]; +#ifdef NETWORK_HEADER_V2 + buffer[0] = packet->flags; + buffer[1] = ((packet->seq>>4)&0xf0) | ((packet->ack>>8)&0x0f); + buffer[2] = packet->seq; + buffer[3] = packet->ack; + buffer[4] = packet->crc>>8; + buffer[5] = packet->crc&0xff; +#else + buffer[0] = packet->ID[0]; + buffer[1] = packet->ID[1]; + buffer[2] = packet->version; + buffer[3] = packet->flags; + buffer[4] = packet->seq>>8; + buffer[5] = packet->seq&0xff; + buffer[6] = packet->ack>>8; + buffer[7] = packet->ack&0xff; + buffer[8] = (packet->crc>>24)&0xff; + buffer[9] = (packet->crc>>16)&0xff; + buffer[10] = (packet->crc>>8)&0xff; + buffer[11] = packet->crc&0xff; +#endif + mem_copy(buffer+NETWORK_HEADER_SIZE, packet->data, packet->data_size); + int send_size = NETWORK_HEADER_SIZE+packet->data_size; + //dbg_msg("network", "sending packet, size=%d (%d + %d)", send_size, NETWORK_HEADER_SIZE, packet->data_size); + net_udp4_send(socket, addr, buffer, send_size); +} + +struct NETCONNECTION +{ + unsigned seq; + unsigned ack; + unsigned state; + + ring_buffer buffer; + + int64 last_recv_time; + int64 last_send_time; + const char *error_string; + + NETADDR4 peeraddr; + NETSOCKET socket; + NETSTATS stats; +}; + +struct NETSLOT +{ + int online; + NETCONNECTION conn; +}; + +struct NETSERVER +{ + NETSOCKET socket; + NETSLOT slots[NETWORK_MAX_CLIENTS]; + unsigned char recv_buffer[NETWORK_MAX_PACKET_SIZE]; +}; + +struct NETCLIENT +{ + NETADDR4 server_addr; + NETSOCKET socket; + unsigned char recv_buffer[NETWORK_MAX_PACKET_SIZE]; + + NETCONNECTION conn; +}; + +static void conn_reset_stats(NETCONNECTION *conn) +{ + mem_zero(&conn->stats, sizeof(conn->stats)); +} + +static void conn_reset(NETCONNECTION *conn) +{ + conn->seq = 0; + conn->ack = 0; + conn->state = NETWORK_CONNSTATE_OFFLINE; + conn->error_string = 0; + conn->last_send_time = 0; + conn->last_recv_time = 0; + conn->buffer.reset(); +} + +static const char *conn_error(NETCONNECTION *conn) +{ + return conn->error_string; +} +/* +static int conn_state(NETCONNECTION *conn) +{ + return conn->state; +}*/ + +static void conn_init(NETCONNECTION *conn, NETSOCKET socket) +{ + conn_reset(conn); + conn_reset_stats(conn); + conn->socket = socket; +} + +static void conn_ack(NETCONNECTION *conn, int ack) +{ + while(1) + { + ring_buffer::item *i = conn->buffer.first(); + if(!i) + break; + + NETPACKETDATA *resend = (NETPACKETDATA *)i->data(); + if(resend->seq <= ack) + conn->buffer.pop_first(); + else + break; + } +} + +static void conn_send_raw(NETCONNECTION *conn, NETPACKETDATA *data) +{ + conn->last_send_time = time_get(); + conn->stats.send_packets++; + conn->stats.send_bytes += data->data_size + NETWORK_HEADER_SIZE; + send_packet(conn->socket, &conn->peeraddr, data); +} + +static void conn_resend(NETCONNECTION *conn) +{ + ring_buffer::item *i = conn->buffer.first(); + while(i) + { + NETPACKETDATA *resend = (NETPACKETDATA *)i->data(); + conn->stats.resend_packets++; + conn->stats.resend_bytes += resend->data_size + NETWORK_HEADER_SIZE; + conn_send_raw(conn, resend); + i = i->next; + } +} + +static void conn_send(NETCONNECTION *conn, int flags, int data_size, const void *data) +{ + if(flags&NETWORK_PACKETFLAG_VITAL) + conn->seq++; + + NETPACKETDATA p; + p.ID[0] = 'T'; + p.ID[1] = 'W'; + p.version = NETWORK_VERSION; + p.flags = flags; + p.seq = conn->seq; + p.ack = conn->ack; + p.crc = 0; + p.data_size = data_size; + p.data = (unsigned char *)data; + p.first_send_time = time_get(); + + if(flags&NETWORK_PACKETFLAG_VITAL) + { + // save packet if we need to resend + NETPACKETDATA *resend = (NETPACKETDATA *)conn->buffer.alloc(sizeof(NETPACKETDATA)+p.data_size); + *resend = p; + resend->data = (unsigned char *)(resend+1); + mem_copy(resend->data, p.data, p.data_size); + } + + // TODO: calc crc + conn_send_raw(conn, &p); +} + +static int conn_connect(NETCONNECTION *conn, NETADDR4 *addr) +{ + if(conn->state != NETWORK_CONNSTATE_OFFLINE) + return -1; + + // init connection + conn_reset(conn); + conn->peeraddr = *addr; + conn->state = NETWORK_CONNSTATE_CONNECT; + conn_send(conn, NETWORK_PACKETFLAG_CONNECT, 0, 0); + return 0; +} + +static int conn_feed(NETCONNECTION *conn, NETPACKETDATA *p, NETADDR4 *addr) +{ + conn->last_recv_time = time_get(); + conn->stats.recv_packets++; + conn->stats.recv_bytes += p->data_size + NETWORK_HEADER_SIZE; + + if(conn->state == NETWORK_CONNSTATE_OFFLINE) + { + if(p->flags == NETWORK_PACKETFLAG_CONNECT) + { + // send response and init connection + conn->state = NETWORK_CONNSTATE_ONLINE; + conn->peeraddr = *addr; + conn_send(conn, NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT, 0, 0); + dbg_msg("connection", "got connection, sending connect+accept"); + } + } + else if(net_addr4_cmp(&conn->peeraddr, addr) == 0) + { + if(conn->state == NETWORK_CONNSTATE_ONLINE) + { + // remove packages that are acked + conn_ack(conn, p->ack); + + // + if(p->flags&NETWORK_PACKETFLAG_RESEND) + conn_resend(conn); + + if(p->flags&NETWORK_PACKETFLAG_VITAL) + { + if(p->seq == conn->ack+1) + { + // in sequence + conn->ack++; + } + else + { + // out of sequence, request resend + dbg_msg("conn", "asking for resend"); + conn_send(conn, NETWORK_PACKETFLAG_RESEND, 0, 0); + return 0; + } + } + + return 1; + } + else if(conn->state == NETWORK_CONNSTATE_CONNECT) + { + // connection made + if(p->flags == NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT) + { + conn_send(conn, NETWORK_PACKETFLAG_ACCEPT, 0, 0); + conn->state = NETWORK_CONNSTATE_ONLINE; + dbg_msg("connection", "got connect+accept, sending accept. connection online"); + } + } + /* + else if(conn->state == NETWORK_CONNSTATE_CONNECTACCEPTED) + { + // connection made + if(p->flags == NETWORK_PACKETFLAG_ACCEPT) + { + conn->state = NETWORK_CONNSTATE_ONLINE; + dbg_msg("connection", "got accept. connection online"); + } + }*/ + else + { + // strange packet, wrong state + } + } + else + { + // strange packet, not ment for me + } + + return 0; +} + + + +static void conn_update(NETCONNECTION *conn) +{ + if(conn->state == NETWORK_CONNSTATE_ERROR) + return; + + // check for timeout + if(conn->state != NETWORK_CONNSTATE_OFFLINE && + conn->state != NETWORK_CONNSTATE_CONNECT && + (time_get()-conn->last_recv_time) > time_freq()*3) + { + conn->state = NETWORK_CONNSTATE_ERROR; + conn->error_string = "timeout"; + } + + // check for large buffer errors + if(conn->buffer.size() > 1024*64) + { + conn->state = NETWORK_CONNSTATE_ERROR; + conn->error_string = "too weak connection (out of buffer)"; + } + + if(conn->buffer.first()) + { + NETPACKETDATA *resend = (NETPACKETDATA *)conn->buffer.first()->data(); + if(time_get()-resend->first_send_time > time_freq()*3) + { + conn->state = NETWORK_CONNSTATE_ERROR; + conn->error_string = "too weak connection (not acked for 3 seconds)"; + } + } + + // send keep alives if nothing has happend for 250ms + if(conn->state == NETWORK_CONNSTATE_ONLINE) + { + if(time_get()-conn->last_send_time> time_freq()/4) + conn_send(conn, NETWORK_PACKETFLAG_VITAL, 0, 0); + } + else if(conn->state == NETWORK_CONNSTATE_CONNECT) + { + if(time_get()-conn->last_send_time > time_freq()/2) // send a new connect every 500ms + conn_send(conn, NETWORK_PACKETFLAG_CONNECT, 0, 0); + } + else if(conn->state == NETWORK_CONNSTATE_CONNECTACCEPTED) + { + if(time_get()-conn->last_send_time > time_freq()/2) // send a new connect/accept every 500ms + conn_send(conn, NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT, 0, 0); + } +} + + +static int check_packet(unsigned char *buffer, int size, NETPACKETDATA *packet) +{ + // check the size + if(size < NETWORK_HEADER_SIZE || size > NETWORK_MAX_PACKET_SIZE) + return -1; + + // read the packet +#ifdef NETWORK_HEADER_V2 + packet->ID[0] = 'T'; + packet->ID[1] = 'W'; + packet->version = NETWORK_VERSION; + packet->flags = buffer[0]; + packet->seq = ((buffer[1]&0xf0)<<4)|buffer[2]; + packet->ack = ((buffer[1]&0x0f)<<8)|buffer[3]; + packet->crc = (buffer[8]<<24)|(buffer[9]<<16)|(buffer[10]<<8)|buffer[11]; +#else + packet->ID[0] = buffer[0]; + packet->ID[1] = buffer[1]; + packet->version = buffer[2]; + packet->flags = buffer[3]; + packet->seq = (buffer[4]<<8)|buffer[5]; + packet->ack = (buffer[6]<<8)|buffer[7]; + packet->crc = (buffer[8]<<24)|(buffer[9]<<16)|(buffer[10]<<8)|buffer[11]; +#endif + packet->data_size = size - NETWORK_HEADER_SIZE; + packet->data = buffer+NETWORK_HEADER_SIZE; + + // check the packet + if(packet->ID[0] != 'T' || packet->ID[1] != 'W') + return 1; + + if(packet->version != NETWORK_VERSION) + return 1; + + // TODO: perform crc check + + // return success + return 0; +} + +NETSERVER *net_server_open(int port, int max_clients, int flags) +{ + NETSERVER *server = (NETSERVER *)mem_alloc(sizeof(NETSERVER), 1); + mem_zero(server, sizeof(NETSERVER)); + server->socket = net_udp4_create(port); + + for(int i = 0; i < NETWORK_MAX_CLIENTS; i++) + conn_init(&server->slots[i].conn, server->socket); + + return server; +} + +int net_server_close(NETSERVER *s) +{ + // TODO: implement me + return 0; +} + +int net_server_newclient(NETSERVER *s) +{ + for(int i = 0; i < NETWORK_MAX_CLIENTS; i++) + { + if(!s->slots[i].online && s->slots[i].conn.state == NETWORK_CONNSTATE_ONLINE) + { + s->slots[i].online = 1; + return i; + } + } + + return -1; +} + +int net_server_delclient(NETSERVER *s) +{ + for(int i = 0; i < NETWORK_MAX_CLIENTS; i++) + { + if(s->slots[i].online && s->slots[i].conn.state != NETWORK_CONNSTATE_ONLINE) + { + s->slots[i].online = 0; + return i; + } + } + + return -1; +} + +int net_server_drop(NETSERVER *s, int client_id, const char *reason) +{ + // TODO: insert lots of checks here + dbg_msg("net_server", "client dropped. cid=%d reason=\"%s\"", client_id, reason); + conn_reset(&s->slots[client_id].conn); + return 0; +} + +int net_server_update(NETSERVER *s) +{ + for(int i = 0; i < NETWORK_MAX_CLIENTS; i++) + { + conn_update(&s->slots[i].conn); + if(conn_error(&s->slots[i].conn)) + net_server_drop(s, i, conn_error(&s->slots[i].conn)); + } + return 0; +} + +int net_server_recv(NETSERVER *s, NETPACKET *packet) +{ + while(1) + { + NETADDR4 addr; + int bytes = net_udp4_recv(s->socket, &addr, s->recv_buffer, NETWORK_MAX_PACKET_SIZE); + + // no more packets for now + if(bytes <= 0) + break; + + NETPACKETDATA data; + int r = check_packet(s->recv_buffer, bytes, &data); + if(r == 0) + { + // ok packet, process it + if(data.flags == NETWORK_PACKETFLAG_CONNECT) + { + // client that wants to connect + int found = 0; + for(int i = 0; i < NETWORK_MAX_CLIENTS; i++) + { + if(s->slots[i].conn.state == NETWORK_CONNSTATE_OFFLINE) + { + conn_feed(&s->slots[i].conn, &data, &addr); + found = 1; + break; + } + } + + if(!found) + { + // TODO: send error + } + } + else + { + // find matching slot + for(int i = 0; i < NETWORK_MAX_CLIENTS; i++) + { + if(net_addr4_cmp(&s->slots[i].conn.peeraddr, &addr) == 0) + { + if(conn_feed(&s->slots[i].conn, &data, &addr)) + { + if(data.data_size) + { + packet->client_id = i; + packet->address = addr; + packet->flags = 0; + packet->data_size = data.data_size; + packet->data = data.data; + return 1; + } + } + } + } + } + } + else + { + // errornous packet, drop it + } + + // read header + // do checksum + } + + return 0; +} + +int net_server_send(NETSERVER *s, NETPACKET *packet) +{ + // TODO: insert stuff for stateless stuff + dbg_assert(packet->client_id >= 0, "errornous client id"); + dbg_assert(packet->client_id < NETWORK_MAX_CLIENTS, "errornous client id"); + conn_send(&s->slots[packet->client_id].conn, 0, packet->data_size, packet->data); + return 0; +} + +void net_server_stats(NETSERVER *s, NETSTATS *stats) +{ + mem_zero(stats, sizeof(NETSTATS)); + + int num_stats = sizeof(NETSTATS)/sizeof(int); + int *istats = (int *)stats; + + for(int c = 0; c < NETWORK_MAX_CLIENTS; c++) + { + int *sstats = (int *)(&(s->slots[c].conn.stats)); + for(int i = 0; i < num_stats; i++) + istats[i] += sstats[i]; + } +} + +// +NETCLIENT *net_client_open(int flags) +{ + NETCLIENT *client = (NETCLIENT *)mem_alloc(sizeof(NETCLIENT), 1); + mem_zero(client, sizeof(NETCLIENT)); + client->socket = net_udp4_create(0); + conn_init(&client->conn, client->socket); + return client; +} + +int net_client_close(NETCLIENT *c) +{ + // TODO: implement me + return 0; +} + +int net_client_update(NETCLIENT *c) +{ + // TODO: implement me + conn_update(&c->conn); + if(conn_error(&c->conn)) + net_client_disconnect(c, conn_error(&c->conn)); + return 0; +} + +int net_client_disconnect(NETCLIENT *c, const char *reason) +{ + // TODO: do this more graceful + dbg_msg("net_client", "disconnected. reason=\"%s\"", reason); + conn_reset(&c->conn); + return 0; +} + +int net_client_connect(NETCLIENT *c, NETADDR4 *addr) +{ + //net_client_disconnect(c); + conn_connect(&c->conn, addr); + return 0; +} + +int net_client_recv(NETCLIENT *c, NETPACKET *packet) +{ + while(1) + { + NETADDR4 addr; + int bytes = net_udp4_recv(c->socket, &addr, c->recv_buffer, NETWORK_MAX_PACKET_SIZE); + + // no more packets for now + if(bytes <= 0) + break; + + NETPACKETDATA data; + int r = check_packet(c->recv_buffer, bytes, &data); + if(r == 0) + { + // ok packet, process it + conn_feed(&c->conn, &data, &addr); + // fill in packet + packet->client_id = 0; + packet->address = addr; + packet->flags = 0; + packet->data_size = data.data_size; + packet->data = data.data; + return 1; + } + else + { + // errornous packet, drop it + } + + // read header + // do checksum + } + + return 0; +} + +int net_client_send(NETCLIENT *c, NETPACKET *packet) +{ + // TODO: insert stuff for stateless stuff + dbg_assert(packet->client_id == 0, "errornous client id"); + conn_send(&c->conn, 0, packet->data_size, packet->data); + return 0; +} + +int net_client_state(NETCLIENT *c) +{ + if(c->conn.state == NETWORK_CONNSTATE_ONLINE) + return NETSTATE_ONLINE; + if(c->conn.state == NETWORK_CONNSTATE_OFFLINE) + return NETSTATE_OFFLINE; + return NETSTATE_CONNECTING; +} + +void net_client_stats(NETCLIENT *c, NETSTATS *stats) +{ + *stats = c->conn.stats; +} diff --git a/src/engine/network.h b/src/engine/network.h new file mode 100644 index 00000000..de035888 --- /dev/null +++ b/src/engine/network.h @@ -0,0 +1,104 @@ + +struct NETPACKET +{ + // -1 means that it's a stateless packet + // 0 on the client means the server + int client_id; + NETADDR4 address; // only used when client_id == -1 + int flags; + int data_size; + const void *data; +}; + +struct NETSTATS +{ + int send_bytes; + int recv_bytes; + int send_packets; + int recv_packets; + + int resend_packets; + int resend_bytes; +}; + +struct NETSERVER; +struct NETCLIENT; + +enum +{ + NETFLAG_ALLOWSTATELESS=1, + PACKETFLAG_VITAL=1, + + NETSTATE_OFFLINE=0, + NETSTATE_CONNECTING, + NETSTATE_ONLINE, +}; + +// server side +NETSERVER *net_server_open(int port, int max_clients, int flags); +int net_server_recv(NETSERVER *s, NETPACKET *packet); +int net_server_send(NETSERVER *s, NETPACKET *packet); +int net_server_close(NETSERVER *s); +int net_server_update(NETSERVER *s); +int net_server_drop(NETSERVER *s, int client_id, const char *reason); +int net_server_newclient(NETSERVER *s); // -1 when no more, else, client id +int net_server_delclient(NETSERVER *s); // -1 when no more, else, client id +void net_server_stats(NETSERVER *s, NETSTATS *stats); + +// client side +NETCLIENT *net_client_open(int flags); +int net_client_disconnect(NETCLIENT *c, const char *reason); +int net_client_connect(NETCLIENT *c, NETADDR4 *addr); +int net_client_recv(NETCLIENT *c, NETPACKET *packet); +int net_client_send(NETCLIENT *c, NETPACKET *packet); +int net_client_close(NETCLIENT *c); +int net_client_update(NETCLIENT *c); +int net_client_state(NETCLIENT *c); +void net_client_stats(NETCLIENT *c, NETSTATS *stats); + + +// wrapper classes for c++ +#ifdef __cplusplus +class net_server +{ + NETSERVER *ptr; +public: + net_server() : ptr(0) {} + ~net_server() { close(); } + + int open(int port, int max, int flags) { ptr = net_server_open(port, max, flags); return ptr != 0; } + int close() { int r = net_server_close(ptr); ptr = 0; return r; } + + int recv(NETPACKET *packet) { return net_server_recv(ptr, packet); } + int send(NETPACKET *packet) { return net_server_send(ptr, packet); } + int update() { return net_server_update(ptr); } + + int drop(int client_id, const char *reason) { return net_server_drop(ptr, client_id, reason); } + int newclient() { return net_server_newclient(ptr); } + int delclient() { return net_server_delclient(ptr); } + + void stats(NETSTATS *stats) { net_server_stats(ptr, stats); } +}; + + +class net_client +{ + NETCLIENT *ptr; +public: + net_client() : ptr(0) {} + ~net_client() { close(); } + + int open(int flags) { ptr = net_client_open(flags); return ptr != 0; } + int close() { int r = net_client_close(ptr); ptr = 0; return r; } + + int connect(NETADDR4 *addr) { return net_client_connect(ptr, addr); } + int disconnect(const char *reason) { return net_client_disconnect(ptr, reason); } + + int recv(NETPACKET *packet) { return net_client_recv(ptr, packet); } + int send(NETPACKET *packet) { return net_client_send(ptr, packet); } + int update() { return net_client_update(ptr); } + + int state() { return net_client_state(ptr); } + void stats(NETSTATS *stats) { net_client_stats(ptr, stats); } +}; +#endif diff --git a/src/engine/packet.h b/src/engine/packet.h index fd93d744..6c92bf31 100644 --- a/src/engine/packet.h +++ b/src/engine/packet.h @@ -1,442 +1,356 @@ +#include #include #include #include "versions.h" +#include "ringbuffer.h" +#include "compression.h" +#include "snapshot.h" -#define MACRO_MAKEINT(a,b,c,d) ((a<<24)|(b<<16)|(c<<8)|d) +enum +{ + NETMSG_NULL=0, + + // sent by server + NETMSG_MAP, + NETMSG_SNAP, + NETMSG_SNAPEMPTY, + NETMSG_SNAPSMALL, + + // sent by client + NETMSG_INFO, + NETMSG_ENTERGAME, + NETMSG_INPUT, + NETMSG_SNAPACK, + + // sent by both + NETMSG_ERROR, +}; -// TODO: this is not KISS -class packet + +// this should be revised +enum { - friend class connection; -protected: - enum - { - MAX_PACKET_SIZE = 1024, - }; - - // packet data - struct header + MAX_NAME_LENGTH=32, + MAX_CLANNAME_LENGTH=32, + MAX_INPUT_SIZE=128, + MAX_SNAPSHOT_SIZE=64*1024, + MAX_SNAPSHOT_PACKSIZE=768 +}; + + +class snapshot_storage +{ + struct holder { - unsigned id; - unsigned version; - unsigned size_and_flags; - unsigned crc; - - unsigned msg; - unsigned ack; - unsigned seq; + int tick; + int data_size; + int *data() { return (int *)(this+1); } }; - unsigned char packet_data[MAX_PACKET_SIZE]; - unsigned char *current; + ring_buffer buffer; - // these are used to prepend data in the packet - // used for debugging so we have checks that we - // pack and unpack the same way - enum +public: + void purge_until(int tick) { - DEBUG_TYPE_INT=0x1, - DEBUG_TYPE_STR=0x2, - DEBUG_TYPE_RAW=0x3, - }; + while(1) + { + ring_buffer::item *i = buffer.first(); + if(!i) + break; + holder *h = (holder *)i->data(); + if(h->tick < tick) + buffer.pop_first(); + else + break; + } + } - // writes an int to the packet - void write_int_raw(int i) + void purge_all() { - // TODO: check for overflow - *(int*)current = i; - current += sizeof(int); + buffer.reset(); } - // reads an int from the packet - int read_int_raw() - { - // TODO: check for overflow - int i = *(int*)current; - current += sizeof(int); - return i; - } - - void debug_insert_mark(int type, int size) + void add(int tick, int data_size, void *data) { - write_int_raw((type<<16)|size); + holder *h = (holder *)buffer.alloc(sizeof(holder)+data_size); + h->tick = tick; + h->data_size = data_size; + mem_copy(h->data(), data, data_size); } - void debug_verify_mark(int type, int size) + int get(int tick, void **data) { - if(read_int_raw() != ((type<<16) | size)) - dbg_assert(0, "error during packet disassembly"); + ring_buffer::item *i = buffer.first(); + while(i) + { + holder *h = (holder *)i->data(); + if(h->tick == tick) + { + *data = h->data(); + return h->data_size; + } + + i = i->next; + } + + return -1; } - +}; +/* +class snapshot_delta_builder +{ public: + static const int MAX_ITEMS = 512; - enum - { - FLAG_VITAL=1, - FLAG_RESEND=2 - }; + char data[MAX_SNAPSHOT_SIZE]; + int data_size; - packet(unsigned msg=0) - { - current = packet_data; - current += sizeof(header); + int offsets[MAX_ITEMS]; + int num_items; + + int top_size; + int top_items; - ((header*)packet_data)->id = MACRO_MAKEINT('K','M','A',1); - ((header*)packet_data)->version = TEEWARS_NETVERSION; - ((header*)packet_data)->msg = msg; + int snapnum; + + snapshot_delta_builder() + { + top_size = 0; + top_items = 0; + snapnum = 0; } - - void set_header(unsigned ack, unsigned seq) + + void start() { - ((header*)packet_data)->ack = ack; - ((header*)packet_data)->seq = seq; + data_size = 0; + num_items = 0; } - - // writes an int to the packet - void write_int(int i) + + int finish(void *snapdata) { - debug_insert_mark(DEBUG_TYPE_INT, 4); - write_int_raw(i); + snapnum++; + + // flattern and make the snapshot + snapshot *snap = (snapshot *)snapdata; + snap->data_size = data_size; + snap->num_items = num_items; + int offset_size = sizeof(int)*num_items; + mem_copy(snap->offsets, offsets, offset_size); + mem_copy(snap->data_start(), data, data_size); + return sizeof(int) + offset_size + data_size; + } + + void *new_item(int type, int id, int size) + { + snapshot::item *obj = (snapshot::item *)(data+data_size); + obj->type_and_id = (type<<16)|id; + offsets[num_items] = data_size; + data_size += sizeof(int) + size; + num_items++; + dbg_assert(data_size < MAX_SNAPSHOT_SIZE, "too much data"); + dbg_assert(num_items < MAX_ITEMS, "too many items"); + + return &obj->data; } +}; +*/ + +class snapshot_builder +{ +public: + static const int MAX_ITEMS = 512; - void write_raw(const char *raw, int size) + char data[MAX_SNAPSHOT_SIZE]; + int data_size; + + int offsets[MAX_ITEMS]; + int num_items; + + int top_size; + int top_items; + + int snapnum; + + snapshot_builder() { - debug_insert_mark(DEBUG_TYPE_RAW, size); - while(size--) - *current++ = *raw++; + top_size = 0; + top_items = 0; + snapnum = 0; } - // writes a string to the packet - void write_str(const char *str) + void start() { - debug_insert_mark(DEBUG_TYPE_STR, 0); - int s = strlen(str)+1; - write_int_raw(s); - for(;*str; current++, str++) - *current = *str; - *current = 0; - current++; + data_size = 0; + num_items = 0; } - // reads an int from the packet - int read_int() + snapshot::item *get_item(int index) { - debug_verify_mark(DEBUG_TYPE_INT, 4); - return read_int_raw(); + return (snapshot::item *)&(data[offsets[index]]); } - // reads a string from the packet - const char *read_str() + int *get_item_data(int key) { - debug_verify_mark(DEBUG_TYPE_STR, 0); - int size = read_int_raw(); - const char *s = (const char *)current; - //dbg_msg("packet", "reading string '%s' (%d)", s, size); - current += size; - return s; + for(int i = 0; i < num_items; i++) + { + if(get_item(i)->key() == key) + return (int *)get_item(i)->data(); + } + return 0; } - - const char *read_raw(int size) + + int finish(void *snapdata) { - debug_verify_mark(DEBUG_TYPE_RAW, size); - const char *d = (const char *)current; - current += size; - return d; + snapnum++; + + // flattern and make the snapshot + snapshot *snap = (snapshot *)snapdata; + snap->data_size = data_size; + snap->num_items = num_items; + int offset_size = sizeof(int)*num_items; + mem_copy(snap->offsets(), offsets, offset_size); + mem_copy(snap->data_start(), data, data_size); + return sizeof(snapshot) + offset_size + data_size; } - - // TODO: impelement this - bool is_good() const { return true; } - unsigned version() const { return ((header*)packet_data)->version; } - unsigned msg() const { return ((header*)packet_data)->msg; } - unsigned seq() const { return ((header*)packet_data)->seq; } - unsigned ack() const { return ((header*)packet_data)->ack; } - unsigned flags() const { return (((header*)packet_data)->size_and_flags) & 0xffff; } - - // access functions to get the size and data - int size() const { return (int)(current-(unsigned char*)packet_data); } - int max_size() const { return MAX_PACKET_SIZE; } - void *data() { return packet_data; } + void *new_item(int type, int id, int size) + { + snapshot::item *obj = (snapshot::item *)(data+data_size); + obj->type_and_id = (type<<16)|id; + offsets[num_items] = data_size; + data_size += sizeof(snapshot::item) + size; + num_items++; + dbg_assert(data_size < MAX_SNAPSHOT_SIZE, "too much data"); + dbg_assert(num_items < MAX_ITEMS, "too many items"); + + return obj->data(); + } }; -// TODO: remove all the allocations from this class -class ring_buffer +class data_packer { - struct item + enum { - item *next; - item *prev; - int size; + BUFFER_SIZE=1024*2 }; - item *first; - item *last; - - unsigned buffer_size; + unsigned char buffer[BUFFER_SIZE]; + unsigned char *current; + unsigned char *end; + int error; public: - ring_buffer() - { - first = 0; - last = 0; - buffer_size = 0; - } - - ~ring_buffer() - { - reset(); - } - void reset() { - // clear all - while(peek_data()) - next(); + error = 0; + current = buffer; + end = current + BUFFER_SIZE; } - void *alloc(int size) + void add_int(int i) { - item *i = (item*)mem_alloc(sizeof(item)+size, 1); - i->size = size; - - i->prev = last; - i->next = 0; - if(last) - last->next = i; - else - first = i; - last = i; - - buffer_size += size; - return (void*)(i+1); - } - - unsigned peek_size() - { - if(!first) - return 0; - return first->size; + // TODO: add space check + // TODO: variable length encoding perhaps + // TODO: add debug marker + current = vint_pack(current, i); + //*current++ = (i>>24)&0xff; + //*current++ = (i>>16)&0xff; + //*current++ = (i>>8)&0xff; + //*current++ = i&0xff; } - void *peek_data() + void add_string(const char *p, int limit) { - if(!first) - return 0; - return (void*)(first+1); - } - - void next() - { - if(first) + // TODO: add space check + // TODO: add debug marker + if(limit > 0) { - item *next = first->next; - buffer_size += first->size; - mem_free(first); - first = next; - if(first) - first->prev = 0; - else - last = 0; + while(*p && limit != 0) + { + *current++ = *p++; + limit--; + } + *current++ = 0; + } + else + { + while(*p) + *current++ = *p++; + *current++ = 0; } } - unsigned size() { return buffer_size; } -}; - -// -class connection -{ - baselib::socket_udp4 *socket; - baselib::netaddr4 addr; - unsigned seq; - unsigned ack; - - unsigned counter_sent_bytes; - unsigned counter_recv_bytes; - - int needs_resend; - - /* - struct resend_packet - { - resend_packet *next; - unsigned seq; - unsigned msg; - unsigned size; - char data[1]; - }; - - resend_packet *first_resend; - resend_packet *last_resend; - */ - - ring_buffer resend_buffer; - - void save_for_resend(packet *p) - { - /* - packet *n = (packet *)resend_buffer.alloc(p->size()); - mem_copy(n->data(), p->data(), p->size()); - n->current = (unsigned char*)n->data() + p->size(); - */ - } - - void remove_resends(unsigned ack) + void add_raw(const unsigned char *data, int size) { - /* - while(1) + // TODO: add space check + // TODO: add debug marker + //add_int(size); + while(size) { - packet *p = (packet *)resend_buffer.peek_data(); - if(!p) - break; - - if(p->seq() > ack) - break; - resend_buffer.next(); - }*/ + *current++ = *data++; + size--; + } } -public: - void counter_reset() + int size() const { - counter_sent_bytes = 0; - counter_recv_bytes = 0; + return (const unsigned char *)current-(const unsigned char *)buffer; } - void counter_get(unsigned *sent, unsigned *recved) + const unsigned char *data() { - *sent = counter_sent_bytes; - *recved = counter_recv_bytes; + return (const unsigned char *)buffer; } +}; - void init(baselib::socket_udp4 *socket, const baselib::netaddr4 *addr) +class data_unpacker +{ + const unsigned char *current; + const unsigned char *start; + const unsigned char *end; + int error; + +public: + void reset(const unsigned char *data, int size) { - resend_buffer.reset(); - - this->addr = *addr; - this->socket = socket; - ack = 0; - seq = 0; - needs_resend = 0; - counter_reset(); + error = 0; + start = data; + end = start + size; + current = start; } - void send(packet *p) + int get_int() { - if(p->flags()&packet::FLAG_VITAL) - seq++; - - p->set_header(ack, seq); - - if(p->flags()&packet::FLAG_VITAL) - save_for_resend(p); - - // TODO: request resend if needed, use needs_resend variable - - socket->send(&address(), p->data(), p->size()); - counter_sent_bytes += p->size(); + int i; + current = vint_unpack(current, &i); + // TODO: might be changed into variable width + // TODO: add range check + // TODO: add debug marker + //i = (current[0]<<24) | (current[1]<<16) | (current[2]<<8) | (current[3]); + //current += 4; + return i; } - packet *feed(packet *p) + const char *get_string() { - counter_recv_bytes += p->size(); - - if(p->flags()&packet::FLAG_VITAL) - { - if(p->seq() == ack+1) - { - // packet in seqence, ack it - ack++; - //dbg_msg("network/connection", "packet in sequence. seq/ack=%x", ack); - return p; - } - else if(p->seq() > ack) - { - // packet loss - needs_resend = 1; - dbg_msg("network/connection", "packet loss! seq=%x ack=%x+1", p->seq(), ack); - return p; - } - else - { - // we already got this packet - return 0x0; - } - } - - // remove resends - remove_resends(p->ack()); - - // handle resends - if(p->flags()&packet::FLAG_RESEND) - { - // peer as requested a resend of all non acked packages. - - } - - return p; + // TODO: add range check + // TODO: add debug marker + const char *ptr = (const char *)current; + while(*current) // skip the string + current++; + current++; + return ptr; } - const baselib::netaddr4 &address() const { return addr; } - - void update() + const unsigned char *get_raw(int size) { + // TODO: add range check + // TODO: add debug marker + //int s = get_int(); + //if(size) + //*size = s; + const unsigned char *ptr = current; + current += size; + return ptr; } }; - -//const char *NETWORK_VERSION = "development"; - -enum -{ - NETMSG_CONTEXT_CONNECT=0x00010000, - NETMSG_CONTEXT_GAME=0x00020000, - NETMSG_CONTEXT_GLOBAL=0x00040000, - - // connection phase - NETMSG_CLIENT_CONNECT=NETMSG_CONTEXT_CONNECT|1, - // str32 name - // str32 clan - // str32 password - // str32 skin - - // TODO: These should be implemented to make the server send the map to the client on connect - // NETMSG_CLIENT_FETCH, - // NETMSG_SERVER_MAPDATA, - - NETMSG_SERVER_ACCEPT=NETMSG_CONTEXT_CONNECT|2, - // str32 mapname - - - NETMSG_CLIENT_DONE=NETMSG_CONTEXT_CONNECT|3, - // nothing - - // game phase - NETMSG_SERVER_SNAP = NETMSG_CONTEXT_GAME|1, // server will spam these - // int num_parts - // int part - // int size - // data * - - NETMSG_CLIENT_INPUT = NETMSG_CONTEXT_GAME|1, // client will spam these - // int input[MAX_INPUTS] - - NETMSG_SERVER_EVENT = NETMSG_CONTEXT_GAME|2, - NETMSG_CLIENT_EVENT = NETMSG_CONTEXT_GAME|2, - - NETMSG_CLIENT_CHECKALIVE = NETMSG_CONTEXT_GAME|3, // check if client is alive - - NETMSG_CLIENT_ERROR=0x0fffffff, - // str128 reason - - NETMSG_SERVER_ERROR=0x0fffffff, - // str128 reason -}; - -enum -{ - MAX_NAME_LENGTH=32, - MAX_CLANNAME_LENGTH=32, - MAX_INPUT_SIZE=128, - MAX_SNAPSHOT_SIZE=64*1024, - MAX_SNAPSHOT_PACKSIZE=768 -}; diff --git a/src/engine/ringbuffer.h b/src/engine/ringbuffer.h new file mode 100644 index 00000000..3208efbf --- /dev/null +++ b/src/engine/ringbuffer.h @@ -0,0 +1,84 @@ + +// TODO: remove all the allocations from this class +class ring_buffer +{ +public: + struct item + { + item *next; + item *prev; + int size; + unsigned char *data() { return (unsigned char *)(this+1); } + }; + + item *first_item; + item *last_item; + + unsigned buffer_size; + + ring_buffer() + { + first_item = 0; + last_item = 0; + buffer_size = 0; + } + + ~ring_buffer() + { + reset(); + } + + void reset() + { + // clear all + while(first()) + pop_first(); + } + + void *alloc(int size) + { + item *i = (item*)mem_alloc(sizeof(item)+size, 1); + i->size = size; + + i->prev = last_item; + i->next = 0; + if(last_item) + last_item->next = i; + else + first_item = i; + last_item = i; + + buffer_size += size; + return i->data(); + } + + item *first() + { + return first_item; + } + + /* + void *peek_data() + { + if(!first) + return 0; + return (void*)(first+1); + }*/ + + void pop_first() + { + if(first_item) + { + item *next = first_item->next; + buffer_size -= first_item->size; + mem_free(first_item); + first_item = next; + if(first_item) + first_item->prev = 0; + else + last_item = 0; + } + } + + unsigned size() { return buffer_size; } +}; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index d0ae80f0..5632689e 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -1,138 +1,23 @@ -#include -#include +#include +#include +#include #include #include -//#include "socket.h" #include #include -#include +#include #include -namespace baselib {} -using namespace baselib; - -int net_addr4_cmp(const NETADDR4 *a, const NETADDR4 *b) -{ - if( - a->ip[0] != b->ip[0] || - a->ip[1] != b->ip[1] || - a->ip[2] != b->ip[2] || - a->ip[3] != b->ip[3] || - a->port != b->port - ) - return 1; - return 0; -} - -// --- string handling (MOVE THESE!!) --- -void snap_encode_string(const char *src, int *dst, int length, int max_length) -{ - const unsigned char *p = (const unsigned char *)src; - - // handle whole int - for(int i = 0; i < length/4; i++) - { - *dst = (p[0]<<24|p[1]<<16|p[2]<<8|p[3]); - p += 4; - dst++; - } - - // take care of the left overs - int left = length%4; - if(left) - { - unsigned last = 0; - switch(left) - { - case 3: last |= p[2]<<8; - case 2: last |= p[1]<<16; - case 1: last |= p[0]<<24; - } - *dst = last; - } -} - - -class snapshot_builder -{ -public: - static const int MAX_ITEMS = 512; - //static const int MAX_DATA_SIZE=1*1024; - - char data[MAX_SNAPSHOT_SIZE]; - int data_size; - - int offsets[MAX_ITEMS]; - int num_items; - - int top_size; - int top_items; - - int snapnum; - - snapshot_builder() - { - top_size = 0; - top_items = 0; - snapnum = 0; - } - - void start() - { - data_size = 0; - num_items = 0; - } +#include +#include - int finish(void *snapdata) - { - snapnum++; - // collect some data - /* - int change = 0; - if(data_size > top_size) - { - change++; - top_size = data_size; - } - - if(num_items > top_items) - { - change++; - top_items = num_items; - } - - if(change) - { - dbg_msg("snapshot", "new top, items=%d size=%d", top_items, top_size); - }*/ - - // flattern and make the snapshot - snapshot *snap = (snapshot *)snapdata; - snap->num_items = num_items; - int offset_size = sizeof(int)*num_items; - mem_copy(snap->offsets, offsets, offset_size); - mem_copy(snap->data_start(), data, data_size); - return sizeof(int) + offset_size + data_size; - } - - void *new_item(int type, int id, int size) - { - snapshot::item *obj = (snapshot::item *)(data+data_size); - obj->type_and_id = (type<<16)|id; - offsets[num_items] = data_size; - data_size += sizeof(int) + size; - num_items++; - dbg_assert(data_size < MAX_SNAPSHOT_SIZE, "too much data"); - dbg_assert(num_items < MAX_ITEMS, "too many items"); - - return &obj->data; - } -}; +namespace baselib {} +using namespace baselib; static snapshot_builder builder; @@ -143,7 +28,6 @@ void *snap_new_item(int type, int id, int size) return builder.new_item(type, id, size); } - // class client { @@ -157,34 +41,21 @@ public: // connection state info int state; - - // (ticks) if lastactivity > 5 seconds kick him - int64 lastactivity; - connection conn; + + int last_acked_snapshot; + snapshot_storage snapshots; char name[MAX_NAME_LENGTH]; char clan[MAX_CLANNAME_LENGTH]; - /* - client() - { - state = STATE_EMPTY; - name[0] = 0; - clan[0] = 0; - } - - ~client() - { - dbg_assert(state == STATE_EMPTY, "client destoyed while in use"); - }*/ bool is_empty() const { return state == STATE_EMPTY; } bool is_ingame() const { return state == STATE_INGAME; } - const netaddr4 &address() const { return conn.address(); } }; static client clients[MAX_CLIENTS]; static int current_tick = 0; static int send_heartbeats = 1; +static net_server net; int server_tick() { @@ -193,7 +64,7 @@ int server_tick() int server_tickspeed() { - return 50; + return SERVER_TICK_SPEED; } int server_init() @@ -203,7 +74,7 @@ int server_init() clients[i].state = client::STATE_EMPTY; clients[i].name[0] = 0; clients[i].clan[0] = 0; - clients[i].lastactivity = 0; + //clients[i].lastactivity = 0; } current_tick = 0; @@ -225,12 +96,27 @@ int server_getclientinfo(int client_id, client_info *info) return 0; } -// + +int server_send_msg(int client_id) +{ + const msg_info *info = msg_get_info(); + NETPACKET packet; + packet.client_id = client_id; + packet.data = info->data; + packet.data_size = info->size; + + if(info->flags&MSGFLAG_VITAL) + packet.flags = PACKETFLAG_VITAL; + + net.send(&packet); + return 0; +} + +// TODO: remove this class class server { public: - - socket_udp4 game_socket; + //socket_udp4 game_socket; const char *map_name; const char *server_name; @@ -256,14 +142,14 @@ public: } // start server - if(!game_socket.open(8303)) + if(!net.open(8303, 0, 0)) { dbg_msg("network/server", "couldn't open socket"); return false; } - for(int i = 0; i < MAX_CLIENTS; i++) - dbg_msg("network/server", "\t%d: %d", i, clients[i].state); + //for(int i = 0; i < MAX_CLIENTS; i++) + //dbg_msg("network/server", "\t%d: %d", i, clients[i].state); if (net_host_lookup(MASTER_SERVER_ADDRESS, MASTER_SERVER_PORT, &master_server) != 0) { @@ -304,17 +190,6 @@ public: snaptime += time_get()-start; } - // Check for client timeouts - for (int i = 0; i < MAX_CLIENTS; i++) - { - if (clients[i].state != client::STATE_EMPTY) - { - // check last activity time - if (((lasttick - clients[i].lastactivity) / time_freq()) > SERVER_CLIENT_TIMEOUT) - client_timeout(i); - } - } - lasttick += time_per_tick; } @@ -332,8 +207,7 @@ public: // TODO: fix me netaddr4 me(127, 0, 0, 0, 8303); - - send_heartbeat(0, &me, players, MAX_CLIENTS, server_name, mapname); + //send_heartbeat(0, &me, players, MAX_CLIENTS, server_name, mapname); } lastheartbeat = t+time_per_heartbeat; @@ -357,6 +231,7 @@ public: (simulationtime+snaptime+networktime)/(float)reportinterval*100.0f); unsigned sent_total=0, recv_total=0; + /* for (int i = 0; i < MAX_CLIENTS; i++) if (!clients[i].is_empty()) { @@ -366,7 +241,7 @@ public: sent_total += s; recv_total += r; } - + */ dbg_msg("server/report", "biggestsnap=%d send=%d recv=%d", biggest_snapshot, sent_total/3, recv_total/3); @@ -393,6 +268,8 @@ public: void snap() { + //if(current_tick&1) + // return; mods_presnap(); for(int i = 0; i < MAX_CLIENTS; i++) @@ -400,33 +277,86 @@ public: if(clients[i].is_ingame()) { char data[MAX_SNAPSHOT_SIZE]; + char deltadata[MAX_SNAPSHOT_SIZE]; char compdata[MAX_SNAPSHOT_SIZE]; + //char intdata[MAX_SNAPSHOT_SIZE]; builder.start(); mods_snap(i); // finish snapshot int snapshot_size = builder.finish(data); - // compress it - int compsize = lzw_compress(data, snapshot_size, compdata); - snapshot_size = compsize; - - if(snapshot_size > biggest_snapshot) - biggest_snapshot = snapshot_size; - - const int max_size = MAX_SNAPSHOT_PACKSIZE; - int numpackets = (snapshot_size+max_size-1)/max_size; - for(int n = 0, left = snapshot_size; left; n++) + // remove old snapshos + // keep 1 seconds worth of snapshots + clients[i].snapshots.purge_until(current_tick-SERVER_TICK_SPEED); + + // save it the snapshot + clients[i].snapshots.add(current_tick, snapshot_size, data); + + // find snapshot that we can preform delta against + static snapshot emptysnap; + emptysnap.data_size = 0; + emptysnap.num_items = 0; + + snapshot *deltashot = &emptysnap; + int deltashot_size; + int delta_tick = -1; + { + void *delta_data; + deltashot_size = clients[i].snapshots.get(clients[i].last_acked_snapshot, (void **)&delta_data); + if(deltashot_size >= 0) + { + delta_tick = clients[i].last_acked_snapshot; + deltashot = (snapshot *)delta_data; + } + } + + // create delta + int deltasize = snapshot_create_delta(deltashot, (snapshot*)data, deltadata); + + if(deltasize) { - int chunk = left < max_size ? left : max_size; - left -= chunk; - - packet p(NETMSG_SERVER_SNAP); - p.write_int(numpackets); - p.write_int(n); - p.write_int(chunk); - p.write_raw(&compdata[n*max_size], chunk); - clients[i].conn.send(&p); + // compress it + //int intsize = -1; + unsigned char intdata[MAX_SNAPSHOT_SIZE]; + int intsize = intpack_compress(deltadata, deltasize, intdata); + + int compsize = zerobit_compress(intdata, intsize, compdata); + //dbg_msg("compress", "%5d --delta-> %5d --int-> %5d --zero-> %5d %5d", + //snapshot_size, deltasize, intsize, compsize, intsize-compsize); + snapshot_size = compsize; + + if(snapshot_size > biggest_snapshot) + biggest_snapshot = snapshot_size; + + const int max_size = MAX_SNAPSHOT_PACKSIZE; + int numpackets = (snapshot_size+max_size-1)/max_size; + for(int n = 0, left = snapshot_size; left; n++) + { + int chunk = left < max_size ? left : max_size; + left -= chunk; + + if(numpackets == 1) + msg_pack_start(NETMSG_SNAPSMALL, 0); + else + msg_pack_start(NETMSG_SNAP, 0); + msg_pack_int(current_tick); + msg_pack_int(current_tick-delta_tick); // compressed with + msg_pack_int(chunk); + msg_pack_raw(&compdata[n*max_size], chunk); + msg_pack_end(); + //const msg_info *info = msg_get_info(); + //dbg_msg("server", "size=%d", info->size); + server_send_msg(i); + } + } + else + { + msg_pack_start(NETMSG_SNAPEMPTY, 0); + msg_pack_int(current_tick); + msg_pack_int(current_tick-delta_tick); // compressed with + msg_pack_end(); + server_send_msg(i); } } } @@ -434,11 +364,12 @@ public: mods_postsnap(); } - void send_accept(client *client, const char *map) + void send_map(int cid) { - packet p(NETMSG_SERVER_ACCEPT); - p.write_str(map); - client->conn.send(&p); + msg_pack_start(NETMSG_MAP, MSGFLAG_VITAL); + msg_pack_string(map_name, 0); + msg_pack_end(); + server_send_msg(cid); } void drop(int cid, const char *reason) @@ -451,161 +382,47 @@ public: dbg_msg("game", "player dropped. reason='%s' cid=%x name='%s'", reason, cid, clients[cid].name); } - int find_client(const netaddr4 *addr) + void process_client_packet(NETPACKET *packet) { - // fetch client - for(int i = 0; i < MAX_CLIENTS; i++) + int cid = packet->client_id; + int msg = msg_unpack_start(packet->data, packet->data_size); + if(msg == NETMSG_INFO) { - if(!clients[i].is_empty() && clients[i].address() == *addr) - return i; + strncpy(clients[cid].name, msg_unpack_string(), MAX_NAME_LENGTH); + strncpy(clients[cid].clan, msg_unpack_string(), MAX_CLANNAME_LENGTH); + const char *password = msg_unpack_string(); + const char *skin = msg_unpack_string(); + (void)password; // ignore these variables + (void)skin; + send_map(cid); } - return -1; - } - - void client_process_packet(int cid, packet *p) - { - clients[cid].lastactivity = lasttick; - if(p->msg() == NETMSG_CLIENT_DONE) + else if(msg == NETMSG_ENTERGAME) { dbg_msg("game", "player as entered the game. cid=%x", cid); clients[cid].state = client::STATE_INGAME; mods_client_enter(cid); } - else if(p->msg() == NETMSG_CLIENT_INPUT) + else if(msg == NETMSG_INPUT) { int input[MAX_INPUT_SIZE]; - int size = p->read_int(); + int size = msg_unpack_int(); for(int i = 0; i < size/4; i++) - input[i] = p->read_int(); - if(p->is_good()) - { - //dbg_msg("network/server", "applying input %d %d %d", input[0], input[1], input[2]); - mods_client_input(cid, input); - } + input[i] = msg_unpack_int(); + mods_client_input(cid, input); } - else if(p->msg() == NETMSG_CLIENT_ERROR) + else if(msg == NETMSG_SNAPACK) { - const char *reason = p->read_str(); - if(p->is_good()) - dbg_msg("network/server", "client error. cid=%x reason='%s'", cid, reason); - else - dbg_msg("network/server", "client error. cid=%x", cid); - drop(cid, "client error"); + clients[cid].last_acked_snapshot = msg_unpack_int(); } else { - dbg_msg("network/server", "invalid message. cid=%x msg=%x", cid, p->msg()); - drop(cid, "invalid message"); + dbg_msg("server", "strange message cid=%d msg=%d data_size=%d", cid, msg, packet->data_size); } + } - void process_packet(packet *p, netaddr4 *from) + void process_packet(NETPACKET *packet) { - // do version check - if(p->version() != TEEWARS_NETVERSION) - { - // send an empty packet back. - // this will allow the client to check the version - packet p; - game_socket.send(from, p.data(), p.size()); - return; - } - - if(p->msg() == NETMSG_CLIENT_CONNECT) - { - // we got no state for this client yet - const char *version; - const char *name; - const char *clan; - const char *password; - const char *skin; - - version = p->read_str(); - name = p->read_str(); - clan = p->read_str(); - password = p->read_str(); - skin = p->read_str(); - - if(p->is_good()) - { - /* - // check version - if(strcmp(version, TEEWARS_NETVERSION) != 0) - { - dbg_msg("network/server", "wrong version connecting '%s'", version); - // TODO: send error - return; - }*/ - - // look for empty slot, linear search - int id = -1; - for(int i = 0; i < MAX_CLIENTS; i++) - if(clients[i].is_empty()) - { - id = i; - break; - } - - if(id != -1) - { - // slot found - // TODO: perform correct copy here - mem_copy(clients[id].name, name, MAX_NAME_LENGTH); - mem_copy(clients[id].clan, clan, MAX_CLANNAME_LENGTH); - clients[id].state = client::STATE_CONNECTING; - clients[id].conn.init(&game_socket, from); - - clients[id].lastactivity = lasttick; - clients[id].name[MAX_NAME_LENGTH-1] = 0; - clients[id].clan[MAX_CLANNAME_LENGTH-1] = 0; - - dbg_msg("network/server", "client connected. '%s' on slot %d", name, id); - - // TODO: return success - send_accept(&clients[id], map_name); - } - else - { - // no slot found - // TODO: send error - dbg_msg("network/server", "client connected but server is full"); - - for(int i = 0; i < MAX_CLIENTS; i++) - dbg_msg("network/server", "\t%d: %d", i, clients[i].state); - } - } - } - else - { - int cid = find_client(from); - if(cid >= 0) - { - if(clients[cid].conn.feed(p)) - { - // packet is ok - unsigned msg = p->msg(); - - // client found, check state - if(((msg>>16)&0xff)&clients[cid].state) - { - // state is ok - client_process_packet(cid, p); - } - else - { - // invalid state, disconnect the client - drop(cid, "invalid message at this state"); - } - } - else - { - drop(cid, "connection error"); - } - - } - else - dbg_msg("network/server", "packet from strange address."); - } } void client_timeout(int clientId) @@ -615,67 +432,53 @@ public: void pump_network() { + net.update(); + + // process packets + NETPACKET packet; + while(net.recv(&packet)) + { + + if(packet.client_id == -1) + { + // stateless + } + else + process_client_packet(&packet); + } + + // check for removed clients while(1) { - packet p; - netaddr4 from; - - //int bytes = net_udp4_recv( - int bytes = game_socket.recv(&from, p.data(), p.max_size()); - //int bytes = game_socket.recv(&from, p.data(), p.max_size()); - if(bytes <= 0) + int cid = net.delclient(); + if(cid == -1) break; - - process_packet(&p, &from); + + clients[cid].state = client::STATE_EMPTY; + clients[cid].name[0] = 0; + clients[cid].clan[0] = 0; + clients[cid].snapshots.purge_all(); + + mods_client_drop(cid); + + dbg_msg("server", "del client %d", cid); + } + + // check for new clients + while(1) + { + int cid = net.newclient(); + if(cid == -1) + break; + + clients[cid].state = client::STATE_CONNECTING; + clients[cid].name[0] = 0; + clients[cid].clan[0] = 0; + clients[cid].snapshots.purge_all(); + clients[cid].last_acked_snapshot = -1; + + dbg_msg("server", "new client %d", cid); } - // TODO: check for client timeouts - } - - char *write_int(char *buffer, int integer) - { - *buffer++ = integer >> 24; - *buffer++ = integer >> 16; - *buffer++ = integer >> 8; - *buffer++ = integer; - - return buffer; - } - - char *write_netaddr4(char *buffer, NETADDR4 *address) - { - *buffer++ = address->ip[0]; - *buffer++ = address->ip[1]; - *buffer++ = address->ip[2]; - *buffer++ = address->ip[3]; - - return write_int(buffer, address->port); - } - - void send_heartbeat(int version, netaddr4 *address, int players, int max_players, const char *name, const char *map_name) - { - char buffer[216] = {0}; - char *d = buffer; - - d = write_int(d, 'TWHB'); - d = write_int(d, version); - d = write_netaddr4(d, address); - d = write_int(d,players); - d = write_int(d, max_players); - - int len = strlen(name); - if (len > 128) - len = 128; - - memcpy(d, name, len); - d += 128; - - len = strlen(map_name); - if (len > 64) - len = 64; - - memcpy(d, map_name, len); - d += 64; - game_socket.send(&master_server, buffer, sizeof(buffer)); } }; @@ -683,8 +486,14 @@ int main(int argc, char **argv) { dbg_msg("server", "starting..."); + dbg_msg("server", "%d %d", sizeof(snapshot), sizeof(snapshot::item)); + + config_reset(); + config_load("server.cfg"); + const char *mapname = "data/demo.map"; const char *servername = 0; + // parse arguments for(int i = 1; i < argc; i++) { @@ -705,6 +514,12 @@ int main(int argc, char **argv) // -p (private server) send_heartbeats = 0; } + else if(argv[i][0] == '-' && argv[i][1] == 'o' && argv[i][2] == 0) + { + // -o port + i++; + config_set_sv_port(&config, atol(argv[i])); + } } if(!mapname) diff --git a/src/engine/snapshot.cpp b/src/engine/snapshot.cpp new file mode 100644 index 00000000..2f6f1f36 --- /dev/null +++ b/src/engine/snapshot.cpp @@ -0,0 +1,235 @@ + +#include "packet.h" +#include "snapshot.h" + +struct snapshot_delta +{ + int num_deleted_items; + int num_update_items; + int num_temp_items; // needed? + int data[1]; + + /* + char *data_start() { return (char *)&offsets[num_deleted_items+num_update_items+num_temp_items]; } + + int deleted_item(int index) { return offsets[index]; } + item *update_item(int index) { return (item *)(data_start() + offsets[num_deleted_items+index]); } + item *temp_item(int index) { return (item *)(data_start() + offsets[num_deleted_items+num_update_items+index]); } + * */ +}; + + +static const int MAX_ITEMS = 512; +static snapshot_delta empty = {0,0,0,{0}}; + +void *snapshot_empty_delta() +{ + return ∅ +} + +static int diff_item(int *past, int *current, int *out, int size) +{ + /* + int needed = 0; + while(size) + { + *out = *current-*past; + if(*out) + needed = 1; + out++; + current++; + past++; + size--; + }*/ + + int needed = 0; + while(size) + { + *out = *current-*past; + if(*out) + needed = 1; + + out++; + past++; + current++; + size--; + } + + return needed; +} + +// 1 = 4-3 +// d = n-p + +// n(4) = p(3)+d(1) + +static void undiff_item(int *past, int *diff, int *out, int size) +{ + while(size) + { + *out = *past+*diff; + out++; + past++; + diff++; + size--; + } +} + +int snapshot_create_delta(snapshot *from, snapshot *to, void *dstdata) +{ + //int deleted[MAX_ITEMS]; + //int update[MAX_ITEMS]; + //int mark[MAX_ITEMS]; + //char data[MAX_SNAPSHOT_SIZE]; + + snapshot_delta *delta = (snapshot_delta *)dstdata; + int *data = (int *)delta->data; + + + delta->num_deleted_items = 0; + delta->num_update_items = 0; + delta->num_temp_items = 0; + + // pack deleted stuff + for(int i = 0; i < from->num_items; i++) + { + snapshot::item *fromitem = from->get_item(i); + if(to->get_item_index(fromitem->key()) == -1) + { + // deleted + delta->num_deleted_items++; + *data = fromitem->key(); + data++; + } + } + + // pack updated stuff + int count = 0, size_count = 0; + for(int i = 0; i < to->num_items; i++) + { + // do delta + int itemsize = to->get_item_datasize(i); + + snapshot::item *curitem = to->get_item(i); + int pastindex = from->get_item_index(curitem->key()); + if(pastindex != -1) + { + snapshot::item *pastitem = from->get_item(pastindex); + if(diff_item((int*)pastitem->data(), (int*)curitem->data(), data+3, itemsize/4)) + { + *data++ = itemsize; + *data++ = curitem->type(); + *data++ = curitem->id(); + //*data++ = curitem->key(); + data += itemsize/4; + delta->num_update_items++; + } + } + else + { + *data++ = itemsize; + *data++ = curitem->type(); + *data++ = curitem->id(); + //*data++ = curitem->key(); + + mem_copy(data, curitem->data(), itemsize); + size_count += itemsize; + data += itemsize/4; + delta->num_update_items++; + count++; + } + } + + if(0) + { + dbg_msg("snapshot", "%d %d %d", + delta->num_deleted_items, + delta->num_update_items, + delta->num_temp_items); + } + + // TODO: pack temp stuff + + // finish + //mem_copy(delta->offsets, deleted, delta->num_deleted_items*sizeof(int)); + //mem_copy(&(delta->offsets[delta->num_deleted_items]), update, delta->num_update_items*sizeof(int)); + //mem_copy(&(delta->offsets[delta->num_deleted_items+delta->num_update_items]), temp, delta->num_temp_items*sizeof(int)); + //mem_copy(delta->data_start(), data, data_size); + //delta->data_size = data_size; + + if(!delta->num_deleted_items && !delta->num_update_items && !delta->num_temp_items) + return 0; + + return (int)((char*)data-(char*)dstdata); +} + +int snapshot_unpack_delta(snapshot *from, snapshot *to, void *srcdata, int data_size) +{ + snapshot_builder builder; + snapshot_delta *delta = (snapshot_delta *)srcdata; + int *data = (int *)delta->data; + + builder.start(); + + // unpack deleted stuff + int *deleted = data; + data += delta->num_deleted_items; + + // copy all non deleted stuff + for(int i = 0; i < from->num_items; i++) + { + //dbg_assert(0, "fail!"); + snapshot::item *fromitem = from->get_item(i); + int itemsize = from->get_item_datasize(i); + int keep = 1; + for(int d = 0; d < delta->num_deleted_items; d++) + { + if(deleted[d] == fromitem->key()) + { + keep = 0; + break; + } + } + + if(keep) + { + // keep it + int *newdata = (int *)(snapshot::item *)builder.new_item(fromitem->type(), fromitem->id(), itemsize); + mem_copy(newdata, fromitem->data(), itemsize); + } + } + + // unpack updated stuff + for(int i = 0; i < delta->num_update_items; i++) + { + int itemsize, id, type, key; + itemsize = *data++; + //key = *data++; + type = *data++; + id = *data++; + + key = (type<<16)|id; + + // create the item if needed + int *newdata = builder.get_item_data(key); + if(!newdata) + newdata = (int *)builder.new_item(key>>16, key&0xffff, itemsize); + + int fromindex = from->get_item_index(key); + if(fromindex != -1) + { + // we got an update so we need to apply the diff + int *pastdata = (int *)from->get_item(fromindex)->data(); + undiff_item(pastdata, data, newdata, itemsize/4); + } + else // no previous, just copy the data + mem_copy(newdata, data, itemsize); + + data += itemsize/4; + } + + // TODO: unpack temp stuff + + // finish up + return builder.finish(to); +} diff --git a/src/engine/snapshot.h b/src/engine/snapshot.h index 9d803486..9af94a3b 100644 --- a/src/engine/snapshot.h +++ b/src/engine/snapshot.h @@ -1,19 +1,47 @@ +#ifndef FILE_SNAPSHOT_H +#define FILE_SNAPSHOT_H struct snapshot { + int data_size; int num_items; - int offsets[1]; - + struct item { int type_and_id; - char data[1]; - + + int *data() { return (int *)(this+1); } int type() { return type_and_id>>16; } int id() { return type_and_id&(0xffff); } + int key() { return type_and_id; } }; + + int *offsets() { return (int *)(this+1); } + char *data_start() { return (char *)(offsets() + num_items); } + item *get_item(int index) { return (item *)(data_start() + offsets()[index]); }; - char *data_start() { return (char *)&offsets[num_items]; } - item *get_item(int index) { return (item *)(data_start() + offsets[index]); }; + // returns the number of ints in the item data + int get_item_datasize(int index) + { + if(index == num_items-1) + return (data_size - offsets()[index]) - sizeof(item); + return (offsets()[index+1] - offsets()[index]) - sizeof(item); + } + + int get_item_index(int key) + { + // TODO: this should not be a linear search. very bad + for(int i = 0; i < num_items; i++) + { + if(get_item(i)->key() == key) + return i; + } + return -1; + } }; +void *snapshot_empty_delta(); +int snapshot_create_delta(snapshot *from, snapshot *to, void *data); +int snapshot_unpack_delta(snapshot *from, snapshot *to, void *data, int data_size); + +#endif // FILE_SNAPSHOT_H diff --git a/src/engine/versions.h b/src/engine/versions.h index f5fceb48..7b4fdc2a 100644 --- a/src/engine/versions.h +++ b/src/engine/versions.h @@ -1,3 +1,3 @@ #define TEEWARS_NETVERSION 0xffffffff //#define TEEWARS_NETVERSION_STRING "dev v2" -#define TEEWARS_VERSION "0.2.1-dev" +#define TEEWARS_VERSION "0.2.0-dev" -- cgit 1.4.1