Wednesday, June 30, 2010

Breaking One Big Fab App in Two

‹prev | My Chain | next›

Up today, a quick refactoring that has been a long time coming in the fab.js backend of my (fab) game:
// TODO: test and/or shrink
function broadcast_new (app) {
return function () {
var out = this;

return app.call( function listener(obj) {
if (obj && obj.body && obj.body.id) {
for (var id in players) {
out({body: comet_new_player(JSON.stringify(players[id].status))});
}

var new_id = obj.body.id;
if (!players[new_id]) {
puts("[broadcast_new] adding: " + new_id);
add_player(obj.body, out);

idle_watch(new_id);
setTimeout(function(){keepalive(new_id);}, 30*1000);
}
else if (players[new_id].uniq_id == obj.body.uniq_id) {
puts("[broadcast_new] refreshing session: " + new_id);
add_player(obj.body, out);
}
else {
out();
}
}
else {
out(obj);
}
return listener;
});
};
}
The reason that this function is so long is that it is doing two things: adding players to a local store and broadcasting existing players to the new game player. So I break out the local store into a second (fab) app:
  ( /^\/comet_view/ )
( broadcast_new )
( store_player )
( init_comet )
( player_from_querystring )
I define store_player as:
function store_player (app) {
return function () {
var out = this;

return app.call( function listener(obj) {
if (obj && obj.body && obj.body.id) {
var new_id = obj.body.id;
if (!players[new_id]) {
puts("[store_player] adding: " + new_id);
add_player(obj.body, out);

idle_watch(new_id);
setTimeout(function(){keepalive(new_id);}, 30*1000);
}
else if (players[new_id].uniq_id == obj.body.uniq_id) {
puts("[store_player] refreshing session: " + new_id);
add_player(obj.body, out);
}
else {
out();
}
}
out(obj);

return listener;
});
};
}
Reading along, if there is a body (a player from the upstream app player_from_query_string), then we check to see if the player is already playing. If no, then add the player to a local store. If the player is already in the local store, then refresh the session—as long as the player is not impersonating someone else (as evidenced by the uniq_id). If the player is not new, and is attempting to impersonate someone else, terminate the connection immediately by calling the downstream out() with no arguments.

Finally, if there is no player id in the body, then send the output directly downstream. This allows the init_comet middleware to send comet initialization back to the browser.

That could be simpler, but it is much better that what I had before—this and code trying to broadcast the whereabouts of existing game players to new player. Speaking of that bit of functionality, it is now much simpler:
function broadcast_new (app) {
return function () {
var out = this;

return app.call( function listener(obj) {
if (obj && obj.body && obj.body.id) {
for (var id in players) {
out({body: comet_new_player(JSON.stringify(players[id].status))});
}
}
else {
out(obj);
}
return listener;
});
};
}
Similar to store_player, I send the upstream data back downstream to the browser (via out(obj)), unless there is a new player ID in the body (again, this would be set by player_from_querystring). In that case, I walk through each player in the local store and send back downstream the current status of each player.

I am duplicating the logic of performing an action when player data is present in these two (fab) apps. Tomorrow I really ought to DRY that up.


Day #150

No comments:

Post a Comment