Move TermSession and DglSession common code into a base class.

This commit is contained in:
John "Elwin" Edwards 2015-01-19 14:44:27 -05:00
parent a2bb3fe97d
commit 677d739bed

View file

@ -66,7 +66,66 @@ var dglgames = {};
var allowlogin = true; var allowlogin = true;
var gamemux = new events.EventEmitter(); var gamemux = new events.EventEmitter();
/* TODO move TermSession and DglSession methods into the prototypes. */ /* A base class. TermSession and DglSession inherit from it. */
function BaseGame() {
/* Games subclass EventEmitter, though there are few listeners. */
events.EventEmitter.call(this);
/* Array of watching WebSockets. */
this.watchers = [];
/* replaybuf holds the output since the last screen clear, so watchers can
* begin with a complete screen. replaylen is the number of bytes stored. */
this.replaybuf = new Buffer(1024);
this.replaylen = 0;
/* Time of last activity. */
this.lasttime = new Date();
}
BaseGame.prototype = new events.EventEmitter();
BaseGame.prototype.tag = function () {
if (this.pname === undefined || this.gname === undefined)
return "";
return this.gname + "/" + this.pname;
};
BaseGame.prototype.framepush = function(chunk) {
/* If this chunk resets the screen, discard what preceded it. */
if (isclear(chunk)) {
this.replaybuf = new Buffer(1024);
this.replaylen = 0;
}
/* Make sure there's space. */
while (this.replaybuf.length < chunk.length + this.replaylen) {
var nbuf = new Buffer(this.replaybuf.length * 2);
this.replaybuf.copy(nbuf, 0, 0, this.replaylen);
this.replaybuf = nbuf;
if (this.replaybuf.length > 65536) {
tslog("Warning: %s frame buffer at %d bytes", this.tag(),
this.replaybuf.length);
}
}
chunk.copy(this.replaybuf, this.replaylen);
this.replaylen += chunk.length;
};
/* Adds a watcher. */
BaseGame.prototype.attach = function (wsReq) {
var conn = wsReq.accept(null, wsReq.origin);
conn.sendUTF(JSON.stringify({
"t": "w", "w": this.w, "h": this.h, "p": this.pname, "g": this.gname
}));
conn.sendUTF(JSON.stringify({"t": "d",
"d": this.replaybuf.toString("hex", 0, this.replaylen)}));
conn.on('close', this.detach.bind(this, conn));
this.watchers.push(conn);
};
BaseGame.prototype.detach = function (socket) {
var n = this.watchers.indexOf(socket);
if (n >= 0) {
this.watchers.splice(n, 1);
tslog("A WebSocket watcher has left game %s", this.tag());
}
};
/* Constructor. A TermSession handles a pty and the game running on it. /* Constructor. A TermSession handles a pty and the game running on it.
* gname: (String) Name of the game to launch. * gname: (String) Name of the game to launch.
@ -78,8 +137,7 @@ var gamemux = new events.EventEmitter();
* "exit": Child terminated. Parameters: none * "exit": Child terminated. Parameters: none
*/ */
function TermSession(gname, pname, wsReq) { function TermSession(gname, pname, wsReq) {
/* Subclass EventEmitter to do the hard work. */ BaseGame.call(this);
events.EventEmitter.call(this);
/* Don't launch anything that's not a real game. */ /* Don't launch anything that's not a real game. */
if (gname in games) { if (gname in games) {
this.game = games[gname]; this.game = games[gname];
@ -113,7 +171,6 @@ function TermSession(gname, pname, wsReq) {
sessions[this.gname + "/" + this.pname] = this; sessions[this.gname + "/" + this.pname] = this;
gamemux.emit('begin', this.gname, this.pname, 'rlg'); gamemux.emit('begin', this.gname, this.pname, 'rlg');
/* Set up the lockfile and ttyrec */ /* Set up the lockfile and ttyrec */
this.lasttime = new Date();
var ts = timestamp(this.lasttime); var ts = timestamp(this.lasttime);
var progressdir = path.join("/dgldir/inprogress", this.gname); var progressdir = path.join("/dgldir/inprogress", this.gname);
this.lock = path.join(progressdir, this.pname + ":node:" + ts + ".ttyrec"); this.lock = path.join(progressdir, this.pname + ":node:" + ts + ".ttyrec");
@ -122,16 +179,10 @@ function TermSession(gname, pname, wsReq) {
var ttyrec = path.join("/dgldir/ttyrec", this.pname, this.gname, var ttyrec = path.join("/dgldir/ttyrec", this.pname, this.gname,
ts + ".ttyrec"); ts + ".ttyrec");
this.record = fs.createWriteStream(ttyrec, { mode: 0664 }); this.record = fs.createWriteStream(ttyrec, { mode: 0664 });
/* Holds the output since the last screen clear, so watchers can begin
* with a complete screen. */
this.framebuf = new Buffer(1024);
this.frameoff = 0;
/* The player's WebSocket and its handlers. */ /* The player's WebSocket and its handlers. */
this.playerconn = wsReq.accept(null, wsReq.origin); this.playerconn = wsReq.accept(null, wsReq.origin);
this.playerconn.on('message', this.input_msg.bind(this)); this.playerconn.on('message', this.input_msg.bind(this));
this.playerconn.on('close', this.close.bind(this)); this.playerconn.on('close', this.close.bind(this));
/* Array for watcher connections. */
this.watchers = [];
/* Send initial data. */ /* Send initial data. */
this.playerconn.sendUTF(JSON.stringify({"t": "s", "w": this.w, "h": this.h, this.playerconn.sendUTF(JSON.stringify({"t": "s", "w": this.w, "h": this.h,
"p": this.pname, "g": this.gname})); "p": this.pname, "g": this.gname}));
@ -147,33 +198,7 @@ function TermSession(gname, pname, wsReq) {
this.term.on("data", this.write_ttyrec.bind(this)); this.term.on("data", this.write_ttyrec.bind(this));
this.term.on("exit", this.destroy.bind(this)); this.term.on("exit", this.destroy.bind(this));
} }
TermSession.prototype = new events.EventEmitter(); TermSession.prototype = new BaseGame();
TermSession.prototype.tag = function () {
if (this.pname === undefined || this.gname === undefined)
return "";
return this.gname + "/" + this.pname;
};
TermSession.prototype.framepush = function(chunk) {
/* If this chunk resets the screen, discard what preceded it. */
if (isclear(chunk)) {
this.framebuf = new Buffer(1024);
this.frameoff = 0;
}
/* Make sure there's space. */
while (this.framebuf.length < chunk.length + this.frameoff) {
var nbuf = new Buffer(this.framebuf.length * 2);
this.framebuf.copy(nbuf, 0, 0, this.frameoff);
this.framebuf = nbuf;
if (this.framebuf.length > 65536) {
tslog("Warning: Game %s frame buffer at %d bytes", this.tag(),
this.framebuf.length);
}
}
chunk.copy(this.framebuf, this.frameoff);
this.frameoff += chunk.length;
};
/* Currently this also sends to the player and any watchers. */ /* Currently this also sends to the player and any watchers. */
TermSession.prototype.write_ttyrec = function (datastr) { TermSession.prototype.write_ttyrec = function (datastr) {
@ -244,29 +269,9 @@ TermSession.prototype.destroy = function () {
tslog("Game %s ended.", tag); tslog("Game %s ended.", tag);
}; };
/* Adds a watcher. */
TermSession.prototype.attach = function (wsReq) {
var conn = wsReq.accept(null, wsReq.origin);
conn.sendUTF(JSON.stringify({
"t": "w", "w": this.w, "h": this.h, "p": this.pname, "g": this.gname
}));
conn.sendUTF(JSON.stringify({"t": "d",
"d": this.framebuf.toString("hex", 0, this.frameoff)}));
conn.on('close', this.detach.bind(this, conn));
this.watchers.push(conn);
};
TermSession.prototype.detach = function (socket) {
var n = this.watchers.indexOf(socket);
if (n >= 0) {
this.watchers.splice(n, 1);
tslog("A WebSocket watcher has left game %s", this.tag());
}
};
function DglSession(filename) { function DglSession(filename) {
var ss = this; var ss = this;
events.EventEmitter.call(this); BaseGame.call(this);
var pathcoms = filename.split('/'); var pathcoms = filename.split('/');
this.gname = pathcoms[pathcoms.length - 2]; this.gname = pathcoms[pathcoms.length - 2];
if (!(this.gname in games)) { if (!(this.gname in games)) {
@ -282,27 +287,6 @@ function DglSession(filename) {
* getting into a race. */ * getting into a race. */
this.reading = false; this.reading = false;
this.rpos = 0; this.rpos = 0;
this.framebuf = new Buffer(1024);
this.frameoff = 0;
this.framepush = function(chunk) {
/* If this chunk resets the screen, discard what preceded it. */
if (isclear(chunk)) {
this.framebuf = new Buffer(1024);
this.frameoff = 0;
}
/* Make sure there's space. */
while (this.framebuf.length < chunk.length + this.frameoff) {
var nbuf = new Buffer(this.framebuf.length * 2);
this.framebuf.copy(nbuf, 0, 0, this.frameoff);
this.framebuf = nbuf;
if (this.framebuf.length > 65536) {
tslog("Warning: DGL %s frame buffer at %d bytes", this.tag(),
this.framebuf.length);
}
}
chunk.copy(this.framebuf, this.frameoff);
this.frameoff += chunk.length;
};
this.readchunk = function () { this.readchunk = function () {
if (this.reading) if (this.reading)
return; return;
@ -375,9 +359,6 @@ function DglSession(filename) {
} }
}); });
}); });
this.tag = function () {
return this.gname + "/" + this.pname;
};
this.close = function () { this.close = function () {
this.recwatcher.close() this.recwatcher.close()
/* Ensure all data is handled before quitting. */ /* Ensure all data is handled before quitting. */
@ -393,29 +374,8 @@ function DglSession(filename) {
gamemux.emit('end', this.gname, this.pname); gamemux.emit('end', this.gname, this.pname);
tslog("DGL %s: closed", ss.tag()); tslog("DGL %s: closed", ss.tag());
}; };
this.watchers = [];
/* Attach a watcher. */
this.attach = function (wsReq) {
var conn = wsReq.accept(null, wsReq.origin);
conn.sendUTF(JSON.stringify({
"t": "w", "w": this.w, "h": this.h, "p": this.pname,
"g": this.gname
}));
conn.sendUTF(JSON.stringify({"t": "d",
"d": this.framebuf.toString("hex", 0, this.frameoff)}));
conn.on('close', function () {
/* 'this' is the connection when triggered */
var n = ss.watchers.indexOf(this);
if (n >= 0) {
ss.watchers.splice(n, 1);
tslog("A WebSocket watcher has left DGL game %s", ss.tag());
} }
}); DglSession.prototype = new BaseGame();
this.watchers.push(conn);
};
this.lasttime = new Date();
}
DglSession.prototype = new events.EventEmitter();
function wsStartGame(wsReq) { function wsStartGame(wsReq) {
var playmatch = wsReq.resourceURL.pathname.match(/^\/play\/([^\/]*)$/); var playmatch = wsReq.resourceURL.pathname.match(/^\/play\/([^\/]*)$/);