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

Tuesday, June 29, 2010

MD5 in Node.js (and Fab.js)

‹prev | My Chain | next›

A quick link in my chain tonight as I try to get session IDs MD5 encoded in my (fab) game. Fab.js proper does not have any crypto support, so I need to drop down to node.js.

Happily node.js has a well documented crypto module (then again, everything in node.js is well documented). If I have var data="asdf", I can md5 sign it with:
var data = "asdf";
var crypto = require('crypto');
crypto.createHash('md5').update(data).digest("hex");
// '912ec803b2ce49e4a541068d495ab570'
In my (fab) game, I want to set a new, signed session ID if one is not already present:
function player_from_querystring() {
var out = this;
return function(head) {
if (head && head.url && head.url.search) {
var uniq_id;
if (/MYFABID=(\w+)/.test(head.headers.cookie)) {
uniq_id = RegExp.$1;
}
else {
var crypto = require('crypto');
uniq_id = crypto.
createHash('md5').
update("" + (new Date()).getTime()).
digest("hex");
}


// Other player setup...
}
else {
out();
}
};
}
There is no need for salt or SHA-1. This is a kid's game. I am simply taking reasonable steps to ensure uniq session IDs. If my kids can break this, more power to them.

That gives my nice md5 session IDs:



Actually, I have no need for crypto anywhere else, so I can compress that to:

uniq_id = require('crypto').
createHash('md5').
update("" + (new Date()).getTime()).
digest("hex");
That will have to suffice as a good stopping point for tonight. Up tomorrow, possibly some more refactoring under test.

Day #149

Monday, June 28, 2010

Cookies in Fab.js

‹prev | My Chain | next›

Up today, using cookies in fab.js to better maintain state in my (fab) game.

I will be working on the comet initialization section of the game:
  ( /^\/comet_view/ )
( broadcast_new )
( init_comet )
( player_from_querystring )
This stack of fab apps pulls the player out from the querystring, initializes a comet session for that player, then broadcasts the new player to everyone in the game. First up, I add a time string as the unique session ID for the player in player_from_querystring:
function player_from_querystring() {
var out = this;
return function(head) {
if (head && head.url && head.url.search) {
var uniq_id = "" + (new Date()).getTime();
if (/MYFABID=(\w+)/.test(head.headers.cookie)) {
uniq_id = RegExp.$1;
}


var search = head.url.search.substring(1);
var q = require('querystring').parse(search);
var app = out({ body: {id: q.player, x: q.x || 0, y: q.y || 0, uniq_id: uniq_id} });
if ( app ) app();
}
else {
out();
}
};
}
Nothing too special going on there—it is a typical (fab) unary / upstream application. It returns the anonymous function so that it can pull in the header information, primarily for the query string info, but now also for the cookies. Here I am not setting the cookie, just reading it. Fab.js/node.js do not have great bulit-in cookie parsing support, but fortunately I do not need it. I use a simple RegExp to pull out cookies that look like: Cookie: MYFABID=123456789. If the cookie is present, I will use that to uniquely identify the player, otherwise, I will use the current time.

Moving back down the stack (toward the browser), I use the uniq_id to set the player's session ID in cookies:
function init_comet (app) {
return function () {
var out = this;

return app.call( function listener(obj) {
if (obj && obj.body) {
out({ headers: { "Content-type": "text/html",
"Set-Cookie": "MYFABID=" + obj.body.uniq_id } })

({body: "<html><body>\n" })

}
return listener;
});
};
}
Now I am setting the cookie and using it. Last up is the broadcast_new, which is slightly overloaded. In addition to broadcasting the new player to existing game players, it also stores the new player in a local store. The whole point of this exercise was to get this working through a browser reload. Hopefully this will do:

// 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;
});
};
}
I retain the bit about telling the current user about all players in the current room. Also retained is the conditional that only adds players if they are not already in the room. Newly added here is a follow-up conditional that refreshes the comet session if the new comet session is initialized with the same unique identifier with which the player was first assigned upon entering the room.

This means that no one else can impersonate (at least not without hijacking the session) my player. That is a good stopping point for today. Up tomorrow: replacing the unique ID with a md5sum and some cleanup.

Day #148

Sunday, June 27, 2010

The Best Code

‹prev | My Chain | next›

One of the two bugs that I investigated yesterday was that reloading the game page was not refreshing the comet session. The game continued to use the old comet <iframe> even though the browser had closed it.

As a temporary fix, I removed the conditional for establishing new players (i.e. don't add a new player if the player is already registered). That introduces yet another problem: it is now quite easy to impersonate other players.

Today, I would like to get to a point at which browser reloads re-use the player already in the game, but only that player can re-attach a comet <iframe>. I began fiddling with this last night, but the solution towards which I was driving involved a complex server-side md5 signature being stored and sent back to the browser. Fab.js (by way of node.js) can easily support crypto signatures:
var crypto = require('crypto');
var data = "asdf";
crypto.createHash('md5').update(data).digest("hex");
// '912ec803b2ce49e4a541068d495ab570'
The browser would then cache the md5 signature such that it could survive a page reload. I was thinking about storing the signature in the <iframe> URL or investigating whether or not Javascript object state could survive reload. I wasn't sure how, but it was going to be awesome.

A day later, the best code is the code that I didn't write.

Today, I remembered that browsers have been able to store state in cookies for the better part of a lifetime. To set a cookie in fab.js, I ought to be able to Set-Cookie along with the other HTTP headers that I am already setting:
function init_comet (app) {
return function () {
var out = this;

return app.call( function listener(obj) {
if (obj && obj.body) {
out({ headers: { "Content-type": "text/html",
"Set-Cookie": "MYFABID=123456789" } })

({body: "<html><body>\n" })
//...
}
return listener;
});
};
}
At some point, I will replace that dummy session ID with the MD% sum code. For now, I keep it simple and would like to make sure that I really can set cookies. Sadly, when I access this resource, I do not see the cookie being send back to the browser:



Hrm... Checking in curl, I see that the cookie is being sent back:
cstrom@whitefall:~/repos/my_fab_game$ curl -I http://localhost:4011/comet_view?player=foo\&x=250\&y=350
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: MYFABID=123456789
Connection: keep-alive
Transfer-Encoding: chunked
Am I somehow messing up the syntax? I check in Firefox and there I actually see the cookie being sent back and forth. Is Chrome rejecting my cookie for some reason? Is my Set-Cookie too old fashioned in that it is not sending any other attributes?

Nope. Nothing like that, the cookie is being set, but Chrome's resource inspector is simply not showing it. I eventually track down the cookie in the storage inspector:



Chrome is nice, but there are definitely reasons to stick with Firefox/Firebug. So I really do know how to set cookies in fab.js. It took me quite a while to track that down, so I will have to defer actually doing something with those cookies until tomorrow.

Day #147

Saturday, June 26, 2010

Refreshing Comet Sessions in Fab.js

‹prev | My Chain | next›

Today is minor bug day in my (fab) game. First up, my player seem to be hitting a wall well before the bottom of the room. That turns out to be a simple matter of adding the scroll amount to the screen coordinates where I ought to be subtracting:
    var c_x = avatar.attr("cx") +
$(self.avatar.paper.canvas).parent().offset().left +
$(document).scrollLeft()
;

var c_y = avatar.attr("cy") +
$(self.avatar.paper.canvas).parent().offset().top +
$(document).scrollTop()
;
Sigh. I do not believe that I will ever be able to know when to add or subtract browser coordinates. I may forever be doomed to try one, fail, then go with the right way:
    var c_x = avatar.attr("cx") +
$(self.avatar.paper.canvas).parent().offset().left -
$(document).scrollLeft()
;

var c_y = avatar.attr("cy") +
$(self.avatar.paper.canvas).parent().offset().top -
$(document).scrollTop()
;
Next up, I am having trouble reloading the game.

For each player in my (fab) game, the backend keeps track of the comet <iframe> so that it can broadcast changes. We have noticed of late that reloading the game does not reload the comet <iframe>.

Hrm.. I eventually track this down to the code that adds players:
function broadcast_new (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("[broadcast_new] adding: " + new_id);

// Add player to local store
}

}
else {
out(obj);
}
return listener;
});
};
}
The problem, of course, is that the player is already in the local store so it will not re-added when the page is reloaded.

