Mercurial > hg > rlgwebd
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 59:00b985b8ba6a | 60:31bb3cf4f25f |
|---|---|
| 26 var games = { | 26 var games = { |
| 27 "rogue3": { | 27 "rogue3": { |
| 28 "name": "Rogue V3", | 28 "name": "Rogue V3", |
| 29 "uname": "rogue3", | 29 "uname": "rogue3", |
| 30 "suffix": ".r3sav", | 30 "suffix": ".r3sav", |
| 31 "path": "/bin/rogue3" | 31 "path": "/bin/rogue3", |
| 32 "clear": new Buffer([27, 91, 72, 27, 91, 50, 74]) // CSI H CSI 2J | |
| 32 }, | 33 }, |
| 33 "rogue4": { | 34 "rogue4": { |
| 34 "name": "Rogue V4", | 35 "name": "Rogue V4", |
| 35 "uname": "rogue4", | 36 "uname": "rogue4", |
| 36 "suffix": ".r4sav", | 37 "suffix": ".r4sav", |
| 37 "path": "/bin/rogue4" | 38 "path": "/bin/rogue4", |
| 39 "clear": new Buffer([27, 91, 72, 27, 91, 50, 74]) // CSI H CSI 2J | |
| 38 }, | 40 }, |
| 39 "rogue5": { | 41 "rogue5": { |
| 40 "name": "Rogue V5", | 42 "name": "Rogue V5", |
| 41 "uname": "rogue5", | 43 "uname": "rogue5", |
| 42 "suffix": ".r5sav", | 44 "suffix": ".r5sav", |
| 43 "path": "/bin/rogue5" | 45 "path": "/bin/rogue5", |
| 46 "clear": new Buffer([27, 91, 72, 27, 91, 50, 74]) // CSI H CSI 2J | |
| 44 }, | 47 }, |
| 45 "srogue": { | 48 "srogue": { |
| 46 "name": "Super-Rogue", | 49 "name": "Super-Rogue", |
| 47 "uname": "srogue", | 50 "uname": "srogue", |
| 48 "suffix": ".srsav", | 51 "suffix": ".srsav", |
| 49 "path": "/bin/srogue" | 52 "path": "/bin/srogue", |
| 53 "clear": new Buffer([27, 91, 72, 27, 91, 74]) // CSI H CSI J | |
| 50 } | 54 } |
| 51 }; | 55 }; |
| 52 | 56 |
| 53 /* Global state */ | 57 /* Global state */ |
| 54 var logins = {}; | 58 var logins = {}; |
| 116 var lmsg = this.child.pid.toString() + '\n' + this.w + '\n' + this.h + '\n'; | 120 var lmsg = this.child.pid.toString() + '\n' + this.w + '\n' + this.h + '\n'; |
| 117 fs.writeFile(this.lock, lmsg, "utf8"); | 121 fs.writeFile(this.lock, lmsg, "utf8"); |
| 118 var ttyrec = path.join("/dgldir/ttyrec", this.pname, this.game.uname, | 122 var ttyrec = path.join("/dgldir/ttyrec", this.pname, this.game.uname, |
| 119 ts + ".ttyrec"); | 123 ts + ".ttyrec"); |
| 120 this.record = fs.createWriteStream(ttyrec, { mode: 0664 }); | 124 this.record = fs.createWriteStream(ttyrec, { mode: 0664 }); |
| 125 /* Holds the output since the last screen clear, so watchers can begin | |
| 126 * with a complete screen. */ | |
| 127 this.framebuf = new Buffer(1024); | |
| 128 this.frameoff = 0; | |
| 121 logins[lkey].sessions.push(this.sessid); | 129 logins[lkey].sessions.push(this.sessid); |
| 122 tslog("%s playing %s (index %d, pid %d)", this.pname, this.game.uname, | 130 tslog("%s playing %s (index %d, pid %d)", this.pname, this.game.uname, |
| 123 this.sessid, this.child.pid); | 131 this.sessid, this.child.pid); |
| 124 /* END setup */ | 132 /* END setup */ |
| 125 function ttyrec_chunk(buf) { | 133 function ttyrec_chunk(buf) { |
| 129 chunk.writeUInt32LE(Math.floor(ts.getTime() / 1000), 0); | 137 chunk.writeUInt32LE(Math.floor(ts.getTime() / 1000), 0); |
| 130 chunk.writeUInt32LE(1000 * (ts.getTime() % 1000), 4); | 138 chunk.writeUInt32LE(1000 * (ts.getTime() % 1000), 4); |
| 131 chunk.writeUInt32LE(buf.length, 8); | 139 chunk.writeUInt32LE(buf.length, 8); |
| 132 buf.copy(chunk, 12); | 140 buf.copy(chunk, 12); |
| 133 ss.record.write(chunk); | 141 ss.record.write(chunk); |
| 142 ss.framepush(buf); | |
| 134 ss.emit('data', buf); | 143 ss.emit('data', buf); |
| 135 } | 144 } |
| 136 this.child.stdout.on("data", ttyrec_chunk); | 145 this.child.stdout.on("data", ttyrec_chunk); |
| 137 this.child.stderr.on("data", ttyrec_chunk); | 146 this.child.stderr.on("data", ttyrec_chunk); |
| 147 this.framepush = function(chunk) { | |
| 148 /* If this chunk resets the screen, discard what preceded it. */ | |
| 149 if (bufncmp(chunk, this.game.clear, this.game.clear.length)) { | |
| 150 this.framebuf = new Buffer(1024); | |
| 151 this.frameoff = 0; | |
| 152 } | |
| 153 /* Make sure there's space. */ | |
| 154 while (this.framebuf.length < chunk.length + this.frameoff) { | |
| 155 var nbuf = new Buffer(this.framebuf.length * 2); | |
| 156 this.framebuf.copy(nbuf, 0, 0, this.frameoff); | |
| 157 this.framebuf = nbuf; | |
| 158 if (this.framebuf.length > 65536) { | |
| 159 tslog("Warning: Game %d frame buffer at %d bytes", this.sessid, | |
| 160 this.framebuf.length); | |
| 161 } | |
| 162 } | |
| 163 chunk.copy(this.framebuf, this.frameoff); | |
| 164 this.frameoff += chunk.length; | |
| 165 }; | |
| 138 this.write = function(data) { | 166 this.write = function(data) { |
| 139 this.child.stdin.write(data); | 167 this.child.stdin.write(data); |
| 140 }; | 168 }; |
| 141 this.child.on("exit", function (code, signal) { | 169 this.child.on("exit", function (code, signal) { |
| 142 fs.unlink(ss.lock); | 170 fs.unlink(ss.lock); |
| 143 ss.record.end(); | 171 ss.record.end(); |
| 144 ss.emit('exit', code, signal); | 172 ss.emit('exit', code, signal); |
| 145 var id = ss.sessid; | 173 var id = ss.sessid; |
| 146 delete sessions[id]; | 174 delete sessions[id]; |
| 147 tslog("Session %s ended.", id); | 175 tslog("Game %s ended.", id); |
| 148 }); | 176 }); |
| 149 this.close = function () { | 177 this.close = function () { |
| 150 this.child.kill('SIGHUP'); | 178 this.child.kill('SIGHUP'); |
| 151 }; | 179 }; |
| 152 } | 180 } |
| 163 this.id = randkey(2); | 191 this.id = randkey(2); |
| 164 while (this.id in clients) { | 192 while (this.id in clients) { |
| 165 this.id = randkey(2); | 193 this.id = randkey(2); |
| 166 } | 194 } |
| 167 clients[this.id] = this; | 195 clients[this.id] = this; |
| 196 /* Recreate the current screen state from the session's buffer. */ | |
| 197 this.sendQ.push({"t": "d", "n": this.nsend++, | |
| 198 "d": session.framebuf.toString("hex", 0, session.frameoff)}); | |
| 168 function dataH(buf) { | 199 function dataH(buf) { |
| 169 var reply = {}; | 200 var reply = {}; |
| 170 reply.t = "d"; | 201 reply.t = "d"; |
| 171 reply.n = ss.nsend++; | 202 reply.n = ss.nsend++; |
| 172 reply.d = buf.toString("hex"); | 203 reply.d = buf.toString("hex"); |
| 384 } | 415 } |
| 385 var key = ""; | 416 var key = ""; |
| 386 for (var i = 0; i < words; i++) | 417 for (var i = 0; i < words; i++) |
| 387 key += rand32(); | 418 key += rand32(); |
| 388 return key; | 419 return key; |
| 420 } | |
| 421 | |
| 422 /* Compares two buffers, returns true for equality up to index n */ | |
| 423 function bufncmp(buf1, buf2, n) { | |
| 424 if (!Buffer.isBuffer(buf1) || !Buffer.isBuffer(buf2)) | |
| 425 return false; | |
| 426 for (var i = 0; i < n; i++) { | |
| 427 if (i == buf1.length && i == buf2.length) | |
| 428 return true; | |
| 429 if (i == buf1.length || i == buf2.length) | |
| 430 return false; | |
| 431 if (buf1[i] != buf2[i]) | |
| 432 return false; | |
| 433 } | |
| 434 return true; | |
| 389 } | 435 } |
| 390 | 436 |
| 391 function tslog() { | 437 function tslog() { |
| 392 arguments[0] = new Date().toISOString() + ": " + String(arguments[0]); | 438 arguments[0] = new Date().toISOString() + ": " + String(arguments[0]); |
| 393 console.log.apply(console, arguments); | 439 console.log.apply(console, arguments); |
| 837 return; | 883 return; |
| 838 } | 884 } |
| 839 if (formdata.t == "q") { | 885 if (formdata.t == "q") { |
| 840 /* The client wants to terminate the process. */ | 886 /* The client wants to terminate the process. */ |
| 841 endgame(client, res); | 887 endgame(client, res); |
| 888 return; // endgame() calls readFeed() itself. | |
| 842 } | 889 } |
| 843 else if (formdata.t == "d" && typeof(formdata.d) == "string") { | 890 else if (formdata.t == "d" && typeof(formdata.d) == "string") { |
| 844 if (!(client instanceof Player)) { | 891 if (!(client instanceof Player)) { |
| 845 sendError(res, 7, "Watching", true); | 892 sendError(res, 7, "Watching", true); |
| 846 return; | 893 return; |
