changeset 170:50e4c9feeac2

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.
author John "Elwin" Edwards
date Fri, 09 Jan 2015 13:06:41 -0500
parents 6f4b7e1b32e8
children 671bed5039aa
files rlgwebd.js
diffstat 1 files changed, 44 insertions(+), 74 deletions(-) [+]
line wrap: on
line diff
--- 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);