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; |