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)) {