From d12cb198d00d6e57efd0477b24e8f849518c1d12 Mon Sep 17 00:00:00 2001 From: driAn Date: Tue, 7 Aug 2007 18:08:47 +0000 Subject: [PATCH] stef's demo code git-svn-id: svn://svn.code.sf.net/p/actiongame/code/trunk/ac@1211 e016b1c5-ed68-44aa-a2f7-31cac88cffde --- source/src/protocol.cpp | 21 ++ source/src/protocol.h | 2 +- source/src/savegamedemo.cpp | 368 +++++++++++++++++++++++++++--------- source/src/tools.h | 22 +++ 4 files changed, 319 insertions(+), 94 deletions(-) diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index a77185d7..35e6819c 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -74,6 +74,27 @@ void getstring(char *text, ucharbuf &p, int len) while(*t++); } +// machine independent float (single precision, 24 bit mantissa, 7 bit exponent, sign) + +void putfloat(ucharbuf &p, float n) +{ + int e; + double d = frexp((double) n, &e); + e += 64; + if (e < 0 || e > 127) e &= 0x7f; // hack: this float is not properly initialized... + int m = ((int) (d * (1<<24))) & ((1<<24)-1); + p.put(m); p.put(m>>8); p.put(m>>16); p.put(e | (d<0?0x80:0)); +} + +float getfloat(ucharbuf &p) +{ + int m = p.get(); m |= p.get()<<8; m |= p.get()<<16; + int e = p.get(); + m |= e & 0x80 ? (-1 ^ ((1<<24)-1)) : 0; + return (float) ldexp((double) m / (1<<24), (e & 0x7f) - 64); +} + + void filtertext(char *dst, const char *src, bool whitespace, int len) { for(int c = *src; c; c = *++src) diff --git a/source/src/protocol.h b/source/src/protocol.h index 8c1055eb..8b5e41f6 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -4,7 +4,7 @@ #define CUBE_SERVER_PORT 28763 #define CUBE_SERVINFO_PORT 28764 #define PROTOCOL_VERSION 1125 // bump when protocol changes -#define SAVEGAMEVERSION 1008 // bump if dynent/netprotocol changes or any other savegame/demo data bumped from 5 +#define SAVEGAMEVERSION 1009 // bump if dynent/netprotocol changes or any other savegame/demo data bumped from 5 // network messages codes, c2s, c2c, s2c enum diff --git a/source/src/savegamedemo.cpp b/source/src/savegamedemo.cpp index 25447af8..2e0116b6 100644 --- a/source/src/savegamedemo.cpp +++ b/source/src/savegamedemo.cpp @@ -2,30 +2,189 @@ #include "cube.h" +enum { // demo file blocktypes (uchar) + SD_NULL = 0, SD_INIT1, SD_INIT2, SD_INIT3, SD_NETDATA = 3, SD_EXTRAS = 100, SD_PLAYER1, SD_EOF = 255 + }; + gzFile f = NULL; bool demorecording = false; bool demoplayback = false; bool demoloading = false; vector playerhistory; int democlientnum = 0; +int starttime = 0; + +static uchar *iobuf = (uchar *)calloc(1, 65536); // 64k should be enough for anybody +static ucharbuf dp(iobuf, 65535); void startdemo(); +void stopreset(); void gzcheck(int a, int b) { if(a!=b) fatal("savegame file corrupt (short)"); } void gzput(int i) { if(gzputc(f, i)==-1) fatal("gzputc"); } -void gzputi(int i) { gzcheck(gzwrite(f, &i, sizeof(int)), sizeof(int)); } -void gzputv(vec &v) { gzcheck(gzwrite(f, &v, sizeof(vec)), sizeof(vec)); } +void gzputw(int i) { if(gzputc(f, i & 0xff)==-1 || gzputc(f, (i>>8) & 0xff)==-1 || i >= 1<<16 || i < 0) fatal("gzputw"); } -int gzget() { char c = gzgetc(f); return c; } -int gzgeti() { int i; gzcheck(gzread(f, &i, sizeof(int)), sizeof(int)); return i; } -void gzgetv(vec &v) { gzcheck(gzread(f, &v, sizeof(vec)), sizeof(vec)); } +int gzget() { uchar c = gzgetc(f); return c; } +int gzgetw() { int w = gzgetc(f) | gzgetc(f)<<8; return w; } + +void putvec(ucharbuf &p, vec &d) {loopi(3) putfloat(p, d.v[i]);} +void getvec(vec &d, ucharbuf &p) {loopi(3) d.v[i] = getfloat(p);} + +int gzwritedp(int id) +{ + int len = dp.length(); + gzput(id); gzputw(len); + gzcheck(gzwrite(f, iobuf, len), len); + dp.forcelen(0); + return len; +} + +int gzreaddp(int id, bool seekid = false) +{ + int len, fid; + do { + fid = gzget(); + len = gzgetw(); + gzcheck(gzread(f, iobuf, len), len); + if (fid == SD_EOF) break; + } while (seekid && fid != id && !gzeof(f)); + dp.forcelen(0); dp.forcemaxlen(len); + if(fid != id && fid != SD_EOF) + conoutf("demo playback aborted: block id error found %d, expected %d", fid, id); + if(gzeof(f) || fid == SD_EOF || fid != id) + { + stopreset(); + return -1; + } + return len; +} + +void resetdp() { dp.forcelen(0); dp.forcemaxlen(65535); } + +void putplayerent(ucharbuf &p, playerent *d) +{ + // physent + putvec(p, d->o); putvec(p, d->vel); // origin, velocity + putfloat(p, d->yaw); putfloat(p, d->pitch); putfloat(p, d->roll); // used as vec in one place + putfloat(p, d->maxspeed); // cubes per second, 24 for player + putint(p, d->timeinair); // used for fake gravity + putfloat(p, d->radius); putfloat(p, d->eyeheight); putfloat(p, d->aboveeye); // bounding box size + p.put(d->inwater<<0 | d->onfloor<<1 | d->onladder<<2 | d->jumpnext<<3); + putint(p, d->move); putint(p, d->strafe); + putint(p, d->state); putint(p, d->type); + + // dynent + p.put(d->k_left<<0 | d->k_right<<1 | d->k_up<<2 | d->k_down<<3); // see input code + loopi(2) + { + putint(p, d->prev[i].anim); putint(p, d->prev[i].frame); + putint(p, d->prev[i].range); putint(p, d->prev[i].basetime); + putfloat(p, d->prev[i].speed); + putint(p, d->current[i].anim); putint(p, d->current[i].frame); + putint(p, d->current[i].range); putint(p, d->current[i].basetime); + putfloat(p, d->current[i].speed); + putint(p, d->lastanimswitchtime[i]); + } + // void *lastmodel[2]; seems to be unused... + + // playerent + putint(p, d->clientnum); putint(p, d->lastupdate); putint(p, d->plag); putint(p, d->ping); + putint(p, d->lifesequence); // sequence id for each respawn, used in damage test + putint(p, d->frags); putint(p, d->flagscore); + putint(p, d->health); putint(p, d->armour); + putint(p, d->gunselect); putint(p, d->gunwait); + putint(p, d->lastaction); putint(p, d->lastattackgun); putint(p, d->lastmove); + putint(p, d->lastpain); putint(p, d->lastteamkill); + putint(p, d->clientrole); + loopi(NUMGUNS) { putint(p, d->ammo[i]); putint(p, d->mag[i]); } + sendstring(d->name, p); sendstring(d->team, p); + putint(p, d->shots); //keeps track of shots from auto weapons + //p.put(d->attacking<<0 | d->reloading<<1 | d->hasarmour<<2 | d->weaponchanging<<3); + p.put(d->attacking<<0 | d->reloading<<1 | d->weaponchanging<<3); + putint(p, d->nextweapon); // weapon we switch to + putint(p, d->primary); //primary gun + putint(p, d->nextprimary); // primary after respawning + putint(p, d->skin); putint(p, d->nextskin); // skin after respawning + putint(p, d->thrownademillis); + // struct bounceent *inhandnade; stored pointer was evil, anyway... + putint(p, d->akimbo); + loopi(2) putint(p, d->akimbolastaction[i]); + putint(p, d->akimbomillis); + + // poshist Previous stored locations of this player + putint(p, d->history.nextupdate); putint(p, d->history.curpos); putint(p, d->history.numpos); + loopi(POSHIST_SIZE) putvec(p, d->history.pos[i]); +} + +void getplayerent(playerent *d, ucharbuf &p) +{ + // physent + getvec(d->o, p); getvec(d->vel, p); // origin, velocity + d->yaw = getfloat(p); d->pitch = getfloat(p); d->roll = getfloat(p); // used as vec in one place + d->maxspeed = getfloat(p); // cubes per second, 24 for player + d->timeinair = getint(p); // used for fake gravity + d->radius = getfloat(p); d->eyeheight = getfloat(p); d->aboveeye = getfloat(p); // bounding box size + int e = p.get(); + d->inwater = e & 1; d->onfloor = e>>1 & 1; d->onladder = e>>2 & 1; d->jumpnext = e>>3 & 1; + d->move = getint(p); d->strafe = getint(p); + d->state = getint(p); d->type = getint(p); + + // dynent + e = p.get(); + d->k_left = e & 1; d->k_right = e>>1 & 1; d->k_up = e>>2 & 1; d->k_down = e>>3 & 1; // see input code + loopi(2) + { + d->prev[i].anim = getint(p); d->prev[i].frame = getint(p); + d->prev[i].range = getint(p); d->prev[i].basetime = getint(p); + d->prev[i].speed = getfloat(p); + d->current[i].anim = getint(p); d->current[i].frame = getint(p); + d->current[i].range = getint(p); d->current[i].basetime = getint(p); + d->current[i].speed = getfloat(p); + d->lastanimswitchtime[i] = getint(p); + } + loopi(2) d->lastmodel[i] = NULL; // seems to be unused... + + // playerent + d->clientnum = getint(p); d->lastupdate = getint(p); d->plag = getint(p); d->ping = getint(p); + d->lifesequence = getint(p); // sequence id for each respawn, used in damage test + d->frags = getint(p); d->flagscore = getint(p); + d->health = getint(p); d->armour = getint(p); + d->gunselect = getint(p); d->gunwait = getint(p); + d->lastaction = getint(p); d->lastattackgun = getint(p); d->lastmove = getint(p); + d->lastpain = getint(p); d->lastteamkill = getint(p); + d->clientrole = getint(p); + loopi(NUMGUNS) { d->ammo[i] = getint(p); d->mag[i] = getint(p); } + getstring(d->name, p); getstring(d->team, p); + d->shots = getint(p); //keeps track of shots from auto weapons + e = p.get(); + //d->attacking = e & 1; d->reloading = e>>1 & 1; d->hasarmour = e>>2 & 1; d->weaponchanging = e>>3 & 1; + //fixme + d->nextweapon = getint(p); // weapon we switch to + d->primary = getint(p); //primary gun + d->nextprimary = getint(p); // primary after respawning + d->skin = getint(p); d->nextskin = getint(p); // skin after respawning + d->thrownademillis = getint(p); + d->inhandnade = NULL; // stored pointer was evil, anyway... + d->akimbo = getint(p); + loopi(2) d->akimbolastaction[i] = getint(p); + d->akimbomillis = getint(p); + + // poshist Previous stored locations of this player + d->history.nextupdate = getint(p); d->history.curpos = getint(p); d->history.numpos = getint(p); + loopi(POSHIST_SIZE) getvec(d->history.pos[i], p); +} void stop() { if(f) { - if(demorecording) gzputi(-1); + if(demorecording) + { + resetdp(); + putint(dp, lastmillis-starttime); // total recording time + gzwritedp(SD_EOF); + } gzclose(f); } f = NULL; @@ -48,19 +207,24 @@ void savestate(char *fn) if(!f) { conoutf("could not write %s", fn); return; } gzwrite(f, (void *)"CUBESAVE", 8); gzputc(f, SDL_BYTEORDER==SDL_LIL_ENDIAN ? 1 : 0); - gzputi(SAVEGAMEVERSION); - gzputi(sizeof(playerent)); - gzwrite(f, getclientmap(), _MAXDEFSTR); - gzputi(gamemode); - gzputi(ents.length()); - loopv(ents) gzputc(f, ents[i].spawned); - gzwrite(f, player1, sizeof(playerent)); - gzputi(players.length()); + gzputw(SAVEGAMEVERSION); gzputw(0); // compatibility, so far... + + resetdp(); + sendstring(getclientmap(), dp); + putint(dp, gamemode); + gzwritedp(SD_INIT1); + + putint(dp, ents.length()); + loopv(ents) putint(dp, ents[i].spawned); + putplayerent(dp, player1); + putint(dp, players.length()); loopv(players) { - gzput(players[i]==NULL); - if(players[i]) gzwrite(f, players[i], sizeof(playerent)); + int e = players[i]==NULL; + putint(dp, e); + if (!e) putplayerent(dp, players[i]); } + gzwritedp(SD_INIT2); } void savegame(char *name) @@ -79,15 +243,19 @@ void loadstate(char *fn) string buf; gzread(f, buf, 8); if(strncmp(buf, "CUBESAVE", 8)) goto out; - if(gzgetc(f)!=(SDL_BYTEORDER==SDL_LIL_ENDIAN ? 1 : 0)) goto out; // not supporting save->load accross incompatible architectures simpifies things a LOT - if(gzgeti()!=SAVEGAMEVERSION || gzgeti()!=sizeof(playerent)) goto out; + gzgetc(f); // who cares... + if(gzgetw()!=SAVEGAMEVERSION) goto out; + gzgetw(); // dummy + + if (gzreaddp(SD_INIT1) < 0) goto out; string mapname; - gzread(f, mapname, _MAXDEFSTR); - nextmode = gzgeti(); + getstring(mapname, dp, _MAXDEFSTR); + nextmode = getint(dp); changemap(mapname); // continue below once map has been loaded and client & server have updated + if (dp.remaining() || dp.overread()) conoutf("SD_INIT1 packet size mismatch"); // debug only return; out: - conoutf("aborting: savegame/demo from a different version of AssaultCube or cpu architecture"); + conoutf("aborting: savegame/demo header malformed"); stop(); } @@ -113,48 +281,51 @@ void fixplayerstate(playerent *d) void loadgamerest() { if(demoplayback || !f) return; - - if(gzgeti()!=ents.length()) return loadgameout(); + + if (gzreaddp(SD_INIT2) < 0) return; + if(getint(dp)!=ents.length()) return loadgameout(); loopv(ents) { - ents[i].spawned = gzgetc(f)!=0; + ents[i].spawned = getint(dp)!=0; } restoreserverstate(ents); + + getplayerent(player1, dp); + fixplayerstate(player1); - gzread(f, player1, sizeof(playerent)); - fixplayerstate(player1); - - int nplayers = gzgeti(); - loopi(nplayers) if(!gzget()) + int nplayers = getint(dp); + loopi(nplayers) if(!getint(dp)) { playerent *d = newclient(i); ASSERT(d); - gzread(f, d, sizeof(playerent)); + getplayerent(d, dp); fixplayerstate(d); } - + if (dp.remaining() || dp.overread()) conoutf("SD_INIT2 packet size mismatch"); // debug only conoutf("savegame restored"); if(demoloading) startdemo(); else stop(); } // demo functions -int starttime = 0; int playbacktime = 0; int ddamage, bdamage; vec dorig; + void record(char *name) { int cn = getclientnum(); if(cn<0) return; s_sprintfd(fn)("demos/%s.cdgz", name); savestate(fn); - gzputi(cn); + resetdp(); + putint(dp, cn); + gzwritedp(SD_INIT3); conoutf("started recording demo to %s", fn); demorecording = true; starttime = lastmillis; - ddamage = bdamage = 0; + ddamage = bdamage = 0; //fixme player1->lastaction = player1->lastanimswitchtime[0] = player1->lastanimswitchtime[1] = lastmillis; } @@ -165,50 +336,55 @@ void demoblend(int damage) { bdamage = damage; } void incomingdemodata(int chan, uchar *buf, int len, bool extras) { if(!demorecording) return; - gzputi(lastmillis-starttime); - gzputi(len); - gzput(chan); - gzwrite(f, buf, len); - gzput(extras); + resetdp(); + putint(dp, lastmillis-starttime); // playbacktime + putint(dp, len); + putint(dp, chan); + dp.put(buf, len); + putint(dp, extras ? 1 : 0); // pre-announce SD_PLAYER1 block for reading convenience + gzwritedp(SD_NETDATA); if(extras) { - gzput(player1->gunselect); - gzput(player1->lastattackgun); - gzputi(player1->gunwait); + putint(dp, player1->gunselect); + putint(dp, player1->lastattackgun); + putint(dp, player1->gunwait); ASSERT(player1->lastaction == 0 || player1->lastaction-starttime >= 0); - gzputi(player1->lastaction-starttime); - gzputi(player1->lastanimswitchtime[0]-starttime); - gzputi(player1->lastanimswitchtime[1]-starttime); - loopi(NUMGUNS) { gzput(player1->ammo[i]); gzput(player1->mag[i]); } - gzput(player1->akimbo ? 1 : 0 | (player1->reloading ? 1 : 0) << 1 | (player1->weaponchanging ? 1 : 0) << 2); + putint(dp, player1->lastaction-starttime); + putint(dp, player1->lastanimswitchtime[0]-starttime); + putint(dp, player1->lastanimswitchtime[1]-starttime); + loopi(NUMGUNS) { putint(dp, player1->ammo[i]); putint(dp, player1->mag[i]); } + putint(dp, player1->akimbo ? 1 : 0 | (player1->reloading ? 1 : 0) << 1 | (player1->weaponchanging ? 1 : 0) << 2); switch(player1->gunselect) { case GUN_GRENADE: - gzputi(player1->thrownademillis-starttime); + putint(dp, player1->thrownademillis-starttime); break; case GUN_PISTOL: if(player1->akimbo) { - gzputi(player1->akimbolastaction[0]-starttime); - gzputi(player1->akimbolastaction[1]-starttime); + putint(dp, player1->akimbolastaction[0]-starttime); + putint(dp, player1->akimbolastaction[1]-starttime); } break; case GUN_SNIPER: - gzput(scoped ? 1 : 0); + putint(dp, scoped ? 1 : 0); break; } - gzputi(player1->health); - gzputi(player1->armour); - gzput(player1->state); - gzputi(bdamage); - bdamage = 0; - gzputi(ddamage); - if(ddamage) { gzputv(dorig); ddamage = 0; } + putint(dp, player1->health); + putint(dp, player1->armour); + putint(dp, player1->state); + putint(dp, bdamage); + bdamage = 0; + putint(dp, ddamage); + if(ddamage) { putvec(dp, dorig); ddamage = 0; } // FIXME: add all other client state which is not send through the network + gzwritedp(SD_PLAYER1); } } +static bool hasbackup = false; + void settingsbackup(bool save) { static string name, team; @@ -219,12 +395,14 @@ void settingsbackup(bool save) s_strcpy(name, player1->name); s_strcpy(team, player1->team); gunselect = player1->gunselect; + hasbackup = true; } - else + else if (hasbackup) { s_strcpy(player1->name, name); s_strcpy(player1->team, team); player1->gunselect = gunselect; + player1->clientnum = 0; } } @@ -239,7 +417,7 @@ void demo(char *name) void stopreset() { conoutf("demo stopped (%d msec elapsed)", lastmillis-starttime); - player1 = newplayerent(); + if (hasbackup) player1 = newplayerent(); disconnect(0, 0); settingsbackup(false); } @@ -249,11 +427,9 @@ int scaletime(int t) { return (int)(t*(100.0f/demoplaybackspeed))+starttime; } void readdemotime() { - if(gzeof(f) || (playbacktime = gzgeti())==-1) - { - stopreset(); - return; - } + if (gzreaddp(SD_NETDATA, true) < 0) // skip unneeded packets + return; // demo done + playbacktime = getint(dp); playbacktime = scaletime(playbacktime); } @@ -275,12 +451,16 @@ void shiftdemoplayer(int i) vector plrs; loopv(players) if(players[i] != NULL) plrs.add(players[i]); int cur = plrs.find(demoplayer); - demoplayer = cur<0 ? player1 : plrs[(cur+i) % plrs.length()]; + if(cur >= 0) + demoplayer = plrs[(cur+i) % plrs.length()]; + else + demoplayer = player1; } void startdemo() { - democlientnum = gzgeti(); + if (gzreaddp(SD_INIT3) < 0) return; + democlientnum = getint(dp); demoplayback = true; starttime = lastmillis; conoutf("now playing demo"); @@ -313,65 +493,67 @@ void demoplaybackstep() { while(demoplayback && lastmillis>=playbacktime) { - int len = gzgeti(); + int len = getint(dp); if(len<1 || len>MAXTRANS) { conoutf("error: huge packet during demo play (%d)", len); stopreset(); return; } - int chan = gzget(); - uchar buf[MAXTRANS]; - gzread(f, buf, len); - - int extras = gzget(); + int chan = getint(dp); + uchar *buf = dp.current(); + dp.advance(len); + int extras = getint(dp); // extras may be >1 in future... + if (dp.remaining() || dp.overread()) conoutf("SD_NETDATA packet size mismatch"); // debug only bool updatehistory = (!extras && playerhistory.length()); - if(updatehistory) memcpy(player1, playerhistory.last(), sizeof(playerent)); + if (updatehistory) memcpy(player1, playerhistory.last(), sizeof(playerent)); localservertoclient(chan, buf, len); // update game state - if(updatehistory) memcpy(playerhistory.last(), player1, sizeof(playerent)); - + if (updatehistory) memcpy(playerhistory.last(), player1, sizeof(playerent)); + playerent *target = (playerent *)players[democlientnum]; ASSERT(target); if(extras) // read additional client side state not present in normal network stream { - target->gunselect = gzget(); - target->lastattackgun = gzget(); - target->gunwait = gzgeti(); - target->lastaction = scaletime(gzgeti()); + if (gzreaddp(SD_PLAYER1) < 0) return; + target->gunselect = getint(dp); + target->lastattackgun = getint(dp); + target->gunwait = getint(dp); + target->lastaction = scaletime(getint(dp)); //ASSERT(target->lastaction >= 0); if(target->lastaction < 0) target->lastaction = 0; - target->lastanimswitchtime[0] = scaletime(gzgeti()); - target->lastanimswitchtime[1] = scaletime(gzgeti()); - loopi(NUMGUNS) { target->ammo[i] = gzget(); target->mag[i] = gzget(); } - uchar flags = gzget(); + target->lastanimswitchtime[0] = scaletime(getint(dp)); + target->lastanimswitchtime[1] = scaletime(getint(dp)); + loopi(NUMGUNS) { target->ammo[i] = getint(dp); target->mag[i] = getint(dp); } + uchar flags = getint(dp); target->akimbo = flags&1 ? true : false; target->reloading = (flags>>1)&1 ? true : false; target->weaponchanging = (flags>>2)&1 ? true : false; switch(target->gunselect) { case GUN_GRENADE: - target->thrownademillis = scaletime(gzgeti()); + target->thrownademillis = scaletime(getint(dp)); break; case GUN_PISTOL: if(player1->akimbo) { - target->akimbolastaction[0] = scaletime(gzgeti()); - target->akimbolastaction[1] = scaletime(gzgeti()); + target->akimbolastaction[0] = scaletime(getint(dp)); + target->akimbolastaction[1] = scaletime(getint(dp)); } break; case GUN_SNIPER: - scoped = gzget() ? true : false; + scoped = getint(dp) ? true : false; break; } - target->health = gzgeti(); - target->armour = gzgeti(); - target->state = gzget(); - if((bdamage = gzgeti())) damageblend(bdamage); - if((ddamage = gzgeti())) { gzgetv(dorig); particle_splash(3, ddamage, 1000, dorig); }; + target->health = getint(dp); + target->armour = getint(dp); + target->state = getint(dp); + if((bdamage = getint(dp))) damageblend(bdamage); + if((ddamage = getint(dp))) { getvec(dorig, dp); particle_splash(3, ddamage, 1000, dorig); }; // FIXME: set more client state here target->lastmove = playbacktime; + if (dp.remaining() || dp.overread()) conoutf("SD_PLAYER1 packet size mismatch"); // debug only } // insert latest copy of player into history diff --git a/source/src/tools.h b/source/src/tools.h index 8861e201..ef037705 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -183,6 +183,28 @@ struct databuf len = maxlen; flags |= OVERREAD; } + + void forcelen(int nl) + { + len = nl < 0 ? 0 : (nl > maxlen ? maxlen : nl); + flags = 0; + } + + void forcemaxlen(int nl) + { + maxlen = nl < 0 ? 0 : nl; + flags = 0; + } + + int advance(int sz) + { + sz = min(sz, maxlen-len); + len += sz; + return sz; + } + + T* current() { return buf + len; } + }; typedef databuf charbuf;