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.
This commit is contained in:
John "Elwin" Edwards 2012-06-19 19:11:59 -07:00
parent 67f187700c
commit 5f914901d1

View file

@ -28,25 +28,29 @@ var games = {
"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 @@ function TermSession(game, lkey, dims, handlers) {
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 @@ function TermSession(game, lkey, dims, handlers) {
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 @@ function TermSession(game, lkey, dims, handlers) {
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 @@ function Watcher(session) {
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 @@ function randkey(words) {
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 @@ function webHandler(req, res) {
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)) {