For now, I will remove the conditional. That will make it easy for others to impersonate players, so I will have to come up with another strategy, but I will leave that for another day.


Day #146

Friday, June 25, 2010

No Joy with eventPhase

‹prev | My Chain | next›

I had a bit of a setback last night. I had hoped to be able to send click events to multiple, overlapping players in my (fab) game.

I am able to generate decorated click events, which I hoped to be able to send to all players in the game at a particular coordinate. Unfortunately, I found yesterday that I had to send the event directly to individual players rather than to the room as a whole (and then onto multiple players).

I am going to make one more run at this today. My only hope at this point is that everything so far has been operating in event bubbling mode. I would like for the room to capture the event, which is then sent to the room descendants—the players.

To simulate events, I have been using the three related methods document.createEvent, event.initMouseEvent, and element.dispatchEvent along the lines of:
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window, 1, 0, 0, 50, 450, false, false, false, 0, null);
evt.is_collision = true
room.paper.canvas.dispatchEvent(evt);
The list of arguments to event.initMouseEvent is ludicrously long. Of that list, only one argument (number 2) deals with bubbling or capturing, and it only specifies whether or not bubbling has been halted (event.bubbles).

My only hope is the event.eventPhase attribute. For some reason, that defaults to zero in Chrome:



According to the documentation, it ought to be 1, 2, or 3. I want to see if 1, or Event.CAPTURING_PHASE might work:
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window, 1, 0, 0, 250, 250, false, false, false, 0, null);
evt.is_collision = true // decorate the event to differentiate it from real mouse clicks
evt.eventPhase = Event.CAPTURING_PHASE
Nothing happens, but I am not too surprised, I have not set up a capture listener, which I do by setting the third parameter on addEventListener to true:
  avatar.node.addEventListener("click", function(evt) {
console.debug("here!");
if (evt.is_collision) self.bounce_away();
}, true);
Sadly, that still has no effect.

I ultimately find that setting the eventPhase on the event has no effect. In fact, when I try it in Firefox, I get:
TypeError: setting a property that has only a getter
Sigh. It looks as though it is simply not possible to create capture events. It was fun to explore this stuff, but it looks to be a dead end. I will circle back to where I left off in my (fab) game development tomorrow night.

Day #145

Thursday, June 24, 2010

Sending Artificial Event to Raphael.js Nodes

‹prev | My Chain | next›

Last night, I figured out how to generate coordinate based click events with document.createEvent, event.initMouseEvent, and element.dispatchEvent. It was an interesting experiment, but of no practical use unless I can use it to generate location based events in my (fab) game.

Yesterday, I was able to generate click events to move players about in my (fab) game. Today, I would like to decorate those same events so that players will not follow them (but may respond to collisions).

So I walk through the same document.createEvent / event.initMouseEvent / element.dispatchEvent code, this time adding a boolean attribute to the event:
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window, 1, 0, 0, 50, 450, false, false, false, 0, null);
evt.is_collision = true
room.paper.canvas.dispatchEvent(evt);
That still works (the player moves to 50,450), so let's try ignoring it in the game:
Player.prototype.notify = function(evt) {
if (!evt.is_collision) return null;
switch(evt.type) {
// send events to players
}
};
Running through the same steps with the decorated event, my player now stays put. Nice!

Now, let's see if I can send that event to the player. When I first attach the raphaël.js avatar to the player, I add a click handler to bounce the player:
Player.prototype.attach_avatar = function(avatar) {
var self = this;

avatar.click(function(evt) {
console.debug("here!");
if (evt.is_collision) self.bounce_away();
});


// more initialization
}
Sadly, when I send the event to the room:
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window, 1, 0, 0, 50, 450, false, false, false, 0, null);
evt.is_collision = true
room.paper.canvas.dispatchEvent(evt);
Nothing happens.

Eventually, I figure out that I can get this working if I send the event directly to the player:
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window, 1, 0, 0, 50, 450, false, false, false, 0, null);
evt.is_collision = true
p.avatar.node.dispatchEvent(evt);
Hrm. I'm not quite sure that will work for me. I had hoped the event would bubble up from the room, but it seems the players are lower in the bubbling sense. I will have to ruminate on this.


Day #144

Wednesday, June 23, 2010

Faking Click Events

‹prev | My Chain | next›

Last night I got document.elementFromPoint working, even with scrolling, for detecting collisions in my (fab) game. While I was working through scrollTops, scrollLefts, and offsets, it occurred to me that there might be a better way to detect collisions using something that browsers do best: events. Specifically, if I can generate an event on collision, then I ought to be able to use event bubbling to find all items at a given coordinate.

For tonight, I just want to see if I can generate click events from javascript. I know that I can call click handlers, but I'd like to create a click even at 250,250 on the web browser. In my (fab) game, this will result in a player moving to that coordinate.

Try #1:
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window, 1, 250, 250, 250, 250, false, false, false, 0, null);
window.dispatchEvent(evt);
But nothing happens. Maybe I need to send the event to the canvas element of the raphaël.js paper:
p = player_list.get_player('bob');
p.avatar.paper.canvas.dispatchEvent(evt);
Still nothing!

Ooh! Wait a second. The player did not move on the screen, but the backend recorded an event so maybe something is working.

Ah, fer crying out loud! My player was already at 250,250 in the room. What made me think to send an event to the current location? Even if it worked, the player would not visibly move. So let's try moving to 50,50 instead:
evt.initMouseEvent("click", true, true, window, 1, 50, 50, 50, 50, false, false, false, 0, null);
p.avatar.paper.canvas.dispatchEvent(evt);
Gah! Nothing.

This time, however, I check the event in Chrome's Javascript console and see that the event that I sent is still going to 250,250. I must need to re-initialize the event with document.createEvent(). So...
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window, 1, 50, 50, 50, 50, false, false, false, 0, null);
p.avatar.paper.canvas.dispatchEvent(evt);
Success! Finally my player moves.

