Friday, September 3, 2010

Cross Browser Web Workers

After some time of hearing about the awesome new HTML5 Web Workers but having no particular need for them, I was finally presented with a problem that they could help me with. I promptly sat down with a handful of Google search results and went a-learnin. Turns out they're basically pretty simple. ( except for a small syntactic gotcha in Chrome, more about that later )

There is one major flaw with them though. As far as I could tell, there's no good way to write your javascript code once, in one form, and have it gracefully degrade to non-Worker enabled browsers (If someone knows differently, please let me know in the comments).

So, the gist of the Workers setup is that your main javascript code will "kick off" a "thread" in the background, telling that thread a separate javascript file to download and execute. This seemed to me to be the first interesting compatibility breaker. What was once written in one file, would now be separated into two files. Secondly the two "threads" communicate through messages, something that doesn't much exist pre-HTML5.

For my needs, the code in the "Worker" should still execute and function if Workers are not available in a user's browser. Sure the user will have a performance impact, but the page should still function. All that I could find around the web were statements like:
if( Worker ) {
// algorithm with workers
} else {
// algorithm without workers
}
That would cause me to duplicate considerable amounts of my codebase, and be a case-by-case solution. Anytime those come up, there must be a better solution!

I quickly modified what I was working on to function via the native Worker implementation in Chrome and Firefox, but then my attention immediately turned to creating a wrapper that would behave like a native Worker (albiet far less efficient) in browsers which had no native implementation.

Turns out, it wasn't all that difficult - due to the simplistic nature of the interface between the two threads. I've bundled it up as a jQuery extension here (sadly the extensions site is down due to spamming).
(function($){
$.worker = function(path) {
if('undefined' === typeof(Worker)){
function IWorker() {
var __w = this;
var messageq = [];
__w.postMessage = function(m){messageq.push(m);};
this.__bindBody = function(data){
var __win = { postMessage : function(m){
if(__w.onmessage){
setTimeout(function(){ __w.onmessage({data: m});}, 1);
}
}
};
with(__win){
eval(data);
__w.postMessage = (onmessage)
? function(m) { setTimeout(function() {onmessage({data: m});}, 1); }
: function(){};
for(var i=0,msg;msg=messageq[i];i++){
__w.postMessage(msg);
}
}
}
}

var iworker = new IWorker();
$.get(path, function(data) {
iworker.__bindBody(data);
});
return iworker;
} else {
return new Worker(path);
}
}
})(jQuery);

To use it, simply replace new Worker("myfile.js") with $.worker("myfile.js"). If the browser supports Workers natively, it'll just fallthrough and use it. Otherwise, it will grab the worker file via ajax, wrap it in a scope that simulates a worker and provides the needed postMessage and onmessage API hooks.
// pre-plugin
var w = new Worker("myfile.js");
w.onmessage = function(e) { alert(e.data); }
w.postMessage("Hi");

// post-plugin
var w = $.worker("myfile.js");
w.onmessage = function(e) { alert(e.data); }
w.postMessage("Hi");
One glaring omission that I have yet to need, is that this implementation does not mimc the onerror event that is possible with Workers. I plan to implement this, but likely won't until either I need it, or someone else asks me to :)

Oh, and the "syntactic gotcha in Chrome" that I mentoined before, kept me spinning for a good hour or more....
In your worker.js file, in order to capture incoming messages, you define a method:
onmessage = function(e){
// do something with e.data
};
That seems straight-forward, though, it is interestingly NOT equivalent to
function onmessage(e){
// do something with e.data
}
in Chrome (though it works fine in Firefox). So, be sure to use the first syntax.

Tuesday, January 12, 2010

PHPUnit on Windows, phew!

Wow, sometimes things can be so friggin difficult! I think I finally have phpUnit installed on my Windows development machine, and figured I'd record my actions here both for my own reuse and hopefully for anyone traveling down the same road.

If you want to jump straight to the instructions, feel free. Otherwise, here's a little backstory.

Backstory

First, to note, I have PHP installed from the thread-safe Win32 installer packages available from http://windows.php.net/download/. I am on version 5.3.1, though I believe this process has very little to do with the version of PHP.

The thing about installing PHPUnit, is that it first requires that you have PEAR installed. The thing about PEAR on Windows, is that the installers for PHP do not ship with a valid way of installing PEAR. All the instructions I could find on the web start with something like:

Install PEAR using c:\PHP\go-pear.bat
This script, however is broken, as it goes looking for PEAR/Pear5.php and just dies there. I tried numerous attempts at patching together the files it says it needs, but alas in the end I decided it just wouldn't work.

After much searching, I came across a go-pear.php script on pear.php.net's own site (http://pear.php.net/go-pear). Not to be confused with go-pear.bat, mind you.

This was the turning point to it all. There were still a few hiccups along the road that hopefully these instructions will help you overcome. Also note that I am working on a Windows 7 machine, so I also had some issues with having to run certain parts as an administrator. If you're on XP, you can ignore the "Run as Administrator" notes, I believe.

Instructions

  1. Copy the go-pear script ( http://pear.php.net/go-pear ) and save it as go-pear.php somewhere,
    I chose %temp% and will use that throughout these instructions

  2. Open a command prompt as an Administrator
    ( Start -> All Programs -> Accessories -> Command Prompt, right-click and select "Run as Administrator )

  3. Change to the directory where you saved go-pear.php
    cd %temp%

  4. Run go-pear.php
    php go-pear.php

  5. Set the prefix path to your PHP directory by selecting #1.
    For me this was: C:\Program Files\PHP, apparently lots of people also install PHP directly in C:\PHP

  6. Let PEAR install, the other defaults were fine for me

  7. The last bit of instructions encourage you to run the PEAR_ENV.reg file to include PEAR in your command path. I did just that, feel free to or not.
Yay, now you should have PEAR installed. Moving on to PHPUnit now:
  1. Tell PEAR where to find PHPUnit
    pear channel-discover pear.phpunit.de
    Note: that this still needs to be performed as an Administrator

  2. PHPUnit apparently depends on a YAML package maintained in a different channel, so also tell PEAR where to find that package
    pear channel-discover pear.symfony-project.com
    Note: that this still needs to be performed as an Administrator

  3. Now, you can install PHPUnit.
    Since I am a sucker for dependencies, and Love to install them all blindly just in case they'll make my life a little easier, I opt to install all dependencies.
    pear install --alldeps phpunit/PHPUnit
Yippie! Now you should have PHPUnit finally installed. The PHPUnit script should now be available in your PHP directory. For me this was C:\Program Files\PHP\phpunit.bat

I hope this helps at least one other person in the world not waste their morning figuring through these steps. If you find that your experience is different that this in any way, please post a comment here so that others may benefit as well.

Good Luck, and happy Unit Testing!