diff options
Diffstat (limited to 'src/engine/e_demorec.cpp')
| -rw-r--r-- | src/engine/e_demorec.cpp | 640 |
1 files changed, 640 insertions, 0 deletions
diff --git a/src/engine/e_demorec.cpp b/src/engine/e_demorec.cpp new file mode 100644 index 00000000..1bab1b8d --- /dev/null +++ b/src/engine/e_demorec.cpp @@ -0,0 +1,640 @@ +#include <base/system.h> +#include "e_demorec.h" +#include "e_memheap.h" +#include "e_snapshot.h" +#include "e_compression.h" +#include "e_network.h" +#include "e_engine.h" +#include "e_if_other.h" + +static IOHANDLE record_file = 0; +static const unsigned char header_marker[8] = {'T', 'W', 'D', 'E', 'M', 'O', 0, 1}; + +/* Record */ +static int record_lasttickmarker = -1; +static int record_lastkeyframe; +static unsigned char record_lastsnapshotdata[CSnapshot::MAX_SIZE]; + +int demorec_isrecording() { return record_file != 0; } + +int demorec_record_start(const char *filename, const char *netversion, const char *map, int crc, const char *type) +{ + DEMOREC_HEADER header; + if(record_file) + return -1; + + record_file = engine_openfile(filename, IOFLAG_WRITE); + + if(!record_file) + { + dbg_msg("demorec/record", "Unable to open '%s' for recording", filename); + return -1; + } + + /* write header */ + mem_zero(&header, sizeof(header)); + mem_copy(header.marker, header_marker, sizeof(header.marker)); + str_copy(header.netversion, netversion, sizeof(header.netversion)); + str_copy(header.map, map, sizeof(header.map)); + str_copy(header.type, type, sizeof(header.type)); + header.crc[0] = (crc>>24)&0xff; + header.crc[1] = (crc>>16)&0xff; + header.crc[2] = (crc>>8)&0xff; + header.crc[3] = (crc)&0xff; + io_write(record_file, &header, sizeof(header)); + + record_lastkeyframe = -1; + record_lasttickmarker = -1; + + dbg_msg("demorec/record", "Recording to '%s'", filename); + return 0; +} + +/* + Tickmarker + 7 = Always set + 6 = Keyframe flag + 0-5 = Delta tick + + Normal + 7 = Not set + 5-6 = Type + 0-4 = Size +*/ + +enum +{ + CHUNKTYPEFLAG_TICKMARKER = 0x80, + CHUNKTICKFLAG_KEYFRAME = 0x40, /* only when tickmarker is set*/ + + CHUNKMASK_TICK = 0x3f, + CHUNKMASK_TYPE = 0x60, + CHUNKMASK_SIZE = 0x1f, + + CHUNKTYPE_SNAPSHOT = 1, + CHUNKTYPE_MESSAGE = 2, + CHUNKTYPE_DELTA = 3, + + CHUNKFLAG_BIGSIZE = 0x10 +}; + +static void demorec_record_write_tickmarker(int tick, int keyframe) +{ + if(record_lasttickmarker == -1 || tick-record_lasttickmarker > 63 || keyframe) + { + unsigned char chunk[5]; + chunk[0] = CHUNKTYPEFLAG_TICKMARKER; + chunk[1] = (tick>>24)&0xff; + chunk[2] = (tick>>16)&0xff; + chunk[3] = (tick>>8)&0xff; + chunk[4] = (tick)&0xff; + + if(keyframe) + chunk[0] |= CHUNKTICKFLAG_KEYFRAME; + + io_write(record_file, chunk, sizeof(chunk)); + } + else + { + unsigned char chunk[1]; + chunk[0] = CHUNKTYPEFLAG_TICKMARKER | (tick-record_lasttickmarker); + io_write(record_file, chunk, sizeof(chunk)); + } + + record_lasttickmarker = tick; +} + +static void demorec_record_write(int type, const void *data, int size) +{ + char buffer[64*1024]; + char buffer2[64*1024]; + unsigned char chunk[3]; + + if(!record_file) + return; + + + /* pad the data with 0 so we get an alignment of 4, + else the compression won't work and miss some bytes */ + mem_copy(buffer2, data, size); + while(size&3) + buffer2[size++] = 0; + size = intpack_compress(buffer2, size, buffer); /* buffer2 -> buffer */ + size = CNetBase::Compress(buffer, size, buffer2, sizeof(buffer2)); /* buffer -> buffer2 */ + + + chunk[0] = ((type&0x3)<<5); + if(size < 30) + { + chunk[0] |= size; + io_write(record_file, chunk, 1); + } + else + { + if(size < 256) + { + chunk[0] |= 30; + chunk[1] = size&0xff; + io_write(record_file, chunk, 2); + } + else + { + chunk[0] |= 31; + chunk[1] = size&0xff; + chunk[2] = size>>8; + io_write(record_file, chunk, 3); + } + } + + io_write(record_file, buffer2, size); +} + +void demorec_record_snapshot(int tick, const void *data, int size) +{ + if(record_lastkeyframe == -1 || (tick-record_lastkeyframe) > SERVER_TICK_SPEED*5) + { + /* write full tickmarker */ + demorec_record_write_tickmarker(tick, 1); + + /* write snapshot */ + demorec_record_write(CHUNKTYPE_SNAPSHOT, data, size); + + record_lastkeyframe = tick; + mem_copy(record_lastsnapshotdata, data, size); + } + else + { + /* create delta, prepend tick */ + char delta_data[CSnapshot::MAX_SIZE+sizeof(int)]; + int delta_size; + + /* write tickmarker */ + demorec_record_write_tickmarker(tick, 0); + + delta_size = CSnapshot::CreateDelta((CSnapshot*)record_lastsnapshotdata, (CSnapshot*)data, &delta_data); + if(delta_size) + { + /* record delta */ + demorec_record_write(CHUNKTYPE_DELTA, delta_data, delta_size); + mem_copy(record_lastsnapshotdata, data, size); + } + } +} + +void demorec_record_message(const void *data, int size) +{ + demorec_record_write(CHUNKTYPE_MESSAGE, data, size); +} + +int demorec_record_stop() +{ + if(!record_file) + return -1; + + dbg_msg("demorec/record", "Stopped recording"); + io_close(record_file); + record_file = 0; + return 0; +} + +/* Playback */ +typedef struct KEYFRAME +{ + long filepos; + int tick; +} KEYFRAME; + +typedef struct KEYFRAME_SEARCH +{ + KEYFRAME frame; + struct KEYFRAME_SEARCH *next; +} KEYFRAME_SEARCH; + +static IOHANDLE play_file = 0; +static DEMOREC_PLAYCALLBACK play_callback_snapshot = 0; +static DEMOREC_PLAYCALLBACK play_callback_message = 0; +static KEYFRAME *keyframes = 0; + +static DEMOREC_PLAYBACKINFO playbackinfo; +static unsigned char playback_lastsnapshotdata[CSnapshot::MAX_SIZE]; +static int playback_lastsnapshotdata_size = -1; + + +const DEMOREC_PLAYBACKINFO *demorec_playback_info() { return &playbackinfo; } +int demorec_isplaying() { return play_file != 0; } + +int demorec_playback_registercallbacks(DEMOREC_PLAYCALLBACK snapshot_cb, DEMOREC_PLAYCALLBACK message_cb) +{ + play_callback_snapshot = snapshot_cb; + play_callback_message = message_cb; + return 0; +} + +static int read_chunk_header(int *type, int *size, int *tick) +{ + unsigned char chunk = 0; + + *size = 0; + *type = 0; + + if(io_read(play_file, &chunk, sizeof(chunk)) != sizeof(chunk)) + return -1; + + if(chunk&CHUNKTYPEFLAG_TICKMARKER) + { + /* decode tick marker */ + int tickdelta = chunk&(CHUNKMASK_TICK); + *type = chunk&(CHUNKTYPEFLAG_TICKMARKER|CHUNKTICKFLAG_KEYFRAME); + + if(tickdelta == 0) + { + unsigned char tickdata[4]; + if(io_read(play_file, tickdata, sizeof(tickdata)) != sizeof(tickdata)) + return -1; + *tick = (tickdata[0]<<24) | (tickdata[1]<<16) | (tickdata[2]<<8) | tickdata[3]; + } + else + { + *tick += tickdelta; + } + + } + else + { + /* decode normal chunk */ + *type = (chunk&CHUNKMASK_TYPE)>>5; + *size = chunk&CHUNKMASK_SIZE; + + if(*size == 30) + { + unsigned char sizedata[1]; + if(io_read(play_file, sizedata, sizeof(sizedata)) != sizeof(sizedata)) + return -1; + *size = sizedata[0]; + + } + else if(*size == 31) + { + unsigned char sizedata[2]; + if(io_read(play_file, sizedata, sizeof(sizedata)) != sizeof(sizedata)) + return -1; + *size = (sizedata[1]<<8) | sizedata[0]; + } + } + + return 0; +} + +static void scan_file() +{ + long start_pos; + HEAP *heap = 0; + KEYFRAME_SEARCH *first_key = 0; + KEYFRAME_SEARCH *current_key = 0; + /*DEMOREC_CHUNK chunk;*/ + int chunk_size, chunk_type, chunk_tick = 0; + int i; + + heap = memheap_create(); + + start_pos = io_tell(play_file); + playbackinfo.seekable_points = 0; + + while(1) + { + long current_pos = io_tell(play_file); + + if(read_chunk_header(&chunk_type, &chunk_size, &chunk_tick)) + break; + + /* read the chunk */ + if(chunk_type&CHUNKTYPEFLAG_TICKMARKER) + { + if(chunk_type&CHUNKTICKFLAG_KEYFRAME) + { + KEYFRAME_SEARCH *key; + + /* save the position */ + key = (KEYFRAME_SEARCH *)memheap_allocate(heap, sizeof(KEYFRAME_SEARCH)); + key->frame.filepos = current_pos; + key->frame.tick = chunk_tick; + key->next = 0; + if(current_key) + current_key->next = key; + if(!first_key) + first_key = key; + current_key = key; + playbackinfo.seekable_points++; + } + + if(playbackinfo.first_tick == -1) + playbackinfo.first_tick = chunk_tick; + playbackinfo.last_tick = chunk_tick; + } + else if(chunk_size) + io_skip(play_file, chunk_size); + + } + + /* copy all the frames to an array instead for fast access */ + keyframes = (KEYFRAME*)mem_alloc(playbackinfo.seekable_points*sizeof(KEYFRAME), 1); + for(current_key = first_key, i = 0; current_key; current_key = current_key->next, i++) + keyframes[i] = current_key->frame; + + /* destroy the temporary heap and seek back to the start */ + memheap_destroy(heap); + io_seek(play_file, start_pos, IOSEEK_START); +} + +static void do_tick() +{ + static char compresseddata[CSnapshot::MAX_SIZE]; + static char decompressed[CSnapshot::MAX_SIZE]; + static char data[CSnapshot::MAX_SIZE]; + int chunk_type, chunk_tick, chunk_size; + int data_size; + int got_snapshot = 0; + + /* update ticks */ + playbackinfo.previous_tick = playbackinfo.current_tick; + playbackinfo.current_tick = playbackinfo.next_tick; + chunk_tick = playbackinfo.current_tick; + + while(1) + { + if(read_chunk_header(&chunk_type, &chunk_size, &chunk_tick)) + { + /* stop on error or eof */ + dbg_msg("demorec", "end of file"); + demorec_playback_pause(); + break; + } + + /* read the chunk */ + if(chunk_size) + { + if(io_read(play_file, compresseddata, chunk_size) != (unsigned)chunk_size) + { + /* stop on error or eof */ + dbg_msg("demorec", "error reading chunk"); + demorec_playback_stop(); + break; + } + + data_size = CNetBase::Decompress(compresseddata, chunk_size, decompressed, sizeof(decompressed)); + if(data_size < 0) + { + /* stop on error or eof */ + dbg_msg("demorec", "error during network decompression"); + demorec_playback_stop(); + break; + } + + data_size = intpack_decompress(decompressed, data_size, data); + + if(data_size < 0) + { + dbg_msg("demorec", "error during intpack decompression"); + demorec_playback_stop(); + break; + } + } + + if(chunk_type == CHUNKTYPE_DELTA) + { + /* process delta snapshot */ + static char newsnap[CSnapshot::MAX_SIZE]; + + got_snapshot = 1; + + data_size = CSnapshot::UnpackDelta((CSnapshot*)playback_lastsnapshotdata, (CSnapshot*)newsnap, data, data_size); + + if(data_size >= 0) + { + if(play_callback_snapshot) + play_callback_snapshot(newsnap, data_size); + + playback_lastsnapshotdata_size = data_size; + mem_copy(playback_lastsnapshotdata, newsnap, data_size); + } + else + dbg_msg("demorec", "error duing unpacking of delta, err=%d", data_size); + } + else if(chunk_type == CHUNKTYPE_SNAPSHOT) + { + /* process full snapshot */ + got_snapshot = 1; + + playback_lastsnapshotdata_size = data_size; + mem_copy(playback_lastsnapshotdata, data, data_size); + if(play_callback_snapshot) + play_callback_snapshot(data, data_size); + } + else + { + /* if there were no snapshots in this tick, replay the last one */ + if(!got_snapshot && play_callback_snapshot && playback_lastsnapshotdata_size != -1) + { + got_snapshot = 1; + play_callback_snapshot(playback_lastsnapshotdata, playback_lastsnapshotdata_size); + } + + /* check the remaining types */ + if(chunk_type&CHUNKTYPEFLAG_TICKMARKER) + { + playbackinfo.next_tick = chunk_tick; + break; + } + else if(chunk_type == CHUNKTYPE_MESSAGE) + { + if(play_callback_message) + play_callback_message(data, data_size); + } + } + } +} + +void demorec_playback_pause() +{ + playbackinfo.paused = 1; +} + +void demorec_playback_unpause() +{ + if(playbackinfo.paused) + { + /*playbackinfo.start_tick = playbackinfo.current_tick; + playbackinfo.start_time = time_get();*/ + playbackinfo.paused = 0; + } +} + +int demorec_playback_load(const char *filename) +{ + play_file = engine_openfile(filename, IOFLAG_READ); + if(!play_file) + { + dbg_msg("demorec/playback", "could not open '%s'", filename); + return -1; + } + + /* clear the playback info */ + mem_zero(&playbackinfo, sizeof(playbackinfo)); + playbackinfo.first_tick = -1; + playbackinfo.last_tick = -1; + /*playbackinfo.start_tick = -1;*/ + playbackinfo.next_tick = -1; + playbackinfo.current_tick = -1; + playbackinfo.previous_tick = -1; + playbackinfo.speed = 1; + + playback_lastsnapshotdata_size = -1; + + /* read the header */ + io_read(play_file, &playbackinfo.header, sizeof(playbackinfo.header)); + if(mem_comp(playbackinfo.header.marker, header_marker, sizeof(header_marker)) != 0) + { + dbg_msg("demorec/playback", "'%s' is not a demo file", filename); + io_close(play_file); + play_file = 0; + return -1; + } + + /* scan the file for interessting points */ + scan_file(); + + /* ready for playback */ + return 0; +} + +int demorec_playback_nextframe() +{ + do_tick(); + return demorec_isplaying(); +} + +int demorec_playback_play() +{ + /* fill in previous and next tick */ + while(playbackinfo.previous_tick == -1 && demorec_isplaying()) + do_tick(); + + /* set start info */ + /*playbackinfo.start_tick = playbackinfo.previous_tick; + playbackinfo.start_time = time_get();*/ + playbackinfo.current_time = playbackinfo.previous_tick*time_freq()/SERVER_TICK_SPEED; + playbackinfo.last_update = time_get(); + return 0; +} + +int demorec_playback_set(float percent) +{ + int keyframe; + int wanted_tick; + if(!play_file) + return -1; + + /* -5 because we have to have a current tick and previous tick when we do the playback */ + wanted_tick = playbackinfo.first_tick + (int)((playbackinfo.last_tick-playbackinfo.first_tick)*percent) - 5; + + keyframe = (int)(playbackinfo.seekable_points*percent); + + if(keyframe < 0 || keyframe >= playbackinfo.seekable_points) + return -1; + + /* get correct key frame */ + if(keyframes[keyframe].tick < wanted_tick) + while(keyframe < playbackinfo.seekable_points-1 && keyframes[keyframe].tick < wanted_tick) + keyframe++; + + while(keyframe && keyframes[keyframe].tick > wanted_tick) + keyframe--; + + /* seek to the correct keyframe */ + io_seek(play_file, keyframes[keyframe].filepos, IOSEEK_START); + + /*playbackinfo.start_tick = -1;*/ + playbackinfo.next_tick = -1; + playbackinfo.current_tick = -1; + playbackinfo.previous_tick = -1; + + /* playback everything until we hit our tick */ + while(playbackinfo.previous_tick < wanted_tick) + do_tick(); + + demorec_playback_play(); + + return 0; +} + +void demorec_playback_setspeed(float speed) +{ + playbackinfo.speed = speed; +} + +int demorec_playback_update() +{ + int64 now = time_get(); + int64 deltatime = now-playbackinfo.last_update; + playbackinfo.last_update = now; + + if(!demorec_isplaying()) + return 0; + + if(playbackinfo.paused) + { + + } + else + { + int64 freq = time_freq(); + playbackinfo.current_time += (int64)(deltatime*(double)playbackinfo.speed); + + while(1) + { + int64 curtick_start = (playbackinfo.current_tick)*freq/SERVER_TICK_SPEED; + + /* break if we are ready */ + if(curtick_start > playbackinfo.current_time) + break; + + /* do one more tick */ + do_tick(); + + if(playbackinfo.paused) + return 0; + } + + /* update intratick */ + { + int64 curtick_start = (playbackinfo.current_tick)*freq/SERVER_TICK_SPEED; + int64 prevtick_start = (playbackinfo.previous_tick)*freq/SERVER_TICK_SPEED; + playbackinfo.intratick = (playbackinfo.current_time - prevtick_start) / (float)(curtick_start-prevtick_start); + playbackinfo.ticktime = (playbackinfo.current_time - prevtick_start) / (float)freq; + } + + if(playbackinfo.current_tick == playbackinfo.previous_tick || + playbackinfo.current_tick == playbackinfo.next_tick) + { + dbg_msg("demorec/playback", "tick error prev=%d cur=%d next=%d", + playbackinfo.previous_tick, playbackinfo.current_tick, playbackinfo.next_tick); + } + } + + return 0; +} + +int demorec_playback_stop() +{ + if(!play_file) + return -1; + + dbg_msg("demorec/playback", "Stopped playback"); + io_close(play_file); + play_file = 0; + mem_free(keyframes); + keyframes = 0; + return 0; +} + + |