I spend a little more time fiddle with the screenX/screenY and clientX/clientY coordinates in the evt.initMouseEvent call (arguments 6/7 and 8/9 respectively). Eventually I boil it down to simply needing to send clientX/clientY to make things work as desired:
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window, 1, 0, 0, 50, 450, false, false, false, 0, null);
p.avatar.paper.canvas.dispatchEvent(evt);
Since I am sending clientX/clientY, I do need to account for page offsets. Luckily, I learned all about that last night.

Up tomorrow, I will see if I can decorate click events so that real events will result in player movement, but others can be used to detect collisions.


Day #143

Tuesday, June 22, 2010

Scrolling with jQuery (and Raphaël.js)

‹prev | My Chain | next›

Up tonight, a quick review of what I did last. I got things working last night, but I slapped it together without much thought.

The solution involved document.elementFromPoint, which finds the topmost (in a z-index sense) element at a given point. Like most folks, I glossed over the documentation when I first started using this method and missed this bit:
pixels relative to the upper-left corner of the document's containing window or frame
I did not expect document.elementFromPoint to account for the margin between the raphaël.js "paper" and the top-left of the document. So I had been manually correcting for that margin:
    var c_el = document.elementFromPoint(avatar.attr("cx") + 8,
avatar.attr("cy") + 8);
Instead of manually adding the 8 pixels, I would like to do it a more repeatable fashion (i.e. so I can change the CSS without needing to change my code). Happily, I am using jQuery in addition to raphaël.js in my (fab) game, so I can use offset(), which returns the coordinates of an element relative to the document (just what I need!).

To translate into game coordinates, I need the offset of the room. Trying to get a jQuery matched set for a raphaël.js paper / SVG element results in an empty matched set. Thankfully (and for that reason), I wrapped the room inside a <div>. Thus I can get access to the offset like so:
$(paper.canvas).parent().offset()
Getting back to my poor reading of the document.elementFromPoint documentation, it had not occurred to me that I needed to account for scrolling. As the documentation states, and as I found from bitter experience last night, I do.

I lucked into the scrollTop() method yesterday, but that turns out to be another of jQuery's magical functions that does exactly what I need it to. It, along with its sister method, scrollLeft(), return the offset of an element relative to the browser viewport. Thus, to get my element at the point of collision in my (fab) game, I simply need:
    var c_x = avatar.attr("cx") +
$(self.avatar.paper.canvas).parent().offset().left +
$(document).scrollLeft();

var c_y = avatar.attr("cy") +
$(self.avatar.paper.canvas).parent().offset().top +
$(document).scrollTop();

var c_el = document.elementFromPoint(c_x, c_y);
Nice to know that I hit pretty close to the mark last night. Even better to know that I hit the mark tonight.

Day #142

Monday, June 21, 2010

Not Obvious to Me

‹prev | My Chain | next›

Tonight, I'd like to see if I can figure out what is going on with chatting in my (fab) game. Chatting works, but immediately after I say something, collisions no longer work:

video

My collision detection is rather convoluted at this point:
    var c_el = document.elementFromPoint(avatar.attr("cx") + 8,
avatar.attr("cy") + 8);

if (!self.initial_walk &&
!self.mid_bounce &&
c_el != self.avatar.node &&
c_el != self.avatar.paper.bottom.node) {
// console.debug(c_el);
// console.debug(self.avatar);
self.stop();
self.bounce_away();
}
So I have a few things to check. In the javascript console, I look my player up in the player_list and test those attributes:



Hrm... Both of those attributes are false, as they ought to be. So why is the player not colliding?

I try approaching the problem from the opposite direction. I am trying to find the colliding element with document.elementFromPoint. I add a player to the room at x-y 250,350 then try document.elementFromPoint(250 + 8, 350 +8) (the 8 is the offset of the room from the top and left of the document). It works before I chat, but fails after I chat.

I try manually simulating the chat event, but it always works. Until I use the chat built into the game. It's maddening.

I don't actually solve the problem until I record the video above. Until then I only scrolled the chat dialog into view when I wanted to chat. When I recorded the view above (at first), I scrolled into view prior to recording... and it always failed to collide players properly. At first I blamed the video recording software, but ultimately I realized the common theme to failure was scrolling. Whenever I chat, I have been scrolling the chat into the view port, which messes with the offset.

I am able to get things working—even with scrolling through judicious use of jQuery's offsetTop and offsetLeft:
    var c_el = document.elementFromPoint(avatar.attr("cx") + 8  - $(document).scrollleft(),
avatar.attr("cy") + 8 - $(document).scrollTop());
Wish I would have thought of that sooner...

Day #141

Sunday, June 20, 2010

Refactoring Under Test with Fab.js

‹prev | My Chain | next›

I refactored and DRYed up the fab.js code the in the backend of my (fab) game last night. I ended up with a robust upstream app, named unary_try, that invoked a callback to broadcast state changes to all players in the game then closed the downstream connection.

It seems to work well enough, but I have no tests—something that I ought to have given that I have 3 resources using it. It could also stand a little clean-up—something that tests would help as well.

The first test is easy enough—if the request contains only headers, then I should ignore it:
    function
terminatesIfHead() {
var ret;
function downstream (obj) { ret = obj; }

var upstream_listener = unary_try.call(downstream);
upstream_listener({head: "foo"});

this(typeof(ret) == "undefined");
}
Here, I am simply hoping that the downstream (close to the browser) app is called with an empty argument. That test passes straight out. For something a bit more complex, I try to verify that the callback is called with the body POSTed downstream:
  var request = { body: "foo-bar" },
body = null,
callback = function() {body = arguments[0];};

function
invokesCallbackIfBody() {
function downstream () { }

var upstream_listener = unary_try.call(downstream);
upstream_listener(request);

this(body == "foo-bar");
}
That test fails however. It takes me a sad amount of time and way too many puts statements to realize that I am not supplying the callback to the unary_try, but I ultimately get the test passing with:
    function
invokesCallbackIfBody() {
function downstream () { }

var upstream_listener = unary_try(callback).call(downstream);
upstream_listener(request);

this(body == "foo-bar");
},
In addition to these two tests, I add three others verifying that:
invokesCallbackIfBody: true
terminatesEvenIfBody: true
terminatesCleanlyOnException: true
terminatesIfHead: true
terminatesIfEmptyRequest: true
Done. 5 passed, 0 failed.
With that, I believe that I have fully covered the features of unary_try. Now I am ready to refactor it:
function unary_try(fn) {
return function() {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {
try {
if ( obj && obj.body ) {
fn.call(obj.body, obj.body);
}
} catch (e) {
puts("[unary_try] error type: " + e.type +
", message: " + e.message);
}
finally {
out();
}
}
return listener;
};
};
}
I do not believe that I need the initial conditional checking for the POST value (covered by the terminatesIfEmptyRequest test). The finally block ought to cover it. So I refactor to:
function unary_try(fn) {
return function() {
var out = this;
return function listener( obj ) {
try {
if ( obj && obj.body ) {
fn.call(obj.body, obj.body);
}
}
catch (e) {
puts("[unary_try] error: " + e.message +
' (' + e.type + ')');
}
finally {
out();
}
return listener;
};
};
}
My tests still pass I have eliminated all but one conditional (and that one is mostly a convenience):
Running 5 tests...
invokesCallbackIfBody: true
terminatesEvenIfBody: true
terminatesCleanlyOnException: true
terminatesIfHead: true
terminatesIfEmptyRequest: true
Done. 5 passed, 0 failed.
That's a good stopping point for tonight. Up tomorrow: investigating a bug in collision detection when chatting.

Day #140

Saturday, June 19, 2010

DRYing up Fab.js Apps

‹prev | My Chain | next›

Tonight, I hope to DRY up the move/bounce/chat handlers in the fab.js part of my (fab) game. The code in question:
  // TODO: These broadcast resources are not DRY
( /move/ )
( function() {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {

broadcast(comet_walk_player(obj.body));
update_player_status(JSON.parse(""+obj.body));
}
return listener;
};
} )


( /chat/ )
( function() {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {

var msg = JSON.parse(obj.body.toString());
msg.body = msg.say.substr(0,100);
broadcast(comet_player_say(JSON.stringify(msg)));
}
return listener;
};
} )


( /bounce/ )
( function() {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {

try {
update_player_status(JSON.parse(""+obj.body));
broadcast(comet_bounce_player(obj.body));
} catch (e) {
puts("[bounce_handler] error type: " + e.type +
", message: " + e.message);
}
finally {
out();
}
}
return listener;
};
} )
Each of these unary (non-middleware) fab apps updates the player status in the local store then broadcasts it to the other players in the room. The exact details of the status attribute being stored / broadcast varies from resource to resource, but the overall structure of the unary apps is nearly identical (as the initial comment notes).

The only difference in structure between the three is the try-catch-finally chain in the bounce handler. I added that last night and would like to add it to the other two, minus the duplication, of course.

First up, I factor the try-catch-finally version out into a library app. Until I can think of a better name, I call it unary_try:
var puts = require( "sys" ).puts;

function unary_try(fn) {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {
try {
if ( obj.body ) {
fn.call(obj, obj);
}
} catch (e) {
puts("[unary_try] error type: " + e.type +
", message: " + e.message);
}
finally {
out();
}
}
return listener;
};
}

exports.app = unary_try;
I ought to be able to reduce the bounce handler to this:
  ( /bounce/ )
( unary_try( function () {
update_player_status(JSON.parse(""+this.body));
broadcast(comet_bounce_player(this.body));
}
) )
Sending that anonymous function to the unary_try app establishes a callback that is invoked when the downstream (i.e. web browser) POSTs data. Using the call method and sending the POST contents as the first object binds the this variable to the POST body inside the anonymous function. In this case, it is a cheap way to keep the anonymous functions as small as possible.

When I run the game, however, it exits immediately. This is an indication that I have forgotten a function wrapper somewhere and, indeed, I have done so here. Specifically, the unary_try app is not a fab app—it takes the callback as its only argument, but it has to return a unary app to fit properly in the chain:
function unary_try(fn) {
return function() {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {
try {
if ( obj.body ) {
fn.call(obj, obj);
}
} catch (e) {
puts("[unary_try] error type: " + e.type +
", message: " + e.message);
}
finally {
out();
}
}
return listener;
};
};
}
Ah, much better, now the game starts up and collision / bounce messages are broadcast. Before applying this to the other two resources, I switch the fn context to the POSTs body rather than the entire request—it is the only thing that I care about in each of the scenarios, so I might as well be as specific as possible:
//...
try {
if ( obj && obj.body ) {
fn.call(obj.body, obj.body);
}
}
//...
With that, I can DRY up my other two broadcast resource nicely:
  ( /move/ )
( unary_try( function () {
update_player_status(JSON.parse(""+this));
broadcast(comet_walk_player(this));
} ) )

( /chat/ )
( unary_try( function () {
var msg = JSON.parse(this.toString());
msg.body = msg.say.substr(0,100);
broadcast(comet_player_say(JSON.stringify(msg)));
} ) )

( /bounce/ )
( unary_try( function () {
update_player_status(JSON.parse(""+this));
broadcast(comet_bounce_player(this));
} ) )
Much better! I do not have any tests written against this code and I think that I might be able to simplify it a bit. I think I will pick up there tomorrow.

Day #139

Friday, June 18, 2010

Error Handling in Fab.js

‹prev | My Chain | next›

