WIP empdump, can't shrink tables

This commit is contained in:
Markus Armbruster 2008-02-26 08:18:41 +01:00
parent b9c725224e
commit 4ef3574427
8 changed files with 433 additions and 55 deletions

View file

@ -104,7 +104,7 @@ deps := $(obj:.o=.d)
# Library archives: # Library archives:
libs := $(addprefix lib/, libcommon.a libas.a libgen.a libglobal.a) libs := $(addprefix lib/, libcommon.a libas.a libgen.a libglobal.a)
# Programs: # Programs:
util := $(addprefix src/util/, $(addsuffix $(EXEEXT), empsched fairland files pconfig)) util := $(addprefix src/util/, $(addsuffix $(EXEEXT), empdump empsched fairland files pconfig))
client := src/client/empire$(EXEEXT) client := src/client/empire$(EXEEXT)
server := src/server/emp_server$(EXEEXT) server := src/server/emp_server$(EXEEXT)
# Info subjects: # Info subjects:

View file

@ -174,9 +174,10 @@ precisely specified. We use EBNF (ISO 14977) for syntax, except we
use '-' in meta-identifiers and omit the concatenation symbol ','. use '-' in meta-identifiers and omit the concatenation symbol ','.
table = header { record } footer ; table = header { record } footer ;
header = "XDUMP" space [ "meta" space ] name space timestamp newline ; header = "XDUMP" space [ "meta" space ]
name = name-chr { name-chr } ; identifier space timestamp newline ;
name-chr = ? ASCII characters 33..126 ? ; identifier = id-chr { id-chr } ;
id-char = ? ASCII characters 33..126 except '"#()<>=' ? ;
timestamp = intnum ; timestamp = intnum ;
footer = "/" number newline ; footer = "/" number newline ;
record = [ fields ] newline ; record = [ fields ] newline ;
@ -211,7 +212,7 @@ Notes:
Semantics: Semantics:
* The table name in the header is one of the names in xdump table. * The table identifier in the header is one of the names in xdump table.
* The timestamp increases monotonically. It has a noticeable * The timestamp increases monotonically. It has a noticeable
granularity: game state may change between an xdump and the next granularity: game state may change between an xdump and the next
@ -524,7 +525,7 @@ EBNF changes:
* Header and footer: * Header and footer:
header = "config" name newline colhdr newline ; header = "config" identifier newline colhdr newline ;
colhdr = { identifier [ "(" ( intnum | identifier ) ")" ] } [ "..." ] ; colhdr = { identifier [ "(" ( intnum | identifier ) ")" ] } [ "..." ] ;
footer = "/config" newline ; footer = "/config" newline ;
@ -542,7 +543,7 @@ EBNF changes:
update. update.
- The column header is due to the self-containedness requirement. - The column header is due to the self-containedness requirement.
It contains just the essential bit of meta-data: the column name. It contains just the essential bit of meta-data: the column names.
* Symbolic fields: * Symbolic fields:
@ -662,7 +663,7 @@ and section `Objectives':
enemy ships, nations). enemy ships, nations).
(4) Bandwidth will be minimized (i.e. the format will be as (4) Bandwidth will be minimized (i.e. the format will be as
concise as possible) while remaining human readable (i.e. no concise as possible) while remaining human-readable (i.e. no
binary messages). [Note that data compression may be added at a later binary messages). [Note that data compression may be added at a later
date, but if it is added, it will be added on a separate port to date, but if it is added, it will be added on a separate port to
maintain backwards compatability.] maintain backwards compatability.]
@ -735,14 +736,14 @@ f. The data to sync is not readily available the server.
limited view on certain parts of server game state on certain limited view on certain parts of server game state on certain
events. events.
To be complete, a machine readable protocol must disclose as much To be complete, a machine-readable protocol must disclose as much
information as the human readable output. Tracking server game information as the human-readable output. Tracking server game
state changes cannot do that alone. For instance, lookout tells state changes cannot do that alone. For instance, lookout tells
you ship#, owner and location. That event does not trigger any you ship#, owner and location. That event does not trigger any
state change on the server! state change on the server!
To be correct, a machine readable protocol must disclose no more To be correct, a machine-readable protocol must disclose no more
information than the human readable output. When you observe a information than the human-readable output. When you observe a
server game state change, you can only guess what event triggered server game state change, you can only guess what event triggered
it, and what it disclosed to which player. You're stuck with it, and what it disclosed to which player. You're stuck with
conservative assumptions. That's the death knell for completeness. conservative assumptions. That's the death knell for completeness.
@ -761,7 +762,7 @@ a shortcut, and it didn't work. That doesn't mean there's no way at
all. I believe the only way to get this done right is by tracking all. I believe the only way to get this done right is by tracking
*events*. Whenever something is printed to a player, be it live *events*. Whenever something is printed to a player, be it live
connection or telegram, we need to transmit precisely the same connection or telegram, we need to transmit precisely the same
information in machine readable form. Much more work. information in machine-readable form. Much more work.
xdump shares valuable ideas with C_SYNC, e.g. using selector xdump shares valuable ideas with C_SYNC, e.g. using selector
meta-data. It is, however, much more modest in scope. We're pretty meta-data. It is, however, much more modest in scope. We're pretty

View file

@ -42,11 +42,13 @@
struct xdstr { struct xdstr {
natid cnum; /* dump for this country */ natid cnum; /* dump for this country */
int divine; /* is this a deity dump? */ int divine; /* is this a deity dump? */
int human; /* dump human-readable format */
void (*pr)(char *fmt, ...); /* callback for printing dump */ void (*pr)(char *fmt, ...); /* callback for printing dump */
}; };
struct xdstr *xdinit(struct xdstr *, natid, void (*)(char *, ...)); struct xdstr *xdinit(struct xdstr *, natid, int, void (*)(char *, ...));
extern void xdhdr(struct xdstr *, char *, int); extern void xdhdr(struct xdstr *, char *, int);
extern void xdcolhdr(struct xdstr *, struct castr[]);
extern void xdflds(struct xdstr *, struct castr[], void *); extern void xdflds(struct xdstr *, struct castr[], void *);
extern struct valstr *xdeval(struct valstr *, struct xdstr *, nsc_type, void *, ptrdiff_t, int, int); extern struct valstr *xdeval(struct valstr *, struct xdstr *, nsc_type, void *, ptrdiff_t, int, int);
extern char *xdprval(struct xdstr *, struct valstr *, char *); extern char *xdprval(struct xdstr *, struct valstr *, char *);

57
man/empdump.6 Normal file
View file

@ -0,0 +1,57 @@
.TH EMPDUMP 6
.SH NAME
empdump \- Export/import Empire game state
.SH SYNOPSIS
.B empdump
[
.B \-mtxhv
]
[
.BI \-e " configfile"
]
[
.I dump-file
]
.br
.SH DESCRIPTION
.B empdump
imports and exports game state as plain text.
.SH OPTIONS
.TP
.BI \-e " configfile"
Use game configuration in \fIconfigfile\fR.
.TP
.B \-h
Help. Print brief usage information and exit.
.TP
.TP
.B \-m
Use machine-readable format for export. Import always recognizes both
machine-readable and human-readable format.
.TP
.B \-t
Test import, don't update game state.
.TP
.B \-v
Print version information and exit.
.TP
.B \-x
Export game state to standard output.
.SH OPERANDS
.TP
.I dump-file
The file to import.
.SH "LIMITATIONS"
.B empdump
can't export player bmaps, power report, telegrams, announcements,
message of the day, no-login message and log files. Exported
floating-point values may be inexact. Importing an exported game
state may not result in identical data files; besides the loss of
floating-point precision just mentioned, coordinates are normalized,
and characters beyond a string's terminating zero in a character array
may be lost.
FIXME can't change file size, fix or document here
.SH "SEE ALSO"
\fIemp_server\fR(6).
.SH AUTHOR
Markus Armbruster <armbru@pond.sub.org>

View file

@ -223,7 +223,7 @@ xdump(void)
if (!p || !*p) if (!p || !*p)
return RET_SYN; return RET_SYN;
xdinit(&xd, player->cnum, pr); xdinit(&xd, player->cnum, 0, pr);
natp = getnatp(player->cnum); natp = getnatp(player->cnum);
type = isdigit(p[0]) ? atoi(p) : ef_byname(p); type = isdigit(p[0]) ? atoi(p) : ef_byname(p);
if (type >= 0 && type < EF_MAX) { if (type >= 0 && type < EF_MAX) {

View file

@ -76,20 +76,23 @@
#include <config.h> #include <config.h>
#include <ctype.h> #include <ctype.h>
#include <limits.h>
#include "file.h" #include "file.h"
#include "nat.h" #include "nat.h"
#include "xdump.h" #include "xdump.h"
/* /*
* Initialize XD to dump for country CNUM. * Initialize XD to dump for country CNUM.
* If HUMAN, dump in human-readable format.
* Dump is to be delivered through callback PR. * Dump is to be delivered through callback PR.
* Return XD. * Return XD.
*/ */
struct xdstr * struct xdstr *
xdinit(struct xdstr *xd, natid cnum, void (*pr)(char *fmt, ...)) xdinit(struct xdstr *xd, natid cnum, int human, void (*pr)(char *fmt, ...))
{ {
xd->cnum = cnum; xd->cnum = cnum;
xd->divine = getnatp(cnum)->nat_stat == STAT_GOD; xd->divine = getnatp(cnum)->nat_stat == STAT_GOD;
xd->human = human;
xd->pr = pr; xd->pr = pr;
return xd; return xd;
} }
@ -116,25 +119,17 @@ xdeval(struct valstr *val, struct xdstr *xd,
return val; /* FIXME nstr_exec_val() should return VAL */ return val; /* FIXME nstr_exec_val() should return VAL */
} }
/* Dump VAL prefixed with SEP, return " ". */ /*
char * * Dump string STR to XD with funny characters escaped.
xdprval(struct xdstr *xd, struct valstr *val, char *sep) * Dump at most N characters.
*/
static void
xdpresc(struct xdstr *xd, char *str, size_t n)
{ {
unsigned char *s, *e, *l; unsigned char *s, *e, *l;
switch (val->val_type) { s = (unsigned char *)str;
case NSC_LONG: l = s + n;
xd->pr("%s%ld", sep, val->val_as.lng);
break;
case NSC_DOUBLE:
xd->pr("%s%#g", sep, val->val_as.dbl);
break;
case NSC_STRING:
s = (unsigned char *)val->val_as.str.base;
if (s) {
xd->pr("%s\"", sep);
l = s + val->val_as.str.maxsz;
/* FIXME maxsz == INT_MAX ! */
for (;;) { for (;;) {
for (e = s; for (e = s;
e < l && *e != '"' && *e != '\\' && isgraph(*e); e < l && *e != '"' && *e != '\\' && isgraph(*e);
@ -147,6 +142,27 @@ xdprval(struct xdstr *xd, struct valstr *val, char *sep)
break; break;
s = e; s = e;
} }
}
/* Dump VAL in machine readable format, prefixed with SEP, return " ". */
static char *
xdprval_nosym(struct xdstr *xd, struct valstr *val, char *sep)
{
switch (val->val_type) {
case NSC_LONG:
xd->pr("%s%ld", sep, val->val_as.lng);
break;
case NSC_DOUBLE:
#if 0 /* TODO have %a */
xd->pr("%s%a", sep, val->val_as.dbl);
#else
xd->pr("%s%#g", sep, val->val_as.dbl);
#endif
break;
case NSC_STRING:
if (val->val_as.str.base) {
xd->pr("%s\"", sep);
xdpresc(xd, val->val_as.str.base, val->val_as.str.maxsz);
xd->pr("\""); xd->pr("\"");
} else } else
xd->pr("%snil", sep); xd->pr("%snil", sep);
@ -158,6 +174,66 @@ xdprval(struct xdstr *xd, struct valstr *val, char *sep)
return " "; return " ";
} }
/*
* Dump symbol with value KEY from symbol table TYPE to XD.
* Prefix with SEP, return " ".
*/
static char *
xdprsym(struct xdstr *xd, int key, int type, char *sep)
{
char *sym = symbol_by_value(key, ef_ptr(type, 0));
if (CANT_HAPPEN(!sym))
xd->pr("%s%ld", sep, key);
else {
xd->pr("%s", sep);
xdpresc(xd, sym, INT_MAX);
}
return " ";
}
/*
* Dump VAL prefixed with SEP, return " ".
* CA describes the field from which the value was fetched.
*/
static char *
xdprval_sym(struct xdstr *xd, struct valstr *val, struct castr *ca, char *sep)
{
unsigned long bit;
struct castr *ca_sym;
char *sym;
if (xd->human && val->val_type == NSC_LONG && ca->ca_table != EF_BAD) {
ca_sym = ef_cadef(ca->ca_table);
if (ca_sym != symbol_ca)
;
else if (ca->ca_flags & NSC_BITS) {
xd->pr("%s(", sep);
sep = "";
for (bit = 1; bit; bit <<= 1) {
if (bit & val->val_as.lng)
sep = xdprsym(xd, bit, ca->ca_table, sep);
}
xd->pr(")");
return " ";
} else
return xdprsym(xd, val->val_as.lng, ca->ca_table, sep);
}
return xdprval_nosym(xd, val, sep);
}
/*
* Dump VAL prefixed with SEP, return " ".
* XD must not be human-readable.
*/
char *
xdprval(struct xdstr *xd, struct valstr *val, char *sep)
{
CANT_HAPPEN(xd->human);
return xdprval_nosym(xd, val, sep);
}
/* /*
* Dump field values of a context object to XD. * Dump field values of a context object to XD.
* CA[] describes fields. * CA[] describes fields.
@ -180,25 +256,65 @@ xdflds(struct xdstr *xd, struct castr ca[], void *ptr)
do { do {
xdeval(&val, xd, xdeval(&val, xd,
ca[i].ca_type, ptr, ca[i].ca_off, j, ca[i].ca_len); ca[i].ca_type, ptr, ca[i].ca_off, j, ca[i].ca_len);
sep = xdprval(xd, &val, sep); sep = xdprval_sym(xd, &val, &ca[i], sep);
} while (++j < n); } while (++j < n);
} }
} }
/* /*
* Dump header for dump NAME. * Dump header for dump NAME to XD.
* If META, it's for the meta-data dump rather than the data dump. * If META, it's for the meta-data dump rather than the data dump.
*/ */
void void
xdhdr(struct xdstr *xd, char *name, int meta) xdhdr(struct xdstr *xd, char *name, int meta)
{ {
xd->pr("XDUMP %s%s %ld\n", meta ? "meta " : "", name, (long)time(NULL)); if (xd->human) {
xd->pr("config %s\n", name);
} else
xd->pr("XDUMP %s%s %ld\n",
meta ? "meta " : "", name, (long)time(NULL));
} }
/* Dump footer for a dump that dumped N objects. */ /*
* Dump column header to XD.
* CA[] describes fields.
* Does nothing unless XD is human-readable.
*/
void
xdcolhdr(struct xdstr *xd, struct castr ca[])
{
int i, j, n;
char *sep = "";
if (!xd->human)
return;
for (i = 0; ca[i].ca_name; ++i) {
if (ca[i].ca_flags & NSC_DEITY && !xd->divine)
continue;
if (ca[i].ca_flags & NSC_EXTRA)
continue;
n = ca[i].ca_type != NSC_STRINGY ? ca[i].ca_len : 0;
if (n) {
for (j = 0; j < n; j++) {
xd->pr("%s%s(%d)",sep, ca[i].ca_name, j);
sep = " ";
}
} else {
xd->pr("%s%s", sep, ca[i].ca_name);
sep = " ";
}
}
xd->pr("\n");
}
/* Dump footer for a dump that dumped N objects to XD. */
void void
xdftr(struct xdstr *xd, int n) xdftr(struct xdstr *xd, int n)
{ {
if (xd->human)
xd->pr("/config\n");
else
xd->pr("/%d\n", n); xd->pr("/%d\n", n);
} }
@ -217,6 +333,7 @@ xdmeta(struct xdstr *xd, int type)
return RET_SYN; return RET_SYN;
xdhdr(xd, ef_nameof(type), 1); xdhdr(xd, ef_nameof(type), 1);
xdcolhdr(xd, ca);
for (i = 0; ca[i].ca_name; i++) { for (i = 0; ca[i].ca_name; i++) {
if (ca[i].ca_flags & NSC_DEITY && !xd->divine) if (ca[i].ca_flags & NSC_DEITY && !xd->divine)

View file

@ -132,21 +132,6 @@ skipfs(FILE *fp)
return ch; return ch;
} }
/*
* Read an identifier from FP into BUF.
* BUF must have space for 1024 characters.
* Return number of characters read on success, -1 on failure.
*/
static int
getid(FILE *fp, char *buf)
{
int n;
if (fscanf(fp, "%1023[^#()<>=#\" \t\n]%n", buf, &n) != 1
|| !isalpha(buf[0]))
return -1;
return n;
}
/* /*
* Decode escape sequences in BUF. * Decode escape sequences in BUF.
* Return BUF on success, null pointer on failure. * Return BUF on success, null pointer on failure.
@ -172,6 +157,22 @@ xuesc(char *buf)
return buf; return buf;
} }
/*
* Read an identifier from FP into BUF.
* BUF must have space for 1024 characters.
* Return number of characters read on success, -1 on failure.
*/
static int
getid(FILE *fp, char *buf)
{
int n;
if (fscanf(fp, "%1023[^\"#()<>= \t\n]%n", buf, &n) != 1
|| !isalpha(buf[0]))
return -1;
xuesc(buf);
return n;
}
/* /*
* Try to read a field name from FP. * Try to read a field name from FP.
* I is the field number, counting from zero. * I is the field number, counting from zero.

200
src/util/empdump.c Normal file
View file

@ -0,0 +1,200 @@
/*
* Empire - A multi-player, client/server Internet based war game.
* Copyright (C) 1986-2008, Dave Pare, Jeff Bailey, Thomas Ruschak,
* Ken Stevens, Steve McClure
*
* 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 the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ---
*
* See files README, COPYING and CREDITS in the root of the source
* tree for related information and legal notices. It is expected
* that future projects/authors will amend these files as needed.
*
* ---
*
* empdump.c: Export/import Empire game state
*
* Known contributors to this file:
* Markus Armbruster, 2008
*/
#include <config.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include "file.h"
#include "nat.h"
#include "optlist.h"
#include "prototypes.h"
#include "version.h"
#include "xdump.h"
static void dump_table(int, int);
int
main(int argc, char *argv[])
{
char *config_file = NULL;
char *import = NULL;
int export = 0;
int private = 0;
int human = 1;
int opt, i, lineno, type;
FILE *impf;
int dirty[EF_MAX];
while ((opt = getopt(argc, argv, "e:mtxhv")) != EOF) {
switch (opt) {
case 'e':
config_file = optarg;
break;
case 'h':
printf("Usage: %s [OPTION]... [DUMP-FILE]\n"
" -e CONFIG-FILE configuration file\n"
" (default %s)\n"
" -m use machine-readable format\n"
" -t test import, don't update game state\n"
" -x export to standard output\n"
" -h display this help and exit\n"
" -v display version information and exit\n",
argv[0], dflt_econfig);
exit(0);
case 'm':
human = 0;
break;
case 't':
private = EFF_PRIVATE;
break;
case 'x':
export = 1;
break;
case 'v':
printf("%s\n\n%s", version, legal);
exit(0);
default:
fprintf(stderr, "Try -h for help.\n");
exit(1);
}
}
if (argv[optind])
import = argv[optind++];
if (import) {
impf = fopen(import, "r");
if (!impf) {
fprintf(stderr, "Cant open %s for reading (%s)\n",
import, strerror(errno));
exit(1);
}
} else
private = EFF_PRIVATE;
/* read configuration & initialize */
empfile_init();
if (emp_config(config_file) < 0)
exit(1);
empfile_fixup();
nsc_init();
if (read_builtin_tables() < 0)
exit(1);
if (read_custom_tables() < 0)
exit(1);
if (chdir(gamedir)) {
fprintf(stderr, "Can't chdir to %s (%s)\n",
gamedir, strerror(errno));
exit(1);
}
global_init();
for (i = 0; i < EF_MAX; i++) {
if (!EF_IS_GAME_STATE(i))
continue;
if (!ef_open(i, EFF_MEM | private))
exit(1);
}
/* import from IMPORT */
memset(dirty, 0, sizeof(dirty));
if (import) {
lineno = 1;
while ((type = xundump(impf, import, &lineno, EF_BAD)) >= 0)
dirty[type] = 1;
if (type == EF_BAD)
exit(1);
}
if (ef_verify() < 0)
exit(1);
/* export to stdout */
if (export) {
for (i = 0; i < EF_MAX; i++) {
if (!EF_IS_GAME_STATE(i))
continue;
dump_table(i, human);
}
}
/* write out imported data */
for (i = 0; i < EF_MAX; i++) {
if (!EF_IS_GAME_STATE(i))
continue;
if (!private && dirty[i]) {
if (!ef_close(i))
exit(1);
}
}
return 0;
}
static void
printf_wrapper(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
}
static void
dump_table(int type, int human)
{
struct xdstr xd;
struct castr *ca;
int i;
void *p;
ca = ef_cadef(type);
if (!ca)
return;
xdinit(&xd, 0, human, printf_wrapper);
xdhdr(&xd, ef_nameof(type), 0);
xdcolhdr(&xd, ca);
for (i = 0; (p = ef_ptr(type, i)); i++) {
xdflds(&xd, ca, p);
printf("\n");
}
xdftr(&xd, i);
}