Threading in JavaScript with Web Workers


I’m about 5 weeks into the second 8 week game competition now building my, as yet unnamed, (original) Syndicate clone.

My levels consist of real images from Bing Maps (which I was surprised to find had nicer imagery when compared with Google, despite Google maps generally being my go-to maps site), which I then outline the buildings on using a level editor I built for the task, then save a big ol JSON array. This is then used in game to tell the little guys where they are and aren’t allowed to walk, amongst other things. The windy streets of my first level (Brighton, on the south coast of the UK) make for some difficult navigation- so I had to employ an A* search algorithm which would let my little guys find their way around these defined polygons which represent the footprints of the buildings. This all worked fine, however I noticed a the browser would lock up for a second or so while it ran through checking all the polygons and building paths for all four of your guys. “Wouldn’t it be great if I could do this in another thread” I thought…

Web Workers to the rescue!

Web workers are new in HTML5 and let you fire up another piece of javascript code in another thread, totally separate from the UI (so it will stop the freezeing I was seeing when the Javascript had to think really hard). You only have one mechanism (correct me if I’m wrong here) to send messages between your “Worker” thread and back to the parent application; the postMessage() method and onmessage callback function.

On the application side you create your new worker like this;


// setup the worker- giving it the url for the js file with the code to execute
var worker = new Worker('mysource.js'); 

// define a call back to handle messages we get from the worker
worker.onmessage = function(msg) {  
  alert(msg);
}

// send a message to the worker
worker.postMessage('shaw');  

Then on the worker side you have the same methods at your disposal;

// this exists in mysource.js
// setup a call back to handle messages we receive from the application
self.onmessage = function(msg) {  
  // post a message back to the application
  self.postMessage('hello ' + msg);  
}

In case you’ve not figured it out, in the above example you should expect to get an alert box saying “hello shaw”.

It’s also work pointing out that “self” is the global scope inside one of these web workers, as you won’t have access to any of the usual browser window or document objects.

So this is all pretty straight forward, but I still had a problem with my route finding example; I’d written a class which has a constructor accepting the polygon array and then a method which works out the route and returns it– how does that work in this model?? How do you call methods and constructors when the only access you have to your thread is a simple postMessage method?

Calling methods from ‘the other side’
postMessage() lets you send JSON as well as just plain old strings, which makes it quite a bit more useful. I used this to build a basic messaging system between the app and the thread- I would pass something like this;

worker.postMessage({ method: 'Init', args : [my_array, null, 5]})

then in the worker I would have a call back which looked like this;

self.onmessage = function(msg) {
    // the if catches the 'Init' special case- this instanciates a new RouteFinder
    if (msg.method == 'Init') 
    {
        // create a new instance of our route finder class, using it's constructor and passing in args
        self.MyRouteFinder = RouteFinder.apply( null, msg.args );
    }
    else  // the else catches all the other method calls, whatever they may be
    {  
        // find the method from the prototype chain, and apply it against our globally scoped object, again passing the args
        // the result of this method call is passed straight back to the application
        self.postMessage(RouteFinder.prototype[msg.method].call(self.MyRouteFinder, msg.args));
    }
}

This worked well for me though there are lots of (often better!) variations of this pattern on-line.

What if I don’t want to put my worker code in another file?

Also not a problem as detailed in this article on html5rocks.com. It basically involves grabbing your code from it’s own script tab on the same page, then smashing it into a Blob, then passing this blob into the construtor of your web worker.

Wait a sec.. how do I debug these workers?

You may have fired up some code in Chrome and noticed it hasn’t worked, so gone to the dev tools (F12) then flicked around in the scripts tab and… wait a minute… the web worker code doesn’t show up in the scripts panel! Well don’t worry- if you’re up to date with your copy of Chrome go down to the bottom right hand corner of the scripts panel where you’ll see one of the collapsably panels is called “Workers” and lists the code executing in each of your workers- just click one to launch another debug window which you can set break points on in the usual way.

  1. No comments yet.
(will not be published)