In the course of last night's work, I inadvertently crashed the fab.js backend with the following backtrace:
broadcasting to 3 players: <script type="text/javascript">window.parent.player_list.bounce_player({"id":"bo)</script>

undefined:1

^
SyntaxError: Unexpected token ILLEGAL
at Object.parse (native)
at listener (/home/cstrom/repos/my_fab_game/game.js:50:39)
at IncomingMessage.<anonymous> (/home/cstrom/.node_libraries/fab/apps/fab.nodejs.js:29:36)
at IncomingMessage.emit (events:25:26)
at HTTPParser.onBody (http:98:23)
at Stream.ondata (http:745:22)
at IOWatcher.callback (net:373:31)
at node.js:204:9
I am unsure of the cause of the clipped JSON string ("{"id":"bo"), but it hardly matters. This could happen due to bug, network failure or someone hacking around. Regardless of the cause, I need to handle it better than crashing the backend.

To prevent crash, I make use of a technique that I have rarely, if ever, used in Javscript: try-catch exception handling. The JSON parser is throwing me a perfectly catch-able exception here, so let's see if I can catch it:
  ( /bounce/ )
( function() {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {
try {
broadcast(comet_bounce_player(obj.body));
update_player_status(JSON.parse(""+obj.body));
} catch (e) {
puts("[bounce_handler] error type: " + e.type +
", message: " + e.message);
}
finally {
out();
}
}
return listener;
};
} )
If I have done this correctly, sending invalid JSON should result in an error message being printed out, but no crash. For good measure, I add a finally block to the try-catch chain so that I tell the downstream browser that I have nothing left to say (true regardless of success).

So I fire up the game a send bogus JSON:
cstrom@whitefall:~/repos/my_fab_game$ curl http://localhost:4011/bounce -d '{id:"foo'
Switching over to the backend, I see:
broadcasting to 3 players: 

[bounce_handler] error type:unexpected_token, message: Unexpected token ILLEGAL
Nice! I get the error message output for which I hoped, but... I still seem to be broadcasting the bogus JSON to all connected players. Checking the Javascript console of one of the 3 attached players, I see that I am, indeed, sending that bogus JSON:



Happily, that is easy enough to prevent: just fail early by putting the JSON parse first in the try block:
//...
try {
update_player_status(JSON.parse(""+obj.body));
broadcast(comet_bounce_player(obj.body));
} catch (e) {
//...
With that, I have a much more robust client and server—all thanks to a simple try-catch. Up tomorrow: more fab.js error handling as I try to generalize this to other upstream apps that are handling game actions.

Day #138

Thursday, June 17, 2010

Colliding with the Raphaël.js Room (delayed reaction)

‹prev | My Chain | next›

While trying to refactor my (fab) game a bit, I run into an odd situation in which the players seem to be continuously colliding with a phantom something. The odd thing is that the players are not in collision at first, but wind up in this state after idling for 20 minutes or so.

Grrr... I hate these hard to reproduce bugs.

To start investigation, I log as much information as possible during collision of raphaël.js elements:
    var c_el = document.elementFromPoint(avatar.attr("cx") + 8,
avatar.attr("cy") + 8);

if (!self.initial_walk &&
!self.mid_bounce &&
c_el != self.avatar.node &&
c_el != self.avatar.paper) {
console.debug(c_el);
console.debug(self.avatar);

self.stop();
self.bounce_away();
}
And then I wait.

A nice feature of the Javascript console in Chrome (and webkit?) is that when you debug an object to the console as I have done, it sends the object itself to the console and not simply a string representation. This means that I can interact with the object to see what it really is.

What I find is that the player is colliding with a rectangle object that is the exact size of the room. In fact, it is colliding with the room itself. For 20 minutes, my player does not collide with the room because I explicitly ignore it:
//...
c_el != self.avatar.paper) {
//...
And then, for some strange reason, it no longer recognizes the room as being the avatar's room ("paper" in raphaël.js parlance).

Thanks again to the Javascript console, I find the rectangle object in the room's "bottom" object:



Testing for collision with that element works at first and continues to work for the duration of my player's time in the room:
    if (!self.initial_walk &&
!self.mid_bounce &&
c_el != self.avatar.node &&
c_el != self.avatar.paper.bottom.node) {
self.stop();
self.bounce_away();
}
Unfortunately, since it takes such a long time for this bug to manifest itself, I cannot be 100% sure that I have isolated the real cause. I will keep an eye on this for the next couple of days.

Day #137

Wednesday, June 16, 2010

Collision Broadcast with Fab.js

‹prev | My Chain | next›

Man, collisions are hard. I have spent the better part of a week getting them in place, working in the right direction, and working properly when first entering the room. I am not even attempting to do this on the backend (where it probably ought to take place). And yet I am still not done.

Up tonight, I need to get collisions to broadcast. Right now, my player bounces on collisions, but other players do not see the collisions:

video

Currently I have a bounce_away client-side method governing this behavior:
Player.prototype.bounce_away = function() {
this.mid_bounce = true;
var x = this.x - 2*Player.radius*this.direction.x,
y = this.y - 2*Player.radius*this.direction.y;

var self = this;
this.avatar.animate({cx: x, cy: y}, 500, "bounce",
function(){self.mid_bounce = false;});
setTimeout(function(){ self.mid_bounce = false; }, 1000);

this.x = x;
this.y = y;
};
I am going to need to broadcast a "bounce to", so first up, I factor out the destination bounce into its own method:
Player.prototype.bounce_away = function() {
var x = this.x - 2*Player.radius*this.direction.x,
y = this.y - 2*Player.radius*this.direction.y;

this.bounce_to(x, y);
};

Player.prototype.bounce_to = function(x, y) {
this.mid_bounce = true;

var self = this;
this.avatar.animate({cx: x, cy: y}, 500, "bounce",
function(){self.mid_bounce = false;});
setTimeout(function(){ self.mid_bounce = false; }, 1000);

this.x = x;
this.y = y;
};
With that, I can tap into the notification system that I already have in place to communicate to the fab.js backend:
Player.prototype.bounce_away = function() {
var x = this.x - 2*Player.radius*this.direction.x,
y = this.y - 2*Player.radius*this.direction.y;

this.notify_server('bounce', {id: this.id, x: x, y: y});
this.bounce_to(x, y);
};
I need a (fab) app that will respond to that bounce. I copy & paste the skeleton from the "player walk" resource and wind up with this:
  ( /bounce/ )
( function() {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {
broadcast(comet_bounce_player(obj.body));
update_player_status(JSON.parse(""+obj.body));
}
return listener;
};
} )
(the copy & paste is duly noted with a TODO to DRY up the code)

The broadcast of the bounce player interfaces with the PlayerList in all attached clients to tell the player to bounce. That method needs to look up the player and tell it to bounce using the newly minted bounce_to method:
PlayerList.prototype.bounce_player = function(attrs) {
var player = this.get_player(attrs.id);
if (player) {
player.stop();
player.bounce_to(attrs.x, attrs.y);
}
};
And, just like that, I have bouncy collisions in both my window and all other windows:

video

Nice. That was almost too easy. I do believe that I will pick up tomorrow trying to find what new bug I have introduced.

Day #136

Tuesday, June 15, 2010

Initial Bounce

‹prev | My Chain | next›

Up tonight in my (fab) game: fixing a bug in the client-side, raphaël.js collision code.

When players first enter a room, they always start in the middle. If a player is already in the room but has yet to move when I enter, then I am already in collision. I would expect one of two things to happen in this situation: (1) I bounce away immediately or (2) I move through the other player as if it is not there. Right now, (2) happens, but subsequent collisions fail to register.

I have a hard time tracking this one down because I clearly stop() the animation before bouncing away from the collision:
    var c_el = document.elementFromPoint(avatar.attr("cx") + 8,
avatar.attr("cy") + 8);

if (!self.mid_bounce &&
c_el != self.avatar.node &&
c_el != self.avatar.paper) {
self.stop();
self.bounce_away();
}
And in bouce_away(), I set mid_bounce so that collision detection does not fire until the bounce is done:
Player.prototype.bounce_away = function() {
this.mid_bounce = true;
var x = this.x - 2*Player.radius*this.direction.x,
y = this.y - 2*Player.radius*this.direction.y;

var self = this;
this.avatar.animate({cx: x, cy: y}, 500, "bounce",
function(){self.mid_bounce = false;});

this.x = x;
this.y = y;
};
My problem is that the finish bounce callback never fires leaving the player mid-bounce eternally.

Initially, I try to resolve this problem in the collision detection by moving the mid_bounce boolean in there. This has no effect, however, and I eventually conclude that the onAnimation callback in raphaël fires before the animation starts. This causes a problem because I stop the animation and try to bounce, but then immediately start the first frame of my animation. This has the effect of completely ignoring the bounce away method, aside from setting mid_bounce to be perpetually true.

My resolution is a bit of a hack. The bounce animation never concludes, but I need to be sure that mid_bounce gets set to false. So I add a simple setTimeout that will fire after the bounce:
Player.prototype.bounce_away = function() {
this.mid_bounce = true;
var x = this.x - 2*Player.radius*this.direction.x,
y = this.y - 2*Player.radius*this.direction.y;

var self = this;
this.avatar.animate({cx: x, cy: y}, 500, "bounce",
function(){self.mid_bounce = false;});
setTimeout(function(){ self.mid_bounce = false; }, 1000);

this.x = x;
this.y = y;
};
With that, I can enter the room, move about, and still get my bouncy collisions:

video

Day #135

Monday, June 14, 2010

Reflective Collisions

‹prev | My Chain | next›

I added initial "bouncy" collisions into my (fab) game yesterday. The "bounce" easing baked into raphaël.js made it fairly easy. One thing missing, however, was bouncing from any direction. The collision bouncing only worked when colliding into something from the right.

To get this working, I need to record the direction in which the player is walking. Bah! I have grown to hate calculating angles from browser coordinates. Calculating angles is hard enough, but add in the complexity of doing so when the y-coordinates increase down the page and, well, bah!

I am already calculating the distance a player is walking along with the differences in x and y position:
Player.prototype.walk_to = function(x, y) {
var p = "M"+ Math.floor(this.x) + " " + Math.floor(this.y) +
" L" + x + " " + y;

var x_diff = x - this.x;
var y_diff = y - this.y;
var distance = Math.sqrt(x_diff * x_diff + y_diff * y_diff);


var time = Player.time_to_max_walk * ( distance / Player.max_walk );
this.avatar.animateAlong(p, time);

this.x = x;
this.y = y;
};
With that information, I can calculate the the x and y changes if the distance were a single pixel:
Player.prototype.walk_to = function(x, y) {
var p = "M"+ Math.floor(this.x) + " " + Math.floor(this.y) +
" L" + x + " " + y;

var x_diff = x - this.x;
var y_diff = y - this.y;
var distance = Math.sqrt(x_diff * x_diff + y_diff * y_diff);
this.direction = {x: x_diff/distance, y: y_diff/distance};

var time = Player.time_to_max_walk * ( distance / Player.max_walk );
this.avatar.animateAlong(p, time);

this.x = x;
this.y = y;
};
Hopefully, that will be enough to reverse direction on collision. Could it be as easy as moving in the opposite direction with a multiplier that assures that I move beyond an avatar's boundary?
Player.prototype.bounce_away = function() {
this.mid_bounce = true;
var x = this.x - 2*Player.radius*this.direction.x,
y = this.y - 2*Player.radius*this.direction.y;


var self = this;
this.avatar.animate({cx: x, cy: y}, 500, "bounce",
function(){self.mid_bounce = false;});

this.x = x;
this.y = y;
};
Indeed, that does the trick. I have nice bouncy collisions no matter the direction of approach:

video

Best of all: no atan2 calculations required!

Day #134

Sunday, June 13, 2010

Bouncy Collisions

‹prev | My Chain | next›

I left off last night with collision detection in my (fab) game stopping the player in motion mid-collision. The problem with that is that the player continues to overlap the collision item making it impossible to move.

First up, I add a call to a newly defined bounce_away method in the collision detection code:
//...
var c_el = document.elementFromPoint(avatar.attr("cx") + 8,
avatar.attr("cy") + 8);

if (c_el != self.avatar.node &&
c_el != self.avatar.paper) {
console.debug(c_el);
console.debug(self.avatar);
self.stop();
self.bounce_away();
}
//...
I can then define the bounce_away() method such that it jumps twice the radius of a player's avatar away from the collision. Eventually, I need to bounce away in the opposite direction of the collision. For now, I just jump to the right:
Player.prototype.bounce_away = function() {
this.mid_bounce = true;
var x = this.x + 2*Player.radius,
y = this.y;

var self = this;
this.avatar.animate({cx: x, cy: y}, 500, "bounce");

this.x = x;
this.y = y;
};
I use the "bounce" easement baked into raphaël.js to hopefully give it a legitimate bouncing feel.

Unfortunately, nothing happens on collision. What's worse, the browser completely freezes. The bounce_away method is fighting against the collision detection code that is trying stop movement. The player tries to bounce away, the collision detection code stops the player and then tries to bounce away again, and so on. Bleh.

So I disable the collision detection code if the player is mid-bounce:
    var c_el = document.elementFromPoint(avatar.attr("cx") + 8,
avatar.attr("cy") + 8);

if (!self.mid_bounce &&
c_el != self.avatar.node &&
c_el != self.avatar.paper) {
console.debug(c_el);
console.debug(self.avatar);
self.stop();
self.bounce_away();
}
});
I then signal the end of the bounce in bounce_away with the callback provided by raphaël.js:
Player.prototype.bounce_away = function() {
this.mid_bounce = true;
var x = this.x + 2*Player.radius,
y = this.y;

var self = this;
this.avatar.animate({cx: x, cy: y}, 500, "bounce",
function(){self.mid_bounce = false;});

this.x = x;
this.y = y;
};
With that, I have some nice, bouncy collisions:

video

Day #133

Saturday, June 12, 2010

Collision Detection in Raphaël.js

‹prev | My Chain | next›

I spend a bit of time fixing up bits and pieces of my (fab) game earlier. Now I would like to start investigating collision detection in the game.

For now, I would like to explore collision detection in raphaël.js. Ultimately, I think collision detection has to take place in the backend, but I am just playing with frontend detection for now.

After looking through the raphaël.js source code for a while with no luck, I resort to Google. Ultimately, I come across a post on Stackoverflow that suggests looking at document.elementFromPoint.

I manually specify the offsetTop and offsetLeft (as 8 / 8) and get this working with:
//...
avatar.onAnimation(function(){
var c_el = document.elementFromPoint(avatar.attr("cx") + 8,
avatar.attr("cy") + 8);

console.debug(c_el);
console.debug(self.avatar);
if (c_el != self.avatar.node &&
c_el != self.avatar.paper) {
console.debug("collision!");
this.stop();
}
//..
I grab the collision element and store it in c_el. I then check to make sure that the colliding element is not me and not the raphaël.js paper. If it is some other element, then I stop my walk.

With that, the player can move about the room, but, if I cross paths with another player, I stop immediately:
video

Unfortunately, the player is now stuck. Any attempt to move results in an immediate collision. Ah well, something to work tomorrow.

Day #132

Friday, June 11, 2010

Functional ReOrg

‹prev | My Chain | next›

Last night, I explored techniques for troubleshooting Javascript / functional problems. Tonight I would like to develop strategies for resolving them. Mind you, I have little interest in making the bug go away. I want to address the underlying problems (if any) that proved fertile ground for the bug in the first place.

Let's see if back-of-the-napkin drawings might help. This is what happens to me when I enter a room in my (fab) game when another player ("Bob") is already there:



The left side of that diagram describes the initialization process of me as a Player, the Room object, and the PlayerList. Since everything in raphaël.js is drawn on a "paper" (the Room), the Room has to draw the player's avatar, which then needs to be attached to the player. Finally, label shadowing and other callbacks need to be added to my characters.

That is a bit complex, but overall OK (I think). The room creates the drawable / raphaël element, which is then added to the Player object. It might seem a little weird that the PlayerList is responsible for telling the Room to draw the player and telling the Player about its raphaël representation, but that is its responsibility. The PlayerList keeps track of players currently in the room, adding and removing them as needed.

Regardless, I think my woes originate on the other side of that diagram. The right side starts with a call to an <iframe> comet session, which uses the PlayerList to animate other users in the room. The most obvious problem that I see in here is that the PlayerList might be accessed before it is even defined (if the <iframe> returns really, really fast). That is just silly.

The other problem on the right side of the diagram is that it tells the PlayerList to instruct players to walk. It does this by first stopping the player, storing current location information, calculating where to go next and then animating the player along the way. Quite aside from the potential race condition on the player's position between left side / right side, I have to recognize that it is silly to be telling players to walk at all. This is when I first log in. Existing players do not need to walk to the right spot—they are already there.

So, I refactor the code such that the <iframe> / comet stuff that adds players comes after all of the initialization (especially after the PlayerList is initialized) and so that no walking calculations are done until a player actually moves. This makes for a much cleaner path for drawing players:



With that, my bug is resolved (drawing certain players in the wrong location). More importantly, I have put myself in a much better position moving forward.

Day #131

Thursday, June 10, 2010

Functional Troubleshooting

‹prev | My Chain | next›

Up tonight, I need to fix a bug when multiple players are in the room. When I first log in as a new player, my avatar has wandered off:



If I click to move in the room, my player zips up from the bottom right corner. Oddly enough, the first player to enter the room, and hence the first player broadcast by the server, is drawn correctly. All other players wander off. Again, oddly, their labels, which should be tied to their positions are where they are supposed to be.

After much searching I track the problem down to the Player.walk_to client side method. I suspect that the distance calculation that I added recently is throwing off the timing of various callbacks, resulting in an incorrect value:
Player.prototype.walk_to = function(x, y) {
var p = "M"+ Math.floor(this.x) + " " + Math.floor(this.y) +
" L" + x + " " + y;

var x_diff = x - this.x;
var y_diff = y - this.y;
var distance = Math.sqrt(x_diff * x_diff + y_diff * y_diff);
var time = Player.time_to_max_walk * ( distance / Player.max_walk );
this.avatar.animateAlong(p, time);

this.x = x;
this.y = y;
};
To try to prove it, I add some console debugging to see what the Player thinks its x-y coordinates are and what the raphaël.js circle / avatar thinks the center x-y coordinates are:
Player.prototype.walk_to = function(x, y) {
var p = "M"+ Math.floor(this.x) + " " + Math.floor(this.y) +
" L" + x + " " + y;

var x_diff = x - this.x;
var y_diff = y - this.y;
var distance = Math.sqrt(x_diff * x_diff + y_diff * y_diff);
var time = Player.time_to_max_walk * ( distance / Player.max_walk );
console.debug("id: " + this.id + ", x : " + x + ", y: " + y);
console.debug("id: " + this.id + ", x : " + this.x + ", y: " + this.y);
console.debug("id: " + this.id + ", cx : " + this.avatar.attrs.cx + ", cy: " + this.avatar.attrs.cy);
console.debug("id: " + this.id + ", x_diff : " + x_diff + ", y_diff: " + y_diff);
console.debug("walk: " + p + ", distance: " + distance + ", time: " + time);
this.avatar.animateAlong(p, time);

this.x = x;
this.y = y;
};
Unfortunately, this tells me nothing of use. The Player and raphaël coordinates always match up.

I struggle with for a long time (the sheer number of debug statements already in there should be a indicator for that) before realizing that the coordinates might align when the code is first evaluated, but could change very shortly thereafter. So I add a setTimeout to evaluate the same debugging after 100 milliseconds:
Player.prototype.walk_to = function(x, y) {
var p = "M"+ Math.floor(this.x) + " " + Math.floor(this.y) +
" L" + x + " " + y;

var x_diff = x - this.x;
var y_diff = y - this.y;
var distance = Math.sqrt(x_diff * x_diff + y_diff * y_diff);
var time = Player.time_to_max_walk * ( distance / Player.max_walk );
console.debug("id: " + this.id + ", x : " + x + ", y: " + y);
console.debug("id: " + this.id + ", x : " + this.x + ", y: " + this.y);
console.debug("id: " + this.id + ", cx : " + this.avatar.attrs.cx + ", cy: " + this.avatar.attrs.cy);
console.debug("id: " + this.id + ", x_diff : " + x_diff + ", y_diff: " + y_diff);
console.debug("walk: " + p + ", distance: " + distance + ", time: " + time);
this.avatar.animateAlong(p, time);

var self = this;
setTimeout(function() {
console.debug("id: " + self.id + ", x : " + x + ", y: " + y);
console.debug("id: " + self.id + ", x : " + self.x + ", y: " + self.y);
console.debug("id: " + self.id + ", cx : " + self.avatar.attrs.cx + ", cy: " + self.avatar.attrs.cy);
console.debug("id: " + self.id + ", x_diff : " + x_diff + ", y_diff: " + y_diff);
console.debug("walk: " + p + ", distance: " + distance + ", time: " + time);
}, 100);

this.x = x;
this.y = y;
};
With that, I finally find my discrepancy. That is one of the troubles with heavily functional code, sometimes problems are not immediately evident. I do think I will add the setTimeout debug statement to my bag of Javascript tricks.

I devise a simple hack to resolve this issue, but I think a bit of refactoring is in order to make the code less susceptible to woes like this in the future. I will pick up with that tomorrow.

Day #130

Wednesday, June 9, 2010

String Literals in Fab.js (for the sake of JSON.parse)

‹prev | My Chain | next›

Up tonight, a bit of hacking on fab.js proper. Last night I encountered this stack trace:
cstrom@whitefall:~/repos/my_fab_game$ ./game.js
/home/cstrom/repos/my_fab_game/game.js:38
say: obj.body.say.substr(0,100)
^
TypeError: Cannot call method 'substr' of undefined
at listener (/home/cstrom/repos/my_fab_game/game.js:38:35)
at listener (/home/cstrom/.node_libraries/fab/apps/fab.map.js:11:19)
at IncomingMessage.<anonymous> (/home/cstrom/.node_libraries/fab/apps/fab.nodejs.js:29:36)
at IncomingMessage.emit (events:25:26)
at HTTPParser.onBody (http:98:23)
at Stream.ondata (http:745:22)
at IOWatcher.callback (net:373:31)
at node.js:204:9
I solved the problem by reworking my (fab) app, but I am thinking that maybe I need to resolve this in fab.js itself. After a bit of legwork, I decide the best point of attack in that stack is fab.nodejs.js:
     if ( inbound ) {
request
.addListener( "end", inbound )
.addListener( "data", function( body ) {
if ( inbound ) inbound = inbound({ body: body });
})
}
Inside fab.nodejs.js, I add some debugging code to see what the body is:
//...
if ( inbound ) {
request
.addListener( "end", inbound )
.addListener( "data", function( body ) {
puts("[fab.node.js] data body isa " + typeof(body));
puts("[fab.node.js] data body toString(): " + body.toString());
for (var prop in body) {
puts("[fab.node.js] " + prop + ": " + body[prop]);
}

if ( inbound ) inbound = inbound({ body: body.toString ? body.toString() : body });
})
}
//...
After sending some dummy chat data to my (fab) game, I find this debug output:
cstrom@whitefall:~/repos/my_fab_game$ ./game.js
[fab.node.js] data body isa object
[fab.node.js] data body toString(): {"id":"foo", "say":"hi"}
[fab.node.js] 0: 123
[fab.node.js] 1: 34
[fab.node.js] 2: 105
[fab.node.js] 3: 100
...
As expected (after last night's investigation), the body is not a string literal, but rather is a String object. The later does not work well with things like JSON.parse, so I need to come up with a way of ensuring that body a string literal.

I do investigate up the stack a bit, but am frustrated to find that it should already be a string literal. From http.js in node.js:
      parser.incoming.emit('data', b.slice(start, start+len));
The slice method ought to produce a string literal:
cstrom@whitefall:~/repos/my_fab_game$ rlwrap node-repl
Welcome to the Node.js REPL.
Enter ECMAScript at the prompt.
Tip 1: Use 'rlwrap node-repl' for a better interface
Tip 2: Type Control-D to exit.
Type '.help' for options.
node> str = new String("foo")
{ '0': 'f', '1': 'o', '2': 'o' }
node> typeof str
'object'
node> str.slice(1,4)
'oo'
node> typeof str.slice(1,4)
'string'
But in practice, sadly, that is not what is emitted by the onBody callback. Ah, well. I can resolve the issue similarly to last night: by calling toString() on the body in fab.nodejs.js:
    if ( inbound ) {
request
.addListener( "end", inbound )
.addListener( "data", function( body ) {
if ( inbound ) inbound = inbound({ body: body.toString ? body.toString() : body });
})
}
Now I can be sure that any data POSTed to any of my (fab) apps will be string literals and thus JSON.parse friendly. That's a good stopping point for tonight.

Day #129

Tuesday, June 8, 2010

Fab.js and String Objects

‹prev | My Chain | next›

While messing about with my (fab) game today, I notice that the chat system is completely broken. Sigh.

By completely broken, when I POST a chat message, I get an "empty" response from the fab.js backend:
cstrom@whitefall:~/repos/my_fab_game$ curl http://localhost:4011/chat -i -d '{"id":"foo", "say":"hi"}'
curl: (52) Empty reply from server
Looking at the backend, I find that it has crashed completely:
...
cstrom@whitefall:~/repos/my_fab_game$ ./game.js
/home/cstrom/repos/my_fab_game/game.js:34
broadcast(comet_player_say(obj.body.substr(0,100)));
^
TypeError: Object {"id":"foo", "say":"hi"} has no method 'substr'
at listener (/home/cstrom/repos/my_fab_game/game.js:34:49)
at IncomingMessage.<anonymous> (/home/cstrom/.node_libraries/fab/apps/fab.nodejs.js:29:36)
at IncomingMessage.emit (events:25:26)
at HTTPParser.onBody (http:98:23)
at Stream.ondata (http:745:22)
at IOWatcher.callback (net:373:31)
at node.js:204:9
Like I said...

Sigh.

I added the substr to limit the chat message to 100 characters, but didn't notice that that the body of the message was a JSON string rather than the chat message itself. Since it is a JSON string, I need to parse the JSON into a Javascript object, limit the chat message in that object to 100 characters, then re-stringify it to be broadcast to the game:
  ( /chat/ )
( function() {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {
var msg = JSON.parse(obj.body);
msg.body = msg.say.substr(0,100);
broadcast(comet_player_say(JSON.stringify(msg)));

}
return listener;
};
} )
Sadly, when I restart and POST to the chat resource, I still get an empty resource:
cstrom@whitefall:~/repos/my_fab_game$ curl http://localhost:4011/chat -i -d '{"id":"foo", "say":"hi"}'
curl: (52) Empty reply from server
The backend is still crashing, this time with a different message:
cstrom@whitefall:~/repos/my_fab_game$ ./game.js
/home/cstrom/repos/my_fab_game/game.js:34
var msg = JSON.parse(obj.body);
^
Hunh? I am not even sure what that means there is not even an error message. Let's have a look at the obj.body just before it crashes:
//...
else if ( obj.body ) {
for (var prop in obj.body) {
puts(prop + ": " + obj.body[prop]);
}
var msg = JSON.parse(obj.body);
msg.body = msg.say.substr(0,100);
broadcast(comet_player_say(JSON.stringify(msg)));
}
//...
After I crash the server, I find this in the output:
cstrom@whitefall:~/repos/my_fab_game$ ./game.js
0: 123
1: 34
2: 105
3: 100
4: 34
...
22: 34
23: 125
length: 24
binarySlice: function binarySlice() { [native code] }
asciiSlice: function asciiSlice() { [native code] }
...
toString: function (encoding, start, stop) {
encoding = (encoding || 'utf8').toLowerCase();
...
Oh man! That is a Javascript String object, not a string literal. Happily, there is a toString() method in that list of properties. I can use that to resolve the problem:
  ( /chat/ )
( function() {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {
var msg = JSON.parse(obj.body.toString());
msg.body = msg.say.substr(0,100);
broadcast(comet_player_say(JSON.stringify(msg)));
}
return listener;
};
} )
Indeed that does resolve the problem. I now have my chat system working again.

That Javascript String object has me a bit worried, though. I know that the fab.js source uses typeof obj == "string" checks in places. Those will fail for String objects (typeof would return "object").

To test this out, I convert the fab app upstream of the chat resource into a binary app. Then, I can delegate the responsibility of converting the supplied JSON string into a JSON object to the built-in fab.parse app:
  ( /chat/ )
(
function(app) {
return function() {
var out = this;
return app.call( function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {
var msg = {
id: obj.body.id,
say: obj.body.say.substr(0,100)
};
broadcast(comet_player_say(JSON.stringify(msg)));
}
return listener;
});
};
} )
(fab.parse)
(fab.echo)
When I access the chat resource now, I get an error similar to the unary version of the chat fab app:
cstrom@whitefall:~/repos/my_fab_game$ ./game.js
/home/cstrom/repos/my_fab_game/game.js:38
say: obj.body.say.substr(0,100)
^
TypeError: Cannot call method 'substr' of undefined
at listener (/home/cstrom/repos/my_fab_game/game.js:38:35)
at listener (/home/cstrom/.node_libraries/fab/apps/fab.map.js:11:19)
at IncomingMessage.<anonymous> (/home/cstrom/.node_libraries/fab/apps/fab.nodejs.js:29:36)
at IncomingMessage.emit (events:25:26)
at HTTPParser.onBody (http:98:23)
at Stream.ondata (http:745:22)
at IOWatcher.callback (net:373:31)
at node.js:204:9
If I alter the fab.parse.js app from:
exports.app = fab.map( function( obj ) {
var body = obj.body;
if ( typeof body == "string" ) obj.body = JSON.parse( body );
return obj;
})
To make use of toString() (which works on string literals and String objects):
exports.app = fab.map( function( obj ) {
var body = obj.body;
if ( body && body.toString ) obj.body = JSON.parse( body.toString() );
return obj;
})
Then my chat server works again.

I suspect that recent node.js changes have started sending String objects and string literals interchangeably. I will investigate a bit more tomorrow then address the issue in the remaining built-in fab apps.

Day #128