Saturday, July 31, 2010

No, Actually Faye Loves Chrome on Linux

‹prev | My Chain | next›

I love being part of the developer community, even if only a small part. I love that there are smart, interesting people doing smart interesting things out there. Not only do I get to hear about what they are doing, but I get to play with the toys that they find fascinating. And, best of all, when I have problems, these same passionate people are actually willing—eager even—to give me pointers as to what I am doing wrong.

For the life of me, I could not get faye work with the latest Chrome. Faye is a pub/sub framework with backends written in both ruby and node.js (I was using the latter). Finally, I submitted a issue on the github issue tracker for faye. Less than two hours later, I had a response and, better still, an explanation.

The latest development versions of Chrome (I am using 6.0.472.11) support a newer version of the websockets working draft, specifically draft 76. Faye does not support anything that is not in a stable, in-production browser. Chrome 6 is a developer series, so I am out of luck. Except I am not because James Coglan has already started work on draft 76 in the ws-draft76 branch of faye.

Let's see if I can resolve my woes on that branch. First up, I need to install jake (it's like make, but for Javascript) to build the package for npm:
cstrom@whitefall:~/repos$ gem install jake
Successfully installed methodphitamine-1.0.0
Successfully installed eventful-1.0.0
Successfully installed oyster-0.9.4
Successfully installed packr-3.1.0
Successfully installed jake-1.0.1
5 gems installed
Then, I clone faye:
cstrom@whitefall:~/repos$ git clone http://github.com/jcoglan/faye.git
Initialized empty Git repository in /home/cstrom/repos/faye/.git/
remote: Counting objects: 2774, done.
remote: Compressing objects: 100% (1105/1105), done.
remote: Total 2774 (delta 1714), reused 2624 (delta 1572)
Receiving objects: 100% (2774/2774), 484.85 KiB | 161 KiB/s, done.
Resolving deltas: 100% (1714/1714), done.
And switch to the ws-draft76 branch:
cstrom@whitefall:~/repos/faye$ gba
* master
remotes/origin/0.3.x
remotes/origin/HEAD -> origin/master
remotes/origin/clients
remotes/origin/cometd-compat
remotes/origin/daemon
remotes/origin/extensions
remotes/origin/jetty-test
remotes/origin/master
remotes/origin/moot-debug
remotes/origin/multiple-subscriptions
remotes/origin/node
remotes/origin/ssl
remotes/origin/websockets
remotes/origin/ws-draft76
cstrom@whitefall:~/repos/faye$ git co ws-draft76
Branch ws-draft76 set up to track remote branch ws-draft76 from origin.
Switched to a new branch 'ws-draft76'
Finally, I build the package with jake:
cstrom@whitefall:~/repos/faye$ jake
/home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require': no such file to load -- em-http (LoadError)
from /home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
from /home/cstrom/repos/faye/lib/faye/network/transport.rb:1
from /home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
from /home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
from ./lib/faye.rb:34
from ./lib/faye.rb:20:in `each'
from ./lib/faye.rb:20
from /home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
from /home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
from /home/cstrom/repos/faye/Jakefile:1
from /home/cstrom/.rvm/gems/ruby-1.8.7-p249/gems/jake-1.0.1/bin/../lib/jake/build.rb:27:in `load'
from /home/cstrom/.rvm/gems/ruby-1.8.7-p249/gems/jake-1.0.1/bin/../lib/jake/build.rb:27:in `initialize'
from /home/cstrom/.rvm/gems/ruby-1.8.7-p249/gems/jake-1.0.1/bin/jake:33:in `new'
from /home/cstrom/.rvm/gems/ruby-1.8.7-p249/gems/jake-1.0.1/bin/jake:33
from /home/cstrom/.rvm/gems/ruby-1.8.7-p249/bin/jake:19:in `load'
from /home/cstrom/.rvm/gems/ruby-1.8.7-p249/bin/jake:19
Hrm... I must need to install another gem:
cstrom@whitefall:~/repos/faye$ gem install em-http
ERROR: could not find gem em-http locally or in a repository
cstrom@whitefall:~/repos/faye$ gem install em-http-request
Building native extensions. This could take a while...
Successfully installed em-http-request-0.2.10
1 gem installed
But even with that installed, I am still getting missing gem errors. Eventually , I read the instructions at the fay github page and:
cstrom@whitefall:~/repos/faye$ gem install hoe eventmachine em-http-request rack thin json
With that, I can successfully run jake, though now it barfs on the Jakefile:
cstrom@whitefall:~/repos/faye$ jake
core src /build/core.js UP-TO-DATE
core min /build/core-min.js 15 kB
faye-browser src /build/faye-browser.js 56 kB
faye-browser min /build/faye-browser-min.js 23 kB
faye-node src /build/faye-node.js 57 kB
faye-node min /build/faye-node-min.js 30 kB
/home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/fileutils.rb:1201:in `stat': No such file or directory - README.txt (Errno::ENOENT)
from /home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/fileutils.rb:1201:in `lstat'
from /home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/fileutils.rb:1179:in `stat'
from /home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/fileutils.rb:1261:in `copy_file'
from /home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/fileutils.rb:463:in `copy_file'
from /home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/fileutils.rb:383:in `cp'
from /home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/fileutils.rb:1396:in `fu_each_src_dest'
from /home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/fileutils.rb:1412:in `fu_each_src_dest0'
from /home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/fileutils.rb:1394:in `fu_each_src_dest'
from /home/cstrom/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/1.8/fileutils.rb:382:in `cp'
from /home/cstrom/repos/faye/Jakefile:13
...
To workaround this, I simply copy the README.rdoc to README.txt (they seem to be identical when compared to the version installed via npm). Now, I can build and install the ws-draft76 version of faye:
cstrom@whitefall:~/repos/faye$ jake
core src /build/core.js 30 kB
core min /build/core-min.js 15 kB
faye-browser src /build/faye-browser.js UP-TO-DATE
faye-browser min /build/faye-browser-min.js UP-TO-DATE
faye-node src /build/faye-node.js 57 kB
faye-node min /build/faye-node-min.js 30 kB
cstrom@whitefall:~/repos/faye$ cd build/
cstrom@whitefall:~/repos/faye/build$ ls
faye-browser.js faye-browser-min.js faye-node.js History.txt package.json README.txt
cstrom@whitefall:~/repos/faye/build$ npm install .
The "ini" module will be removed in future versions of Node, please extract it into your own code.
npm configfile /home/cstrom/.npmrc
npm sudo false
npm cli [ 'install', '.' ]
npm install pkg .
...
npm activate faye 0.5.1
npm readJson /home/cstrom/.node_libraries/.npm/faye/active/package/package.json
npm readJson /home/cstrom/.node_libraries/.npm/faye/0.5.1/package/package.json
npm testEngine required: node ">=0.1.96"
npm build Success: faye-0.5.1
npm ok
Yay!

With that, all that is left it to try out my faye backend:

video

The backend (written in fab.js) and frontend code is still the identical to what I wrote the other night. The subscriptions to the faye channels simply console.debug any message received. As can be seen in the video, I can publish and receive messages from Firefox, Chrome 6.x, and, from another machine, using an older Chrome (5.x). Thus I know that my (fab) faye backend works with long polling, web sockets draft 76 and draft 75.

I stop there for the night, quite content. A big thanks to James Coglan not only for writing faye in the first place, but also for doing an outstanding job supporting it!


Day #181

Friday, July 30, 2010

Faye Hates Chrome on Linux

‹prev | My Chain | next›

Last night, I ran into a bit of a problem with faye, the simple pub/sub messaging framework. My trouble may have been related to me quick diving into getting it working with fab.js, although that actually seemed to work relatively well.

Failure began when I tried to pub/sub messages from the Chromium browser. I am running on linux and, last night, I was running version 6.0.472.0-r53024. Upgrading to 6.0.472.11-r53709 today has no effect. It always works in Firefox and, oddly enough, works in older Chromium (5.x).

The HTML + Javascript code that I am using:
<html>
<head>
<title>Testing Faye/Fab</title>
<script type="text/javascript" src="/faye.js"></script>
<script type="text/javascript">
var client = new Faye.Client('/faye');
client.subscribe('/foo', function(message) {console.debug(message)});

</script></head>

<body>
<h1>Testing Faye/Fab</h1>

</body>
</html>
I pull in the faye source, instantiate a client object, then subscribe to the /foo channel with a callback that prints any channel messages to the console.

In Firefox/Firebug, I see this when messages are broadcast:



But, in Chrome, I see nothing:



I am at something of a loss for how to proceed at this point. It works everywhere but latest Chrome on linux. As I mentioned, I am doing this against a fab.js server. Maybe there is something in there that does not like Linux/Chrome. Yeah, I am grasping at straws, but, if nothing else, I ought to be able to reproduce this in the simplest possible way.

So I create a pure node.js / faye server, app.js:
ar Faye   = require('faye'),
server = new Faye.NodeAdapter({mount: '/faye'});

server.listen(8000);
I run that with the latest node.js on my londo server rather than my laptop to eliminate Linux/Chrome localhost weirdness:
cstrom@londo:~/tmp/faye-test$ node --version
v0.1.102
cstrom@londo:~/tmp/faye-test$ node app.js

I then create a local file that accesses this resource on the remote server (faye can go cross domain, so this ought to work just fine):
<html>
<head>
<title>Testing Faye/Fab</title>
<script type="text/javascript" src="http://londo:8000/faye.js"></script>
<script type="text/javascript">
var client = new Faye.Client('http://londo:8000/faye');
client.subscribe('/foo', function(message) {console.debug(message)});
</script>
</head>

<body>
<h1>Testing Faye/Fab</h1>

</body>
</html>
And again, this still works in Firefox/Firebug, but nothing in Chrome. At least I have eliminated my fab.js stuff as a culprit, but I still cannot get this working with Chrome.

The last thing I try tonight is inspecting the packets with tcpdump. For Chrome, I see:
cstrom@whitefall:~/repos/faye_test$ sudo tcpdump -i eth1  -n -s 0 -X port 8000
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth1, link-type EN10MB (Ethernet), capture size 65535 bytes
...
23:52:25.597289 IP londo.8000 > 192.168.1.150.48884: Flags [P.], seq 23516:23913, ack 853, win 62, options [nop,nop,TS val 279488801 ecr 12072325], length 397
0x0000: 4500 01c1 cb48 4000 3f06 eafd c0a8 010a E....H@.?.......
0x0010: c0a8 0196 1f40 bef4 23a5 be0b 5f9c 909b .....@..#..._...
0x0020: 8018 003e 2dfc 0000 0101 080a 10a8 a921 ...>-..........!
0x0030: 00b8 3585 4854 5450 2f31 2e31 2032 3030 ..5.HTTP/1.1.200
0x0040: 204f 4b0d 0a43 6f6e 7465 6e74 2d54 7970 .OK..Content-Typ
0x0050: 653a 2074 6578 742f 6a61 7661 7363 7269 e:.text/javascri
0x0060: 7074 0d0a 436f 6e6e 6563 7469 6f6e 3a20 pt..Connection:.
0x0070: 6b65 6570 2d61 6c69 7665 0d0a 5472 616e keep-alive..Tran
0x0080: 7366 6572 2d45 6e63 6f64 696e 673a 2063 sfer-Encoding:.c
0x0090: 6875 6e6b 6564 0d0a 0d0a 3132 300d 0a5f hunked....120.._
0x00a0: 5f6a 736f 6e70 315f 5f28 5b7b 2269 6422 _jsonp1__([{"id"
0x00b0: 3a22 6e75 646c 6e33 6776 7263 397a 3139 :"nudln3gvrc9z19
0x00c0: 7376 3336 6331 7767 3933 797a 222c 2263 sv36c1wg93yz","c
0x00d0: 6861 6e6e 656c 223a 222f 6d65 7461 2f68 hannel":"/meta/h
0x00e0: 616e 6473 6861 6b65 222c 2273 7563 6365 andshake","succe
0x00f0: 7373 6675 6c22 3a74 7275 652c 2276 6572 ssful":true,"ver
0x0100: 7369 6f6e 223a 2231 2e30 222c 2273 7570 sion":"1.0","sup
0x0110: 706f 7274 6564 436f 6e6e 6563 7469 6f6e portedConnection
0x0120: 5479 7065 7322 3a5b 226c 6f6e 672d 706f Types":["long-po
0x0130: 6c6c 696e 6722 2c22 6361 6c6c 6261 636b lling","callback
0x0140: 2d70 6f6c 6c69 6e67 222c 2277 6562 736f -polling","webso
0x0150: 636b 6574 225d 2c22 636c 6965 6e74 4964 cket"],"clientId
0x0160: 223a 226d 6730 756b 7131 326c 7831 6b6f ":"mg0ukq12lx1ko
0x0170: 3138 6763 6272 6331 767a 7232 7a6f 222c 18gcbrc1vzr2zo",
0x0180: 2261 6476 6963 6522 3a7b 2272 6563 6f6e "advice":{"recon
0x0190: 6e65 6374 223a 2272 6574 7279 222c 2269 nect":"retry","i
0x01a0: 6e74 6572 7661 6c22 3a30 2c22 7469 6d65 nterval":0,"time
0x01b0: 6f75 7422 3a36 3030 3030 7d7d 5d29 3b0d out":60000}}]);.
0x01c0: 0a .
23:52:25.597354 IP 192.168.1.150.48884 > londo.8000: Flags [.], ack 23913, win 906, options [nop,nop,TS val 12072326 ecr 279488801], length 0
0x0000: 4500 0034 b88f 4000 4006 fe43 c0a8 0196 E..4..@.@..C....
0x0010: c0a8 010a bef4 1f40 5f9c 909b 23a5 bf98 .......@_...#...
0x0020: 8010 038a 4d90 0000 0101 080a 00b8 3586 ....M.........5.
0x0030: 10a8 a921 ...!

...

23:52:52.288511 IP 192.168.1.150.32916 > londo.8000: Flags [P.], seq 1:192, ack 1, win 92, options [nop,nop,TS val 12078999 ecr 279491470], length 191
0x0000: 4500 00f3 5a45 4000 4006 5bcf c0a8 0196 E...ZE@.@.[.....
0x0010: c0a8 010a 8094 1f40 7989 15d7 3c22 9ae6 .......@y...<"..
0x0020: 8018 005c b842 0000 0101 080a 00b8 4f97 ...\.B........O.
0x0030: 10a8 b38e 4745 5420 2f66 6179 6520 4854 ....GET./faye.HT
0x0040: 5450 2f31 2e31 0d0a 5570 6772 6164 653a TP/1.1..Upgrade:
0x0050: 2057 6562 536f 636b 6574 0d0a 436f 6e6e .WebSocket..Conn
0x0060: 6563 7469 6f6e 3a20 5570 6772 6164 650d ection:.Upgrade.
0x0070: 0a48 6f73 743a 206c 6f6e 646f 3a38 3030 .Host:.londo:800
0x0080: 300d 0a4f 7269 6769 6e3a 206e 756c 6c0d 0..Origin:.null.
0x0090: 0a53 6563 2d57 6562 536f 636b 6574 2d4b .Sec-WebSocket-K
0x00a0: 6579 313a 2031 3734 756f 3220 3739 2b59 ey1:.174uo2.79+Y
0x00b0: 4d20 2020 6020 207a 2020 3478 4c65 204a M...`..z..4xLe.J
0x00c0: 3820 5633 300d 0a53 6563 2d57 6562 536f 8.V30..Sec-WebSo
0x00d0: 636b 6574 2d4b 6579 323a 2035 3734 2032 cket-Key2:.574.2
0x00e0: 3136 3720 4e36 380d 0a0d 0a04 6641 d419 167.N68.....fA..
0x00f0: d2e7 ad ...

