quakeforge/qw/source/sv_recorder.c

663 lines
14 KiB
C

/*
sv_recorder.c
Interface for recording server state (server side demos and qtv)
Copyright (C) 2005 #AUTHOR#
Author: Bill Currie
Date: 2005/5/1
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
$Id$
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
static __attribute__ ((used)) const char rcsid[] =
"$Id$";
#ifdef HAVE_STRING_H
# include "string.h"
#endif
#ifdef HAVE_STRINGS_H
# include "strings.h"
#endif
#ifdef HAVE_UNISTD_H
# include "unistd.h"
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <time.h>
#include "QF/sizebuf.h"
#include "QF/sys.h"
#include "qw/bothdefs.h"
#include "server.h"
#include "sv_demo.h"
#include "sv_progs.h"
#include "sv_recorder.h"
typedef struct dbuffer_s {
byte *data;
int start, end, last;
int maxsize;
} dbuffer_t;
typedef struct header_s {
byte type;
byte full;
int to;
int size;
byte data[1];
} header_t;
typedef struct demobuf_s {
sizebuf_t sz;
int bufsize;
header_t *h;
} demobuf_t;
typedef struct demo_frame_s {
double time;
demobuf_t buf;
} demo_frame_t;
#define DEMO_FRAMES 64
#define DEMO_FRAMES_MASK (DEMO_FRAMES - 1)
typedef struct rec_s {
demobuf_t *dbuf;
dbuffer_t dbuffer;
sizebuf_t datagram;
int lastto;
int lasttype;
double time, pingtime;
int stats[MAX_CLIENTS][MAX_CL_STATS]; // ouch!
demo_frame_t frames[DEMO_FRAMES];
int forceFrame;
int parsecount;
int lastwritten;
} rec_t;
struct recorder_s {
recorder_t *next;
void (*write)(void *, sizebuf_t *, int);
int (*frame)(void *);
void (*end_frame)(recorder_t *, void *);
void (*finish)(void *, sizebuf_t *);
void *user;
int paused;
delta_t delta;
entity_state_t entities[UPDATE_BACKUP][MAX_DEMO_PACKET_ENTITIES];
plent_state_t players[UPDATE_BACKUP][MAX_CLIENTS];
};
static rec_t rec;
static recorder_t *free_recorders;
static recorder_t recorders_list[3];
static byte buffer[20 * MAX_MSGLEN];
static byte datagram_data[MAX_DATAGRAM];
static byte msg_buffer[2][MAX_DATAGRAM];
#define MIN_DEMO_MEMORY 0x100000
#define USECACHE (sv_demoUseCache->int_val && svs.demomemsize)
#define MAXSIZE (rec.dbuffer.end < rec.dbuffer.last ? \
rec.dbuffer.start - rec.dbuffer.end : \
rec.dbuffer.maxsize - rec.dbuffer.end)
#define HEADER ((int) (intptr_t) &((header_t *) 0)->data)
static void
dbuffer_init (dbuffer_t *dbuffer, byte *buf, size_t size)
{
dbuffer->data = buf;
dbuffer->maxsize = size;
dbuffer->start = 0;
dbuffer->end = 0;
dbuffer->last = 0;
}
static void
set_msgbuf (demobuf_t *prev, demobuf_t *cur)
{
// fix the maxsize of previous msg buffer,
// we won't be able to write there anymore
if (prev != NULL)
prev->sz.maxsize = prev->bufsize;
rec.dbuf = cur;
memset (rec.dbuf, 0, sizeof (*rec.dbuf));
rec.dbuf->sz.data = rec.dbuffer.data + rec.dbuffer.end;
rec.dbuf->sz.maxsize = MAXSIZE;
}
static void
write_msg (sizebuf_t *msg, int type, int to, float time, sizebuf_t *dst)
{
int msec;
static double prevtime;
msec = (time - prevtime) * 1000;
prevtime += msec * 0.001;
while (msec > 250) {
//the maximum interval we can send is 255ms, but that's nasty with,
//floats, so keep things to a nice round number of 250ms (0.25 is float
//friendly)
MSG_WriteByte (dst, 250);
MSG_WriteByte (dst, dem_read);
MSG_WriteLong (dst, -1);
MSG_WriteByte (dst, A2C_PRINT);
MSG_WriteString (dst, "");
msec -= 250;
}
if (msec < 2)
msec = 0;
MSG_WriteByte (dst, msec);
if (rec.lasttype != type || rec.lastto != to) {
rec.lasttype = type;
rec.lastto = to;
switch (rec.lasttype) {
case dem_all:
MSG_WriteByte (dst, dem_all);
break;
case dem_multiple:
MSG_WriteByte (dst, dem_multiple);
MSG_WriteLong (dst, rec.lastto);
break;
case dem_single:
case dem_stats:
MSG_WriteByte (dst, rec.lasttype | (rec.lastto << 3));
break;
default:
while (sv.recorders)
SVR_RemoveUser (sv.recorders);
Sys_Printf ("bad demo message type:%d", type);
return;
}
} else {
MSG_WriteByte (dst, dem_read);
}
MSG_WriteLong (dst, msg->cursize);
SZ_Write (dst, msg->data, msg->cursize);
}
static void
write_to_msg (int type, int to, float time, sizebuf_t *dst)
{
int pos = 0, oldm, oldd;
header_t *p;
int size;
sizebuf_t msg;
p = (header_t *) rec.dbuf->sz.data;
rec.dbuf->h = NULL;
oldm = rec.dbuf->bufsize;
oldd = rec.dbuffer.start;
while (pos < rec.dbuf->bufsize) {
size = p->size;
pos += HEADER + size;
// no type means we are writing everything to disk
if (!type || (p->type == type && p->to == to)) {
if (size) {
msg.data = p->data;
msg.cursize = size;
write_msg (&msg, p->type, p->to, time, dst);
}
// data is written so it needs to be cleard from demobuf
if (rec.dbuf->sz.data != (byte *) p)
memmove (rec.dbuf->sz.data + size + HEADER,
rec.dbuf->sz.data, (byte *) p - rec.dbuf->sz.data);
rec.dbuf->bufsize -= size + HEADER;
rec.dbuf->sz.data += size + HEADER;
pos -= size + HEADER;
rec.dbuf->sz.maxsize -= size + HEADER;
rec.dbuffer.start += size + HEADER;
}
// move along
p = (header_t *) (p->data + size);
}
if (rec.dbuffer.start == rec.dbuffer.last) {
if (rec.dbuffer.start == rec.dbuffer.end) {
rec.dbuffer.end = 0; // rec.dbuffer is empty
rec.dbuf->sz.data = rec.dbuffer.data;
}
// go back to begining of the buffer
rec.dbuffer.last = rec.dbuffer.end;
rec.dbuffer.start = 0;
}
}
/*
set_buf
Sets position in the buf for writing to specific client
*/
static void
set_buf (byte type, int to)
{
header_t *p;
int pos = 0;
p = (header_t *) rec.dbuf->sz.data;
while (pos < rec.dbuf->bufsize) {
pos += HEADER + p->size;
if (type == p->type && to == p->to && !p->full) {
rec.dbuf->sz.cursize = pos;
rec.dbuf->h = p;
return;
}
p = (header_t *) (p->data + p->size);
}
// type && to not exist in the buf, so add it
p->type = type;
p->to = to;
p->size = 0;
p->full = 0;
rec.dbuf->bufsize += HEADER;
rec.dbuf->sz.cursize = rec.dbuf->bufsize;
rec.dbuffer.end += HEADER;
rec.dbuf->h = p;
}
static void
move_buf (void)
{
// set the last message mark to the previous frame (i/e begining of this
// one)
rec.dbuffer.last = rec.dbuffer.end - rec.dbuf->bufsize;
// move buffer to the begining of demo buffer
memmove (rec.dbuffer.data, rec.dbuf->sz.data, rec.dbuf->bufsize);
rec.dbuf->sz.data = rec.dbuffer.data;
rec.dbuffer.end = rec.dbuf->bufsize;
rec.dbuf->h = NULL; // it will be setup again
rec.dbuf->sz.maxsize = MAXSIZE + rec.dbuf->bufsize;
}
static void
clear_rec (void)
{
memset (&rec, 0, sizeof (rec));
dbuffer_init (&rec.dbuffer, buffer, sizeof (buffer));
set_msgbuf (NULL, &rec.frames[0].buf);
rec.datagram.allowoverflow = true;
rec.datagram.maxsize = sizeof (datagram_data);
rec.datagram.data = datagram_data;
}
void
SVR_Init (void)
{
recorder_t *r;
unsigned i;
for (i = 0, r = recorders_list;
i < (sizeof (recorders_list) / sizeof (recorders_list[0])) - 1;
i++, r++)
r->next = r + 1;
r->next = 0;
free_recorders = recorders_list;
}
recorder_t *
SVR_AddUser (void (*write)(void *, sizebuf_t *, int), int (*frame)(void *),
void (*end_frame)(recorder_t *, void *),
void (*finish)(void *, sizebuf_t *), int demo, void *user)
{
recorder_t *r;
int i;
if (!free_recorders)
return 0;
if (!sv.recorders) {
clear_rec ();
rec.pingtime = sv.time;
}
r = free_recorders;
free_recorders = r->next;
memset (r, 0, sizeof (*r));
r->next = sv.recorders;
sv.recorders = r;
r->delta.type = dt_tp_qtv;
r->delta.pvs = dt_pvs_none;
if (demo) {
r->delta.type = dt_tp_demo;
r->delta.pvs = dt_pvs_fat;
}
for (i = 0; i < UPDATE_BACKUP; i++) {
r->delta.frames[i].entities.entities = r->entities[i];
r->delta.frames[i].players.players = r->players[i];
}
r->delta.delta_sequence = -1;
r->write = write;
r->frame = frame;
r->end_frame = end_frame;
r->finish = finish;
r->user = user;
return r;
}
void
SVR_RemoveUser (recorder_t *r)
{
recorder_t **_r;
sizebuf_t msg;
memset (&msg, 0, sizeof (msg));
msg.data = msg_buffer[0];
msg.maxsize = sizeof (msg_buffer[0]);
// rec.dbuf->sz.cursize = 0;
// rec.dbuf->h = 0;
// rec.dbuf->bufsize = 0;
MSG_WriteByte (&msg, 0);
MSG_WriteByte (&msg, dem_all);
MSG_WriteLong (&msg, 0);
r->finish (r->user, &msg);
if (msg.cursize > 6) {
msg.data[2] = ((msg.cursize - 6) >> 0) & 0xff;
msg.data[3] = ((msg.cursize - 6) >> 8) & 0xff;
msg.data[4] = ((msg.cursize - 6) >> 16) & 0xff;
msg.data[5] = ((msg.cursize - 6) >> 24) & 0xff;
r->write (r->user, &msg, 1);
}
for (_r = &sv.recorders; *_r; _r = &(*_r)->next) {
if (*_r == r) {
*_r = (*_r)->next;
r->next = free_recorders;
free_recorders = r;
break;
}
}
}
static void
write_datagram (recorder_t *r)
{
sizebuf_t msg, dst;
memset (&msg, 0, sizeof (msg));
msg.data = msg_buffer[0];
msg.maxsize = sizeof (msg_buffer[0]);
msg.allowoverflow = true;
memset (&dst, 0, sizeof (dst));
dst.data = msg_buffer[1];
dst.maxsize = sizeof (msg_buffer[1]);
dst.allowoverflow = true;
SV_WriteEntitiesToClient (&r->delta, &msg);
// copy the accumulated multicast datagram
// for this client out to the message
if (rec.datagram.cursize)
SZ_Write (&msg, rec.datagram.data, rec.datagram.cursize);
if (msg.cursize) {
double time = rec.frames[rec.lastwritten & DEMO_FRAMES_MASK].time;
write_msg (&msg, dem_all, 0, time, &dst);
r->write (r->user, &dst, 0);
}
}
static void
write_packet (void)
{
demo_frame_t *frame;
double time;
sizebuf_t msg;
recorder_t *r;
memset (&msg, 0, sizeof (msg));
msg.data = msg_buffer[0];
msg.maxsize = sizeof (msg_buffer[0]);
msg.allowoverflow = true;
frame = &rec.frames[rec.lastwritten & DEMO_FRAMES_MASK];
time = frame->time;
rec.dbuf = &frame->buf;
write_to_msg (0, 0, time, &msg);
for (r = sv.recorders; r; r = r->next) {
if (r->paused)
continue;
r->write (r->user, &msg, 1);
}
rec.dbuf = &rec.frames[rec.parsecount & DEMO_FRAMES_MASK].buf;
rec.dbuf->sz.maxsize = MAXSIZE + rec.dbuf->bufsize;
}
sizebuf_t *
SVR_WriteBegin (byte type, int to, int size)
{
byte *p;
qboolean move = false;
// will it fit?
while (rec.dbuf->bufsize + size + HEADER > rec.dbuf->sz.maxsize) {
// if we reached the end of buffer move msgbuf to the begining
if (!move && rec.dbuffer.end > rec.dbuffer.start)
move = true;
write_packet ();
if (move && rec.dbuffer.start > rec.dbuf->bufsize + HEADER + size)
move_buf ();
}
if (rec.dbuf->h == NULL || rec.dbuf->h->type != type
|| rec.dbuf->h->to != to || rec.dbuf->h->full) {
set_buf (type, to);
}
if (rec.dbuf->h->size + size > MAX_MSGLEN) {
rec.dbuf->h->full = 1;
set_buf (type, to);
}
// we have to make room for new data
if (rec.dbuf->sz.cursize != rec.dbuf->bufsize) {
p = rec.dbuf->sz.data + rec.dbuf->sz.cursize;
memmove (p + size, p, rec.dbuf->bufsize - rec.dbuf->sz.cursize);
}
rec.dbuf->bufsize += size;
rec.dbuf->h->size += size;
if ((rec.dbuffer.end += size) > rec.dbuffer.last)
rec.dbuffer.last = rec.dbuffer.end;
return &rec.dbuf->sz;
}
sizebuf_t *
SVR_Datagram (void)
{
return &rec.datagram;
}
void
SVR_ForceFrame (void)
{
rec.forceFrame = 1;
}
static int
SVR_Frame (void)
{
recorder_t *r;
if (rec.forceFrame) {
rec.forceFrame = 0;
return 1;
}
for (r = sv.recorders; r; r = r->next)
if (r->frame (r->user))
return 1;
return 0;
}
static void
demo_pings (void)
{
client_t *client;
int j;
sizebuf_t *dbuf;
for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) {
if (client->state != cs_spawned && client->state != cs_server)
continue;
dbuf = SVR_WriteBegin (dem_all, 0, 7);
MSG_WriteByte (dbuf, svc_updateping);
MSG_WriteByte (dbuf, j);
MSG_WriteShort (dbuf, SV_CalcPing (client));
MSG_WriteByte (dbuf, svc_updatepl);
MSG_WriteByte (dbuf, j);
MSG_WriteByte (dbuf, client->lossage);
}
}
void
SVR_SendMessages (void)
{
int i, j;
client_t *c;
sizebuf_t *dbuf;
int stats[MAX_CL_STATS];
recorder_t *r;
if (sv_demoPings->value && sv.time - rec.pingtime > sv_demoPings->value) {
demo_pings ();
rec.pingtime = sv.time;
}
if (!SVR_Frame ())
return;
for (i = 0, c = svs.clients; i < MAX_CLIENTS; i++, c++) {
if (c->state != cs_spawned && c->state != cs_server)
continue;
if (c->spectator)
continue;
SV_GetStats (c->edict, 0, stats);
for (j = 0 ; j < MAX_CL_STATS ; j++)
if (stats[j] != rec.stats[i][j]) {
rec.stats[i][j] = stats[j];
if (stats[j] >=0 && stats[j] <= 255) {
dbuf = SVR_WriteBegin (dem_stats, i, 3);
MSG_WriteByte (dbuf, svc_updatestat);
MSG_WriteByte (dbuf, j);
MSG_WriteByte (dbuf, stats[j]);
} else {
dbuf = SVR_WriteBegin (dem_stats, i, 6);
MSG_WriteByte (dbuf, svc_updatestatlong);
MSG_WriteByte (dbuf, j);
MSG_WriteLong (dbuf, stats[j]);
}
}
}
rec.frames[rec.parsecount & DEMO_FRAMES_MASK].time = rec.time = sv.time;
write_packet ();
// send over all the objects that are in the PVS
// this will include clients, a packetentities, and
// possibly a nails update
for (r = sv.recorders; r; r = r->next) {
if (r->paused)
continue;
write_datagram (r);
if (r->end_frame)
r->end_frame (r, r->user);
}
SZ_Clear (&rec.datagram);
rec.parsecount++;
set_msgbuf (rec.dbuf, &rec.frames[rec.parsecount & DEMO_FRAMES_MASK].buf);
rec.lastwritten++;
}
void
SVR_Pause (recorder_t *r)
{
r->paused = 1;
}
void
SVR_Continue (recorder_t *r)
{
r->paused = 0;
}
void
SVR_SetDelta (recorder_t *r, int delta, int in_frame)
{
r->delta.delta_sequence = -1;
if (delta != -1)
r->delta.delta_sequence = delta;
r->delta.in_frame = (r->delta.delta_sequence + 1) & UPDATE_MASK;
if (in_frame != -1)
r->delta.in_frame = in_frame & UPDATE_MASK;
}
int
SVR_NumRecorders (void)
{
recorder_t *rec;
int count;
for (count = 0, rec = sv.recorders; rec; count++, rec = rec->next)
;
return count;
}