RLGWebD: fix simultaneous player bug.

Multiple games can now run at the same time, and data will be sent to
the proper place.  The interaction of multiple players with watchers
has not yet been tested.
This commit is contained in:
John "Elwin" Edwards 2015-01-09 13:06:41 -05:00
parent ef4cb9ac8d
commit 6373e70361

View file

@ -69,35 +69,32 @@ var gamemux = new events.EventEmitter();
/* Constructor. A TermSession handles a pty and the game running on it.
* gname: (String) Name of the game to launch.
* pname: (String) The player's name.
* dims: (Array [Number, Number]) Height and width of the pty.
* handlers: (Object) Key-value pairs, event names and functions to
* install to handle them.
* wsReq: (WebSocketRequest) The request from the client.
*
* Events:
* "data": Data generated by child. Parameters: buf (Buffer)
* "exit": Child terminated. Parameters: none
*/
function TermSession(gname, pname, dims, handlers) {
function TermSession(gname, pname, wsReq) {
var ss = this;
/* Subclass EventEmitter to do the hard work. */
events.EventEmitter.call(this);
for (var evname in handlers)
this.on(evname, handlers[evname]);
/* Don't launch anything that's not a real game. */
if (gname in games) {
this.game = games[gname];
}
else {
this.failed = true;
wsReq.reject(404, errorcodes[2], "No such game");
tslog("Game %s is not available", game);
return;
}
this.pname = pname;
/* Grab a spot in the sessions table. */
sessions[this.game.uname + "/" + this.pname] = this;
/* Set up the sizes. */
this.w = Math.floor(Number(dims[1]));
this.w = Math.floor(Number(wsReq.resourceURL.query.w));
if (!(this.w > 0 && this.w < 256))
this.w = 80;
this.h = Math.floor(Number(dims[0]));
this.h = Math.floor(Number(wsReq.resourceURL.query.h));
if (!(this.h > 0 && this.h < 256))
this.h = 24;
/* Environment. */
@ -111,6 +108,7 @@ function TermSession(gname, pname, dims, handlers) {
this.term = pty.spawn(this.game.path, args, spawnopts);
tslog("%s playing %s (pid %d)", this.pname, this.game.uname, this.term.pid);
this.failed = false;
sessions[this.game.uname + "/" + this.pname] = this;
gamemux.emit('begin', this.game.uname, this.pname);
/* Set up the lockfile and ttyrec */
this.lasttime = new Date();
@ -126,6 +124,7 @@ function TermSession(gname, pname, dims, handlers) {
* with a complete screen. */
this.framebuf = new Buffer(1024);
this.frameoff = 0;
this.playerconn = wsReq.accept(null, wsReq.origin);
/* END setup */
function ttyrec_chunk(datastr) {
ss.lasttime = new Date();
@ -138,6 +137,10 @@ function TermSession(gname, pname, dims, handlers) {
buf.copy(chunk, 12);
ss.record.write(chunk);
ss.framepush(buf);
/* Send to the player. */
var msg = {"t": "d", "d": buf.toString("hex")};
ss.playerconn.sendUTF(JSON.stringify(msg));
/* For the benefit of watchers. */
ss.emit('data', buf);
}
this.term.on("data", ttyrec_chunk);
@ -171,15 +174,39 @@ function TermSession(gname, pname, dims, handlers) {
var tag = ss.tag();
fs.unlink(ss.lock);
ss.record.end();
if (ss.playerconn.connected) {
ss.playerconn.sendUTF(JSON.stringify({"t": "q"}));
ss.playerconn.close();
}
ss.emit('exit');
gamemux.emit('end', ss.game.uname, ss.pname);
delete sessions[tag];
tslog("Game %s ended.", tag);
});
this.close = function () {
if (this.tag() in sessions)
this.term.kill('SIGHUP');
if (ss.tag() in sessions)
ss.term.kill('SIGHUP');
};
/* Send initial data. */
this.playerconn.sendUTF(JSON.stringify({"t": "s", "w": this.w, "h": this.h,
"p": this.pname, "g": this.game.uname}));
/* Attach handlers. */
function messageH(message) {
var parsedMsg = getMsgWS(message);
if (parsedMsg.t == 'q') {
ss.close();
}
else if (parsedMsg.t == 'd') {
var hexstr = parsedMsg.d.replace(/[^0-9a-f]/gi, "");
if (hexstr.length % 2 != 0) {
hexstr = hexstr.slice(0, -1);
}
var keybuf = new Buffer(hexstr, "hex");
ss.write(keybuf);
}
}
this.playerconn.on('message', messageH);
this.playerconn.on('close', this.close);
}
TermSession.prototype = new events.EventEmitter();
@ -315,64 +342,7 @@ function wsWatcher(conn, session) {
"d": session.framebuf.toString("hex", 0, session.frameoff)}));
}
function wsPlay(wsReq, game, pname, dims) {
tslog("wsPlay: running for %s/%s", game, pname);
tslog("Request is for %s", logins[wsReq.resourceURL.query["key"]].name);
var conn;
var session;
/* Listeners on the WebSocket */
function messageH(message) {
var parsedMsg = getMsgWS(message);
if (parsedMsg.t == 'q') {
session.close();
}
else if (parsedMsg.t == 'd') {
var hexstr = parsedMsg.d.replace(/[^0-9a-f]/gi, "");
if (hexstr.length % 2 != 0) {
hexstr = hexstr.slice(0, -1);
}
var keybuf = new Buffer(hexstr, "hex");
session.write(keybuf);
}
}
function closeH() {
session.close();
}
/* These listen on the TermSession. */
function dataH(chunk) {
var msg = {};
msg.t = "d";
msg.d = chunk.toString("hex");
conn.sendUTF(JSON.stringify(msg));
}
function exitH() {
if (conn.connected)
conn.sendUTF(JSON.stringify({"t": "q"}));
conn.close();
session.removeListener('data', dataH);
session.removeListener('exit', exitH);
}
var handlers = {'data': dataH, 'exit': exitH};
session = new TermSession(game, pname, dims, handlers);
if (!session.failed) {
var tag = session.game.uname + "/" + session.pname;
var reply = {"t": "s", "tag": tag, "w": session.w, "h": session.h,
"p": session.pname, "g": session.game.uname};
tslog("Accepting for %s", tag);
tslog("Request is for %s", logins[wsReq.resourceURL.query["key"]].name);
tslog("Session is for %s", session.pname);
conn = wsReq.accept(null, wsReq.origin);
conn.sendUTF(JSON.stringify(reply));
conn.on('message', messageH);
conn.on('close', closeH);
}
else {
wsReq.reject(500, errorcodes[5]);
tslog("Unable to allocate TTY for %s", game);
}
}
function wsStart(wsReq) {
function wsStartGame(wsReq) {
var playmatch = wsReq.resourceURL.pathname.match(/^\/play\/([^\/]*)$/);
if (!playmatch[1] || !(playmatch[1] in games)) {
wsReq.reject(404, errorcodes[2]);
@ -393,14 +363,14 @@ function wsStart(wsReq) {
return;
}
var pname = logins[lkey].name;
var dims = [wsReq.resourceURL.query.h, wsReq.resourceURL.query.w];
function progcallback(err, fname) {
if (fname) {
wsReq.reject(404, errorcodes[4]);
tslog("%s is already playing %s", pname, gname);
}
else
wsPlay(wsReq, gname, pname, dims);
else {
new TermSession(gname, pname, wsReq);
}
};
checkprogress(pname, games[gname], progcallback, []);
}
@ -1044,7 +1014,7 @@ function wsHandler(wsRequest) {
tslog("Game %s is being watched via WebSockets", tsession.tag());
}
else if (playmatch !== null) {
wsStart(wsRequest);
wsStartGame(wsRequest);
}
else if (wsRequest.resourceURL.pathname == "/status") {
var conn = wsRequest.accept(null, wsRequest.origin);