...

23:52:52.290286 IP londo.8000 > 192.168.1.150.32916: Flags [P.], seq 1:45, ack 192, win 54, options [nop,nop,TS val 279491470 ecr 12078999], length 44
0x0000: 4500 0060 2969 4000 3f06 8e3e c0a8 010a E..`)i@.?..>....
0x0010: c0a8 0196 1f40 8094 3c22 9ae6 7989 1696 .....@..<"..y...
0x0020: 8018 0036 31cf 0000 0101 080a 10a8 b38e ...61...........
0x0030: 00b8 4f97 4854 5450 2f31 2e31 2031 3031 ..O.HTTP/1.1.101
0x0040: 2057 6562 2053 6f63 6b65 7420 5072 6f74 .Web.Socket.Prot
0x0050: 6f63 6f6c 2048 616e 6473 6861 6b65 0d0a ocol.Handshake..

...

23:52:52.291472 IP londo.8000 > 192.168.1.150.32916: Flags [P.], seq 45:154, ack 192, win 54, options [nop,nop,TS val 279491470 ecr 12078999], length 109
0x0000: 4500 00a1 296a 4000 3f06 8dfc c0a8 010a E...)j@.?.......
0x0010: c0a8 0196 1f40 8094 3c22 9b12 7989 1696 .....@..<"..y...
0x0020: 8018 0036 06e4 0000 0101 080a 10a8 b38e ...6............
0x0030: 00b8 4f97 5570 6772 6164 653a 2057 6562 ..O.Upgrade:.Web
0x0040: 536f 636b 6574 0d0a 436f 6e6e 6563 7469 Socket..Connecti
0x0050: 6f6e 3a20 5570 6772 6164 650d 0a57 6562 on:.Upgrade..Web
0x0060: 536f 636b 6574 2d4f 7269 6769 6e3a 206e Socket-Origin:.n
0x0070: 756c 6c0d 0a57 6562 536f 636b 6574 2d4c ull..WebSocket-L
0x0080: 6f63 6174 696f 6e3a 2077 733a 2f2f 6c6f ocation:.ws://lo
0x0090: 6e64 6f3a 3830 3030 2f66 6179 650d 0a0d ndo:8000/faye...
For Firefox, I do not see any of the websocket connections—it seems to be using only long polling to do its thing.

At this point, I think it is time to open an issue on the github tracker for faye—maybe someone else has some insights that might help. Unless there is an obvious solution, I will likely dig through the faye source code tomorrow to see what I can see. If nothing else, it might be nice to have a way to force long polling rather than attempting websockets—especially with Chrome+Linux.

Day #180

Thursday, July 29, 2010

Fab Faye

‹prev | My Chain | next›

Communication from server to client in my (fab) game has been done, so far, over comet. Comet is really easy to do in fab.js, so it was a natural approach to take.

I am interested today in seeing if faye might be a better solution in some respects. The documentation says that faye is installable via npm, but when I try a simple install, I get:
cstrom@whitefall:~/repos/faye_test$ npm install faye
The "ini" module will be removed in future versions of Node, please extract it into your own code.
npm configfile /home/cstrom/.npmrc
npm sudo false
npm cli [ 'install', 'faye' ]
npm install pkg faye
npm fetch data faye
npm GET faye
npm install pkg faye

npm ! Error: Tag stable not found for package faye
at F (/home/cstrom/.node_libraries/.npm/npm/0.1.13/package/lib/install.js:136:19)
at /home/cstrom/.node_libraries/.npm/npm/0.1.13/package/lib/install.js:128:16
at IncomingMessage.<anonymous> (/home/cstrom/.node_libraries/.npm/npm/0.1.13/package/lib/utils/registry.js:149:7)
at IncomingMessage.emit (events:42:20)
at HTTPParser.onMessageComplete (http:110:23)
at Client.ondata (http:859:22)
at IOWatcher.callback (net:373:31)
at node.js:204:9

npm failure try running: 'npm help install'
npm failure Report this *entire* log at <http://github.com/isaacs/npm/issues>
npm failure or email it to <npm-@googlegroups.com>
Hmmm... It has been a while since I installed npm, so it could be an old version. Before installing a new version, though, I wonder if it is possible to install with an explicit version:
cstrom@whitefall:~/repos/faye_test$ npm install faye@0.5.1
The "ini" module will be removed in future versions of Node, please extract it into your own code.
npm configfile /home/cstrom/.npmrc
npm sudo false
npm cli [ 'install', 'faye@0.5.1' ]
npm install pkg faye@0.5.1
npm readJson /home/cstrom/.node_libraries/.npm/.cache/faye/0.5.1/package.json
npm GET faye/0.5.1
...
npm build Success: faye-0.5.1
npm ok
Cool. I will worry about npm versions another day, for now, I have faye installed.

Next up is trying to get faye working with fab.js. Since fab.js is built on top of node.js, I reckon it ought to easy to hack it in there. I reckon wrong.

After fiddling with paths for way too long, I give up and create a fab.nodejs.listen_with_faye (fab) app into fab itself. I base it on the built-in (fab) app fab.nodejs.listen, with a few tweaks added for faye:
exports.name      = "fab.nodejs.listen_with_faye";
exports.summary = "Starts a server listening on the port number from the first app, and mounts the second app on it.";
exports.requires = [ "fab.nodejs" ];

var fab = { nodejs: require( "./fab.nodejs" ).app };

var faye = require('faye');

exports.app = function( port ) {
port.call( function( obj ){ port = obj.body; } );

return function( app ) {
var server = require( "http" )
.createServer( fab.nodejs( app ) );

var bayeux = new faye.NodeAdapter({
mount: '/faye',
timeout: 45
});


bayeux.attach(server);

server.listen(port);

return app;
};
};
With that, I can write my standard (fab) skeleton that serves up CSS, Javascript, and static HTML from the stylesheets, javascript, and html directories, respectively:
#!/usr/bin/env node

var puts = require( "sys" ).puts;

with ( require( "fab" ) )

( fab )

( listen_with_faye, 0xFAB )

(/^\/(javascript|stylesheets)/)
(/^\/([-_\w]+)\.(js|css)$/)
(fab.nodejs.fs)
( fab.tmpl, "<%= this[0] %>/<%= this[1] %>.<%= this[2] %>" )
( fab.capture )
(404)

(/^\/([_\w]+)$/)
(fab.nodejs.fs)
( fab.tmpl, "html/<%= this %>.html" )
( fab.capture.at, 0 )

( 404 );
The only change that I make to my standard skeleton is the addition of the listen_with_faye call instead of the vanilla listen.

With that, I am ready to try out my service. First, from the node-repl command line, I subscribe to the "/foo" channel for updates:
cstrom@whitefall:~/repos/faye_test$ 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> var faye = require('faye');
node> var client = new faye.Client('http://localhost:4011/faye');
node> var puts = require( "sys" ).puts;
node> client.subscribe('/foo', function(message) {puts(message)});
...
Then, in a separate node-repl session, I publish a message on that same channel:
node> cstrom@whitefall:~/repos/faye_test$ 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> var faye = require('faye');
node> var client = new faye.Client('http://localhost:4011/faye');
node> client.publish('/foo', "boo");
And, back in my original node-repl, I see the message come through:
node> boo

Yay!

The last thing I try before calling it a night is this in a browser. All I want is the message to appear in the console:
<html>
<head>
<title>Testing Faye/Fab</title>
<script type="text/javascript" src="http://localhost:4011/faye.js"></script>
<script type="text/javascript">
var client = new Faye.Client('http://localhost:4011/faye');
client.subscribe('/foo', function(message) {console.debug(message)});

</script></head>

<body>
<h1>Testing Faye/Fab</h1>

</body>
</html>
What I find is that this works in Firefox, but not in Chrome (no errors, but the callback does not fire). There is no mention of browser compatibility on the faye site, so I am not sure of the cause. I will pick back up tomorrow.


Day #179

Wednesday, July 28, 2010

Always use getBBox() for Raphaël Coordinates

‹prev | My Chain | next›

Up today, I would like to get the onAnimation callback in my (fab) game working with my raphael-animate-frames plugin.

The onAnimation callback is standard raphaël.js that is called after every step of an animation. The animation in my (fab) game does quite a bit. The first thing is move a label (the player's name) along with the player:
  avatar.onAnimation(function(){
console.debug(avatar.attr);
console.debug("x: " + avatar.attr("cx") + ", y: " + avatar.attr("cy"));
self.label.attr({x: avatar.attr("cx"), y: avatar.attr("cy") + Player.shadow_distance});

// more animation code...
}
Unfortunately, that does not work (nor does any of the rest of the animation code). The debug code in there tells me that the x-y coordinates are undefined even though the correct function is being called:
x: undefined, y: undefined
function () {
// delegate to last object in first frame
var obj = this.list[0][this.list[0].length-1];
return obj.attr.apply(obj, arguments);
}
Dang. It would seem that pulling the attr() values off of SVG is not as consistent as I would like. Fortunately, the getBBox() method is available and seems quite a bit more reliable.

If there is one thing that is slowly dawning on me as I do more and more raphaël work, it is that getBBox() is what I need when I want location information. The values that attr() is supposed to have (cx, c, rx, etc) are very much hit-or-miss. I suspect that these are only available if explicitly set when drawing, but that is an investigation for another day. For now, I replace every instance of attr() with a corresponding getBBox() call:
  avatar.onAnimation(function(){
self.label.attr({x: avatar.getBBox().x, y: avatar.getBBox().y + Player.shadow_distance});

// more animation code...
}
One last thing before stopping for the night is that the x-y coordinates from getBBox() are the top-left coordinates. The reason that I had been using cx-cy before was because I wanted the center coordinates for the object.

For that, I add a method, getCenter() to my raphael plugin:
    getCenter: function() {
var bounding_box = this.getBBox();
return {
x: Math.floor(bounding_box.width/2) + bounding_box.x,
y: Math.floor(bounding_box.height/2) + bounding_box.y
};
}
Now I can get more accurate placement in my animations with:
  avatar.onAnimation(function(){
self.label.attr({x: avatar.getCenter().x, y: avatar.getCenter().y + Player.shadow_distance});

// more animation code...
}
I got in trouble adding non-standard methods to my raphaël plugin last night, so I will sleep on the getCenter() method name and implementation before committing it to raphael-svg-frames. Then it will be time to move onto collision detection.


Day #178

Tuesday, July 27, 2010

Careful Naming

‹prev | My Chain | next›


Up today, I would like to add a bit of functionality to my raphael-animate-frames plugin for raphaël.js. I would like to be able to position the frames on the raphaël paper—right now, the frames just show up at the top left of the paper.

In my (fab) game, the frames are being drawn thusly:
  return this
.paper
.svg_frames(frames[0], frames[1])
To position the player, I could expand the svg_frames method to accept x-y coordinates, but that could get messy. I prefer just supplying the frames themselves. But how to move the player?

Raphaël has a translate() method to move objects about the paper. Perhaps I could use that?
  return this
.paper
.svg_frames(frames)
.translate(player.x, player.y);
That'll work. Except it won't. I already have a translate() method in raphael-animate-frames:
    translate: function(x, y, seconds) {
for (var i=0; i<this.list.length; i++) {
this.translate_object(this.list[i], x, y, seconds);
}
this.toggle_frames(seconds);
return this;
}
Aw, dang it. My translate() method has an additional seconds parameter that is not in the core raphaël translate() method. I should have named it differently since it behaves differently.

Ooh. Or I could treat the the seconds parameter as optional. If that parameter is present, then the object moves along a line. If the parameter is not present, then the object moves immediately. That, I can do inside the low-level translate_object() (translates the individual brush strokes in a frame) method:
         // animate along that path
if (ms && ms > 50)
obj.animateAlong(p, ms);
else
obj.translate(x_diff, y_diff);
I had to add that 50 millisecond guard clause to prevent the player from blowing up again. Besides, animating for 50 milliseconds seems silly.

I spend a little more time removing debug statements and general clean-up before calling it a night. Tomorrow, I will pick back up with trying to get collision detection working in my (fab) game working again. That may prove to be difficult with the raphael-animate-frames plugin, but hopefully still doable.

Day #177

Monday, July 26, 2010

Players Shouldn't Explode

‹prev | My Chain | next›

Thanks to my awesome javascript debugging skills, I solved one problem with raphael-animate-frames, my plugin for raphaël.js that animates SVG frames. Today, I hope to get my player walking properly in reaction to clicks. Last I left it, my player was blowing up in response to clicks:

video

In the Player.walk_to() method, I am calling the raphael-svg-frames translate() method to walk the player about:
Player.prototype.walk_to = function(x, y) {
// calculate time
this.avatar.translate(x, y, time);

this.x = x;
this.y = y;
};
So my first tactic in resolving the player blow-out is to try calling translate() in the console:

video

Dang it! I hate it when things works when I expected them to fail.

Next up, I try calling walk_to directly. That still fails. So the error must be something that I am doing in my game code, not in the raphael-svg-frames code.

Through clever use of console.debug, I track this down to the lack of an attrs property on the frames object. The walk_to method is using an invalid this.x valid. This bit of debug code:
Player.prototype.walk_to = function(x, y) {
this.stop();

console.debug("x_o: " + this.x + ", y_o: " + this.y );
console.debug(" x: " + x + ", y: " + 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 );

console.debug("[walk_to] calling translate on avatar " + time);
this.avatar.translate(x, y, time);

this.x = x;
this.y = y;
};
Produces undefined x-y output in the console:
x_o: undefined, y_o: undefined
x: 124, y: 103
That ultimately produces an invalid time local variable. The question is, where does this.x and this.y get set to undefined? Besides walk_to(), that is.

The answer is the stop() method called at the very start of walk_to():
Player.prototype.stop = function () {
this.avatar.stop();

this.x = this.avatar.attrs.cx;
this.y = this.avatar.attrs.cy;
};
The attrs attribute is undefined on the raphael-svg-frames object. It is hard keeping a property in sync with movement. For now, I simply switch to a call to the get bounding box:
Player.prototype.stop = function () {
this.avatar.stop();

this.x = this.avatar.getBBox().x;
this.y = this.avatar.getBBox().y;
};
That finally resolves my exploding player issue:

video

I am happy to stop there for the night. I likely removed bunches of code tracking this down, so tomorrow, I will see about putting it all back together.


Day #176

Sunday, July 25, 2010

Debugging with Chrome's Javascript Console

‹prev | My Chain | next›

I tried using my raphaël.js plugin for animating SVG frames (raphael-animate-frames) without much luck yesterday. I had hoped to pull it into my (fab) game, but it did not go as smoothly as I hoped.

When I click in my (fab) game, the player should walk to the click location—ideally animated as seen in my plugin demo. What happens, however, is that the there is no smooth translation of the player from point A to point B and the player's body parts fly apart:

video

Each body part is moved indivdually by the translate_object() method in raphael-animate-frames. I start my troubleshooting there with some conspicuously placed console.debug statements:
     translate_object: function(frame, x, y, seconds) {
// offset between end coordinates and current location
var x_diff = x - frame[0].getBBox().x;
var y_diff = y - frame[0].getBBox().y;

for (var i=0; i<frame.length; i++) {
var obj = frame[i];
// calculate path starting from absolute coordinates and moving
// relatively from there by the offset
var p = "M " + obj.getBBox().x + " " + obj.getBBox().y +
" l " + x_diff + " " + y_diff;

console.debug(obj);
console.debug(p);

// animate along that path
obj.animateAlong(p, seconds);
};
}
Each obj in the frame is an SVG stroke, drawing a body part. By console.debugging the object itself, I can see the object's properties in Chrome's javascript debugger:



I can also expand attributes on that object to see if they are what I expect them to be. For instance, I want to make sure that the body parts are raphaël objects whose prototype has methods like animateAlong() (used at directly the end of my translate_object method) and attr() (used to get/set raphaël properties):



I have learned quite a lot from just this bit of information. The translate_object method is being called. It is being called for each of the body parts. Despite the added complexity of being used in my (fab) game, the body are still regular raphaël elements—seemingly with all of the correct behaviors still attached. I am also not seeing any javascript errors so, as far as Chrome is concerned, nothing is going wrong.

So why does my player get transported immediately the destination coordinates (rather than gliding there) and why is my player in pieces when it gets there?



I keep thinking about the scene from GalaxyQuest where they are trying to transport the alien pig-thing and it gets turned inside out (and explodes).

Next, I try from a different approach. Everything seems to be getting called as I expect it to, let me try to move an individual part of the player through the Chrome Javascript console. Happily, the console does a nice job of guiding me through this. If I pull myself from the list of players, then type "ava", Chrome lets me know that I probably want the avatar (raphaël's representation of the player in the game):



After accepting the auto-complete suggestion (via right-arrow), I pull the "body" of the player out of the frame. It is the first element in the first frame. I use one of the lineto paths printed to the console and attempt to animateAlong that path:



Hunh? What does that mean?
TypeError: Object #<an Object> has no method 'attr'
Gah! When there are no line numbers, there is an anonymous function. This is one of those occasions that I have to fall back to Firebug. Firebug—still the best.

In firebug, I find that this error traces back to line 113 of the player object in my (fab) game:



Dang it. I thought that I had defined an attr function on my SVG frames object. Clearly I am not:
  avatar.onAnimation(function(){
self.label.attr({x: avatar.attr("cx"), y: avatar.attr("cy") + Player.shadow_distance});
// ...
I solve that via simple delegation, the Javascript way:
    attr: function() {
// delegate to last object in first frame
var obj = this.list[0][this.list[0].length-1];
return obj.attr.apply(obj, arguments);
}
I grab the last element in the frame (the one that will be drawn on top) and apply its attr method. I ensure that the this variable inside that method will refer to that object by passing it in as the first argument to apply. Lastly, I send the arguments to my SVG frames object along to the individual object in the frame.

I could have called attr() directly if I was sure that it would always be called with one or no arguments:
    attr: function() {
// delegate to last object in first frame
var obj = this.list[0][this.list[0].length-1];
return obj.attr(arguments[0]);
}
I could done that, but why not future-proof if it is that easy?

With my apply based attr(), I can animate the individual pieces of the frames, but my player is still blowing up when I try to move it in my (fab) game. I will pick back up trying to solve that problem tomorrow.


Day #175

Saturday, July 24, 2010

First Try at Using My Raphael.js Plugin

‹prev | My Chain | next›


Up today, I would like to get my new plugin for raphaël.js, raphael-animate-frames, working in my (fab) game. I have built the plugin with my (fab) game in mind, but do not think that I have nearly everything needed in there. Let's see.

First up, I branch my (fab) game for animate-frames. There is no sense doing this in master. Even though I am reasonably sure that I can get this working, topic branches are easy:
cstrom@whitefall:~/repos/my_fab_game$ git co -b animate-frames
Switched to a new branch 'animate-frames'
With that I start by adding my new plugin to the HTML source:
...
<script src="/javascript/raphael-min.js" type="application/javascript"></script>
<script src="http://github.com/eee-c/raphael-animate-frames/raw/master/raphael-animate_frames.js"></script>
<script src="/javascript/player.js" type="application/javascript">"></script>
<script src="/javascript/player_list.js" type="application/javascript"></script>
<script src="/javascript/room.js" type="application/javascript"></script>
...
The room is responsible for adding the player's avatar to the room. Currently, that is a circle
Room.prototype.draw_player = function(player, color) {
var c = this.paper.circle(player.x, player.y, 10);
c.attr({fill: color, "fill-opacity": 0.5, stroke: '#000', "stroke-width": 2, "stroke-opacity": 1.0});
return c;
};
I start there by adding my animation:
Room.prototype.draw_player = function(player, color) {
var frames = this.player_frames();

return this
.paper
.svg_frames(frames[0], frames[1]);
};
Hrm... Well, that's kinda ugly. I make a TODO note to allow the svg_frames method to accept an array.

After that, I fix several errors as I try to get the player back in the room. Almost immediately the player shows up, but it quite unresponsive and throws lots of errors. Since the avatar in my (fab) game expects it, I add attrs() and onAnimation methods to the frames object:
// ...
attrs: function () {
this.list[0][0].onAnimation(fn);
},

onAnimation: function(fn) {
this.list[0][0].onAnimation(fn);
}
//...
For both, I use the first element in the first frame, which seems a reasonable thing to assume—the first element is the body or the main part of the animation element. Aside from that, I add a paper property so that the avatar can interact with the raphaël canvas as needed.

I finally get to the point that no more errors occur only to find that my player blows up. Literally:

video

No smooth animation or anything. Rather disheartened, I stop there for the night. I know exactly where I'll pick back up tomorrow.


Day #174

Friday, July 23, 2010

Announce: Raphael Animate Frames

‹prev | My Chain | next›

Up today, I extract my SVG frames animation library for raphaël.js. I create myself a github repository and do the usual local initialization:
cstrom@whitefall:~/repos$ mkdir raphael-animate-frames
cstrom@whitefall:~/repos$ cd raphael-animate-frames/
cstrom@whitefall:~/repos/raphael-animate-frames$ git init
cstrom@whitefall:~/repos/raphael-animate-frames$ touch README
cstrom@whitefall:~/repos/raphael-animate-frames$ git add README
cstrom@whitefall:~/repos/raphael-animate-frames$ git commit -m 'first commit'
cstrom@whitefall:~/repos/raphael-animate-frames$ git remote add origin git@github.com:eee-c/raphael-animate-frames.git
cstrom@whitefall:~/repos/raphael-animate-frames$ git push origin master
With that, I place my animation plugin into raphael-animate_frames.js, commit and push.

To see if I can use it, I remove the plugin from my working copy and replace it with a <script> tag:
<script src="http://github.com/DmitryBaranovskiy/raphael/blob/master/raphael-min.js?raw=true"></script>
<script src="http://github.com/eee-c/raphael-animate-frames/raw/master/raphael-animate_frames.js"></script>
To make sure that I everything is still working, I go to the browser:

video

Yay!

If you are interested in seeing the frames data structure and the final function call, I have set up a github pages demo at http://eee-c.github.com/raphael-animate-frames/demo.html.

At this point, I know that my library works. Tomorrow I will see if I can get it working in my (fab) game.


Day #173

Thursday, July 22, 2010

In Preparation for Extraction

‹prev | My Chain | next›

Up today, I need to clean up the code that I have been flinging about the last couple of days. I ended yesterday with my very own raphaël.js plugin to animate SVG frames as they move about the screen:

video

First up, I need to figure out why I had to add an extra [0] to arguments[0] in frames.add():
Raphael.fn.svg_frames = function() {
var paper = this;

var frames = {
list: [],

add: function() {
for (var i=0; i<arguments[0].length; i++) {
this.list.push(this.draw_object(arguments[0][i]));
};
},

// more object methods
};

frames.add(arguments);
frames.show_frame(frames.list[0]);

return frames;
}
I was in freak refactoring mode yesterday, so I got it working without thinking about why I needed it. Why I need it, is that the arguments object in Javascript is an array-like thing that contains the arguments supplied to the function.

When I call the svg_frames with two frames arguments (e.g. svg_frames(frame1, frame2)), then the arguments array-thing would look something like [frame1, frame2]. When I, in turn, call frames.add(arguments), I am effectively calling frames.add with a single array-like argument: frames.add([frame1, frame2]). Thus, inside frames.add the arguments object looks like: [[frame1, frame2]]. Hence the added [0] on arguments[0].

If I were doing this in Ruby, I would splat the arguments: frames.add(*arguments). In Javascript, I can accomplish something similar with apply:
Raphael.fn.svg_frames = function() {
var paper = this;

var frames = {
list: [],

add: function() {
for (var i=0; i<arguments.length; i++) {
this.list.push(this.draw_object(arguments[i]));
};
},

// more object methods
};

frames.add.apply(frames, arguments);
frames.show_frame(frames.list[0]);

return frames;
}
The apply function is primarily meant to set the this special variable in the invoked function, not to splat arguments. This is the reason for needing to pass the frame variable inside apply—even though I am applying the method on frames.

Next up, some good, old-fashioned code clean-up. The toggle_frames method is just crazy:
function toggle_frames(frames, count) {
if (!count) count=0;
if (count % 2 == 0) {
for (var body_part in frames[0]) {
frames[0][body_part].show();
};
for (var body_part in frames[1]) {
frames[1][body_part].hide();
};
}
else {
for (var body_part in frames[0]) {
frames[0][body_part].hide();
};
for (var body_part in frames[1]) {
frames[1][body_part].show();
};
}
if (count < 10) {
setTimeout(function(){toggle_frames(frames, count+1)}, 500);
}
}
The method calls itself (with a ½ second delay) incrementing the count with each call. If the count is evenly divisible by 2, then the first frame is shown. Otherwise the second frame is shown. Pretty simple explanation. Not so simple code. What is worse is that I have hard-coded the number of frames to 2. What if I want more than 2 frames in my animation?

I get rid of the code duplication by making use of the hide_frame and show_frame methods that I wrote last night. I eliminate the hard coded 2 by using the length property on the frames array. That leaves me in much better shape:
    toggle_frames: function(count) {
var self = this;
if (!count) count=0;

var frames = this.list;
var current_frame = count % frames.length;

for (var i=0; i>frames.length; i++) {
if (i == current_frame) {
this.show_frame(frames[i]);
}
else {
this.hide_frame(frames[i]);
}
}

if (count > 10) {
setTimeout(function(){self.toggle_frames(count+1)}, 500);
}
}
Last up tonight, a bit of sad work. I had originally built each frame as a Javascript object with keys describing body parts:
    draw_object: function(attr_list) {
var self = this;
var memo = {};
attr_list.forEach(function(attrs) {
memo[attrs.label] = self.draw_part(attrs);
});
return memo;
}
I did that so that I could use the animate raphaël.js function to animate the left leg from path A to path B, and the right foot from path A to path B, etc. Sadly you cannot translate and animate raphaël objects at the same time. Thus there is no reason to associate body parts with a key. Defining a frame as a simple array of SVG paths+attributes is sufficient and simpler:
    draw_object: function(attr_list) {
var objects = [];
for (var i=0; i<attr_list.length; i++) {
objects.push(this.draw_part(attr_list[i]))
};
return objects;
}
I need to update several other places that had expected the frames to be an object rather than an array, but, with that done, I believe that my SVG frames plugin for raphaël.js is ready for use. Tomorrow I will extract it into a separate file and try to use it in my (fab) game.


Day #172

Wednesday, July 21, 2010

My Very Own Raphaël.js Plugin

‹prev | My Chain | next›

Today, I would like to convert my work of the past few days into a raphaël.js function. As of yesterday, I have a set of functions that can take an array of SVG objects (manually copied from Inkscape) and cycle through them as the objects move about the screen:

video

I would like to add onto Raphael so that I can do something like:
var standing = { ... };
var walking = { ... };

var frames = paper.svg_frames(standing, walking);

// Animate by cycling through the frames as the
// object moves to x-y 100,100
frames.translate(100, 100, 5*1000);
I spend a good deal of time re-working code from the past couple of days into this structure:
Raphael.fn.svg_frames = function() {
var paper = this;

var frames = {
list: [],

add: function() { ... },

draw_part: function(attrs) { ... },

draw_object: function(attr_list) { ... },

attrs_from_string: function(str) { ... },

show_frame: function(frame) { ... },

hide_frame: function(frame) { ... },

translate: function(x, y, seconds) { ... },

toggle_frames: function(count) { ... },

translate_object: function(obj, x, y, seconds) { ... }

};

frames.add(arguments);
frames.show_frame(frames.list[0]);

return frames;
}
As the raphaël documentations suggests, I add my new svg_frames function onto Raphael.fn. That will allow it to be called from an instance of raphaël paper. Since I am converting from a purely functional approach to a frames object, I spend most of my time removing the list argument that many functions took (it is now an attribute of frames) and adding var self = this as appropriate:
    toggle_frames: function(count) {
var self = this;
var frames = this.list;

if (!count) count=0;

// toggle code here

if (count < 10) {
setTimeout(function(){self.toggle_frames(count+1)}, 500);
}
}
Aside from minor tweaks along those lines, I have very little else needed to change. Just like that, I can move my animations around the raphaël paper by calling a function directly on the paper:
var standing = { ... };
var walking = { ... };

var paper = Raphael("container", 500, 500);

paper
.svg_frames(standing, walking);
.translate(100, 100, 5*1000);
I still have some refactoring to do in the code itself, but I am pleased that it was so easy to get this done today.


Day #171

Tuesday, July 20, 2010

Animating Grouped Raphaël.js Objects

‹prev | My Chain | next›

Last night, I was a bit disappointed to find that I could not use raphaël.js to animate my game players as they moved about the room. The second of the two animations, the player moving or the player animation as it moves, cancels out the first.

I would like to be able to do this in my (fab) game, so I drop down to old-fashioned frame animations. I take my two SVG frames (standing and walking) and pass them to the build_animation() function:
var frames = build_animation(standing, walking);
The build_animation() function works through each SVG description of a frame (using yesterday's draw_object()):
function build_animation() {
var frames = [];
for (var i=0; i<arguments.length; i++) {
frames.push(draw_object(arguments[i]));
};
for (var body_part in frames[0]) {
frames[0][body_part].show();
};
return frames;
}
It shows the first frame—all frames are hidden by draw_object()—then returns the frames.

To move, I need to move both frames so that I can toggle visibility along the way:
var frames = build_animation(standing, walking);
translate_frames(frames, 100, 100, 5*1000);
That function simply iterates over each frame and calls yesterday's translate_object on each:
function translate_frames(frames, x, y, seconds) {
for (var i=0; i<frames.length; i++) {
translate_object(frames[i], x, y, seconds);
}
}
Before animating the player as it moves, I make sure that it does move:

video

Nice!

To animate the player as it walks, I add a call to a new toggle_frames() function at the bottom of translate_frames():
function translate_frames(frames, x, y, seconds) {
for (var i=0; i<frames.length; i++) {
translate_object(frames[i], x, y, seconds);
}
toggle_frames(frames); }
The toggle_frames() function hides the first frame / shows the second frame every other call:
function toggle_frames(frames, count) {
if (!count) count=0;
if (count % 2 == 0) {
for (var body_part in frames[0]) {
frames[0][body_part].show();
};
for (var body_part in frames[1]) {
frames[1][body_part].hide();
};
}
else {
for (var body_part in frames[0]) {
frames[0][body_part].hide();
};
for (var body_part in frames[1]) {
frames[1][body_part].show();
};
}
if (count < 10) {
setTimeout(function(){toggle_frames(frames, count+1)}, 500);
}
}
There is some definite room for DRYing up that code. For now, I just want to make sure it works. At the very end of that function, I call the function again after a half second delay. That will keep calling itself until the tenth time it has been called as specified by the count argument.

With that, I get my animated walk:

video

Happy! Tomorrow, I will clean this up a tad.

Day #170

Monday, July 19, 2010

Combining Raphaël.js Animations

‹prev | My Chain | next›

Yesterday, I got a simple animation of a player walking working in raphaël.js:

video

I have all of the parts of the player moving simultaneously to simulate the walking motion. What I would like to do today is move the player across the screen while continuing the walking motion. I am not even sure if this is possible, but there is one way to find out...

For the walking animation, I am building up walking and standing data structures, then running them through two functions to draw the player and animate it:
var player = draw_object(standing);
animate_object(player, walking);
The player returned from draw_object is a Javascript object, keyed by the names of body parts (body, left leg, right foot, etc.). As of yesterday, the animate_object function iterates through each body part to animate each individually. I hope to do the same today with a translate_object function that takes the x-y coordinate that mark the end of the walk and a duration for the walk:
var player = draw_object(standing);
animate_object(player, walking);
translate_object(player, 250, 250, 5*1000);
So what should the tranlate_object function do to each body part?
function translate_object(obj, x, y, seconds) {
for (var part in obj) {
// What goes here?
};
}
I cannot simply use the translate method built-in to raphaël—it moves objects immediately rather than making a timed animation. There is the animate method. I know that works because I am already using it in the animate_object function to make the walking animation. Can I add another animate on top of that? Again one way to find out...
function translate_object(obj, x, y, seconds) {
for (var part in obj) {
obj[part].animate({cx: x, cy: y}, seconds);
};
}
Well it does not cancel out the walking animation, but unfortunately it does not apply any motion either. Eventually, I try to apply the animation to the body by hand in the console, which does not work either. It does change the coordinates at which raphaël thinks the thing is at, but no movement takes place:



Hmm. That seems kind of like a bug. I will investigate more another day. For now I would really like to see if I can move the player while the animation continues. But first, how to move the player?

Fortunately, I have solved that problem before—using animateAlong to move objects. That involves assembling an SVG lineto path to animate motion along. Using my hard-earned knowledge of SVG paths from last night, I eventually come up with:
function translate_object(obj, x, y, seconds) {
// offset between end coordinates and current location
var x_diff = x - obj.body.getBBox().x;
var y_diff = y - obj.body.getBBox().y;

for (var part in obj) {
// calculate path starting from absolute coordinates and moving
// relatively from there by the offset
var p = "M " + obj[part].getBBox().x + " " + obj[part].getBBox().y +
" l " + x_diff + " " + y_diff;

// animate along that path
obj[part].animateAlong(p, seconds);
};
}
That works to move the player about the screen, but the walking animation is cancelled:

video

Bummer. It would have been nice if the two animations could have been combined, but no such luck. Up tomorrow, I may consider alternatives or move on to other parts of my (fab) game.


Day #169

Sunday, July 18, 2010

Less Owiee

‹prev | My Chain | next›

Up today, I am trying to figure out SVG. Well maybe not all of SVG, but at least SVG paths.

My lack of SVG understanding caused a bit of a problem yesterday in my raphaël.js animations. Specifically, the leg on the left side of my "player" spins around in mid-air:

video

I am reasonably sure that this is caused by how I drew the leg in Inkscape. To animate SVG or raphaël.js, one supplies a start SVG path and an end path. Both paths for the leg—the leg while "standing" and the leg while "walking"—were drawn in Inkscape. For one path, I started drawing at the bottom and went up, but for the other, I started at the top and went down. Directions matter in SVG.

I can fix this simply enough by redrawing in the right direction, but I would like to see if I can figure out SVG well enough to fix this by hand. The SVG paths for the walking & standing left left are:
var standing = [
{ label: "left_leg",
d: "M 9.7001312,18.93142 C 9.4644309,22.702657 9.2287306,26.473893 8.9930303,30.24513" },
... ]

var walking = [
{ label: "left_leg",
d: "m 3.9748751,28.518101 c 2.828427,-3.88909 5.6568539,-7.77818 8.4852809,-11.66727" },
... ]
For comparison, the right leg paths are:
var standing = [
{ label: "right_leg",
d: "m 13.942752,18.93142 c 0.353552,3.653383 0.707104,7.306767 1.060656,10.96015" },
... ]

var walking = [
{ label: "right_leg",
d: "m 23.066757,17.204391 c 1.767767,3.653383 3.535533,7.306767 5.3033,10.96015" },
... ]
Interesting. So the standing version of the left leg is the only one beginning with a capital "M" and containing a capital "C". Simply switching the uppercase "M" and "C" to lowercase has no effect. I did not really expect it to, but it was worth a try.

Actually reading through the documentation I see that the initial M/m + x-y coordinates are absolute coordinates for the start of the path. It is where the pen is put down to start drawing.

Next up is the C/c stuff which is a Bezier curve. I have drawn Bezier curves in graphical packages before, but I have very little what they are. My background is in physics, not graphics. If Bezier curves were covered in physics 101, I missed that day. So I read through the documentation. The fist two points in the Bezier curve are control points, the third is the end point:
class="prettyprint">var standing = [
{ label: "left_leg",
d: "M 9.7001312,18.93142 C 9.4644309,22.702657 9.2287306,26.473893 8.9930303,30.24513" },
... ]
So I am starting at x: 9.7, y: 18.9. I then move through two control points (which should be in nearly a straight line), then end at x: 9.0, y: 30.2. That seems reasonable.

For the "walking" leg, I have:
var walking = [
{ label: "left_leg",
d: "m 3.9748751,28.518101 c 2.828427,-3.88909 5.6568539,-7.77818 8.4852809,-11.66727" },
... ]
Interesting. So I start the pen stroke at x: 4.0, y: 28.5. That is about the same y-coordinate at which the standing leg ended, so I have definitely gone in a different direction drawing things. Next up is a lowercase "c", which indicates that the coordinates are relative to the pen start coordinates. It is also fairly apparent that the coordinates are relative based on the negative y-coordinates in the Bezier curve.

I am unclear what made Inkscape describe one of the lines with absolute coordinates and the other with relative coordinates. The relative coordinates seem to be favored in the other curves so I am tempted to stick with the relative curve and fix the absolute curve. Instead, I will fix the relative. For the other three legs, I started drawing from the player's body and ended down at the feet. Since the relative coordinates are decreasing the y-coordinates, I clearly started the pen stroke at the feet.

To fix, I calculate the absolute coordinates:
m 3.9748751,28.518101
c 6.8033021,24.629011
9.631729,20.739921
12.460156,16.850831
Inverting those coordinates, my new starting point is x: 12.5, y: 16.9 and the new ending point is x: 4.0 and y: 28.5. Definitely going in the right direction now. I could go further and calculate relative coordinates at this point, but why bother? This ought to describe my Bezier curve well and, more importantly, in the right direction:
var walking = [
{ label: "left_leg",
d: "M 12.460156,16.850831 C 9.631729,20.739921 6.8033021,24.629011 3.9748751,28.518101" },
... ]
With my paths for the legs both going in the same direction, the animation stays together:

video

That is a good stopping point for tonight. Tomorrow, I think I will try to animate the player moving in addition to looking as though it is walking.


Day #168