Mercurial > hg > rlgwebd
diff rlgwebd.js @ 60:31bb3cf4f25f
Make sure watchers start with completely drawn screens.
TermSessions now buffer all data since the last screen clear, so new
Watchers can start with complete screens.
author | John "Elwin" Edwards <elwin@sdf.org> |
---|---|
date | Tue, 19 Jun 2012 19:11:59 -0700 |
parents | 96815eae4ebe |
children | 7793ad53b90f |
line wrap: on
line diff
--- a/rlgwebd.js Tue Jun 19 16:19:50 2012 -0700 +++ b/rlgwebd.js Tue Jun 19 19:11:59 2012 -0700 @@ -28,25 +28,29 @@ "name": "Rogue V3", "uname": "rogue3", "suffix": ".r3sav", - "path": "/bin/rogue3" + "path": "/bin/rogue3", + "clear": new Buffer([27, 91, 72, 27, 91, 50, 74]) // CSI H CSI 2J }, "rogue4": { "name": "Rogue V4", "uname": "rogue4", "suffix": ".r4sav", - "path": "/bin/rogue4" + "path": "/bin/rogue4", + "clear": new Buffer([27, 91, 72, 27, 91, 50, 74]) // CSI H CSI 2J }, "rogue5": { "name": "Rogue V5", "uname": "rogue5", "suffix": ".r5sav", - "path": "/bin/rogue5" + "path": "/bin/rogue5", + "clear": new Buffer([27, 91, 72, 27, 91, 50, 74]) // CSI H CSI 2J }, "srogue": { "name": "Super-Rogue", "uname": "srogue", "suffix": ".srsav", - "path": "/bin/srogue" + "path": "/bin/srogue", + "clear": new Buffer([27, 91, 72, 27, 91, 74]) // CSI H CSI J } }; @@ -118,6 +122,10 @@ var ttyrec = path.join("/dgldir/ttyrec", this.pname, this.game.uname, ts + ".ttyrec"); this.record = fs.createWriteStream(ttyrec, { mode: 0664 }); + /* Holds the output since the last screen clear, so watchers can begin + * with a complete screen. */ + this.framebuf = new Buffer(1024); + this.frameoff = 0; logins[lkey].sessions.push(this.sessid); tslog("%s playing %s (index %d, pid %d)", this.pname, this.game.uname, this.sessid, this.child.pid); @@ -131,10 +139,30 @@ chunk.writeUInt32LE(buf.length, 8); buf.copy(chunk, 12); ss.record.write(chunk); + ss.framepush(buf); ss.emit('data', buf); } this.child.stdout.on("data", ttyrec_chunk); this.child.stderr.on("data", ttyrec_chunk); + this.framepush = function(chunk) { + /* If this chunk resets the screen, discard what preceded it. */ + if (bufncmp(chunk, this.game.clear, this.game.clear.length)) { + this.framebuf = new Buffer(1024); + this.frameoff = 0; + } + /* Make sure there's space. */ + while (this.framebuf.length < chunk.length + this.frameoff) { + var nbuf = new Buffer(this.framebuf.length * 2); + this.framebuf.copy(nbuf, 0, 0, this.frameoff); + this.framebuf = nbuf; + if (this.framebuf.length > 65536) { + tslog("Warning: Game %d frame buffer at %d bytes", this.sessid, + this.framebuf.length); + } + } + chunk.copy(this.framebuf, this.frameoff); + this.frameoff += chunk.length; + }; this.write = function(data) { this.child.stdin.write(data); }; @@ -144,7 +172,7 @@ ss.emit('exit', code, signal); var id = ss.sessid; delete sessions[id]; - tslog("Session %s ended.", id); + tslog("Game %s ended.", id); }); this.close = function () { this.child.kill('SIGHUP'); @@ -165,6 +193,9 @@ this.id = randkey(2); } clients[this.id] = this; + /* Recreate the current screen state from the session's buffer. */ + this.sendQ.push({"t": "d", "n": this.nsend++, + "d": session.framebuf.toString("hex", 0, session.frameoff)}); function dataH(buf) { var reply = {}; reply.t = "d"; @@ -388,6 +419,21 @@ return key; } +/* Compares two buffers, returns true for equality up to index n */ +function bufncmp(buf1, buf2, n) { + if (!Buffer.isBuffer(buf1) || !Buffer.isBuffer(buf2)) + return false; + for (var i = 0; i < n; i++) { + if (i == buf1.length && i == buf2.length) + return true; + if (i == buf1.length || i == buf2.length) + return false; + if (buf1[i] != buf2[i]) + return false; + } + return true; +} + function tslog() { arguments[0] = new Date().toISOString() + ": " + String(arguments[0]); console.log.apply(console, arguments); @@ -839,6 +885,7 @@ if (formdata.t == "q") { /* The client wants to terminate the process. */ endgame(client, res); + return; // endgame() calls readFeed() itself. } else if (formdata.t == "d" && typeof(formdata.d) == "string") { if (!(client instanceof Player)) {