# HG changeset patch # User John "Elwin" Edwards # Date 1420826801 18000 # Node ID 50e4c9feeac2368d53a9ca08d8b9e108c698b430 # Parent 6f4b7e1b32e8abf0289006884a704c557e1bea33 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. diff -r 6f4b7e1b32e8 -r 50e4c9feeac2 rlgwebd.js --- a/rlgwebd.js Fri Jan 09 09:43:21 2015 -0500 +++ b/rlgwebd.js Fri Jan 09 13:06:41 2015 -0500 @@ -69,35 +69,32 @@ /* 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 @@ 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 @@ * 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 @@ 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 @@ 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 @@ "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 @@ 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 @@ 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);