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;