Skip to content

Instantly share code, notes, and snippets.

@d1manson
Last active August 31, 2022 16:18
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save d1manson/6714892 to your computer and use it in GitHub Desktop.
Save d1manson/6714892 to your computer and use it in GitHub Desktop.
BuildBridgedWorker is a function that makes communication between worker and main thread simple. It also means you can keep all related code in the same file, which is convenient.
/* EXAMPLE:
var workerCode = function(){
"use strict;" //this will become the first line of the worker
CalculateSomething(a,b,c,d){
var v = a+b+c+d; //trivial calculation
main.DisplayResult(v,"hello");
}
CalculateSomethingBig(buff,d){
var v = new Uint32Array(buff);
for(var i=0;i<v.length;i++)
v[i] /= d;
main.PlotFraction(v.buffer,"done",0,2,"world",[v.buffer]); //the buffer is fully transfered to the main thread (google "transferable objects javascript")
}
//the BuildBridgedWorker will add some extra code on the end to form the complete worker code
}
var DisplayResult = function(val,str){
// do something here
}
var PlotFraction = function(buffer,str1,p1,p2,str2){
// do something here
}
var theWorker = BuildBridgedWorker( workerCode,
["CalculateSomething","CalculateSomethingBig*"], //note asterisk indicating ArrayBuffer transfer
["DisplayResult", "PlotFraction*"],
[DisplayResult,PlotFraction]);
// Some example inputs
var w=9,x=100,y=0,z=2;
var v = new Uint32Array(100);
// And this is how you call the functions in the worker...
theWorker.CalculateSomething(w,x,y,z);
theWorker.CalculateSomethingBig(v.buffer,x,[v.buffer]);
// Note that with the CalculateSomethingBig the buffer is transfered to the worker thread (and dissapears on the main thread)
*/
var BuildBridgedWorker = function(workerFunction,workerExportNames,mainExportNames,mainExportHandles){
//workerFunciton is a function, the interior of which will be turned into a string and used as a worker
//workerExportNames should be an array of string function names available to main
//mainExportNames should be an array of string function names available to worker
//mainExportHandles should be an array of the actual functions corresponding to the functions in main
//for both Names arrays, if the function name ends in an asterisk it means that the last argument passed is going to be an array of ArrayBuffers
//
//The result of all this work is that inside the worker we can call main.SomeMainFunction(thing,otherthing,more,[buffer1,buffer2])
//and in main we can call myWorker.SomeWorkerFunction(hello,world,[buffer1,buffer2])
//
var baseWorkerStr = workerFunction.toString().match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1];
var extraWorkerStr = [];
// build a string for the worker end of the worker-calls-funciton-in-main-thread operation
extraWorkerStr.push("var main = {};\n");
for(var i=0;i<mainExportNames.length;i++){
var name = mainExportNames[i];
if(name.charAt(name.length-1) == "*"){
name = name.substr(0,name.length-1);
mainExportNames[i] = name;//we need this trimmed version back in main
extraWorkerStr.push("main." + name + " = function(/* arguments */){\n var args = Array.prototype.slice.call(arguments); var buffers = args.pop(); \n self.postMessage({foo:'" + name + "', args:args},buffers)\n}; \n");
}else{
extraWorkerStr.push("main." + name + " = function(/* arguments */){\n var args = Array.prototype.slice.call(arguments); \n self.postMessage({foo:'" + name + "', args:args})\n}; \n");
}
}
// build a string for the worker end of the main-thread-calls-function-in-worker operation
var tmpStr = [];
for(var i=0;i<workerExportNames.length;i++){
var name = workerExportNames[i];
name = name.charAt(name.length-1) == "*" ? name.substr(0,name.length-1) : name;
tmpStr.push(name + ": " + name);
}
extraWorkerStr.push("var foos={" + tmpStr.join(",") + "};\n");
extraWorkerStr.push("self.onmessage = function(e){\n");
extraWorkerStr.push("if(e.data.foo in foos) \n foos[e.data.foo].apply(null, e.data.args); \n else \n throw(new Error('Main thread requested function ' + e.data.foo + '. But it is not available.'));\n");
extraWorkerStr.push("\n};\n");
var fullWorkerStr = baseWorkerStr + "\n\n/*==== STUFF ADDED BY BuildBridgeWorker ==== */\n\n" + extraWorkerStr.join("");
// create the worker
var url = window.URL.createObjectURL(new Blob([fullWorkerStr],{type:'text/javascript'}));
var theWorker = new Worker(url);
// buid a funcion for the main part of worker-calls-function-in-main-thread operation
theWorker.onmessage = function(e){
var fooInd = mainExportNames.indexOf(e.data.foo);
if(fooInd != -1)
mainExportHandles[fooInd].apply(null, e.data.args);
else
throw(new Error("Worker requested function " + e.data.foo + ". But it is not available."));
}
// build an array of functions for the main part of main-thread-calls-function-in-worker operation
var ret = {blobURL: url};//this is useful to know for debugging if you have loads of bridged workers in blobs with random names
var makePostMessageForFunction = function(name,hasBuffers){
if(hasBuffers)
return function(/*args...,[ArrayBuffer,..]*/){var args = Array.prototype.slice.call(arguments); var buffers = args.pop(); theWorker.postMessage({foo:name,args:args},buffers);}
else
return function(/*args...*/){var args = Array.prototype.slice.call(arguments); theWorker.postMessage({foo:name,args:args});};
}
for(var i=0;i<workerExportNames.length;i++){
var name = workerExportNames[i];
if(name.charAt(name.length-1) == "*"){
name = name.substr(0,name.length-1);
ret[name] = makePostMessageForFunction(name,true);
}else{
ret[name] = makePostMessageForFunction(name,false);
}
}
return ret; //we return an object which lets the main thread call the worker. The object will take care of the communication in the other direction.
}
@d1manson
Copy link
Author

I'm using this function to calculate temporal autocorrelations of neuronal activity in a webworker - see temporalcorr.js in github.com/d1manson/waveform.

@blittle
Copy link

blittle commented Dec 31, 2013

It would be convenient if this was published on npm or bower. Mind if I do?

@d1manson
Copy link
Author

d1manson commented Jan 3, 2014

blittle - sure, go for it. Maybe tidy it up a bit before hand.

@blittle
Copy link

blittle commented Jan 29, 2014

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment