Last active
August 31, 2022 16:18
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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. | |
} |
It would be convenient if this was published on npm or bower. Mind if I do?
blittle - sure, go for it. Maybe tidy it up a bit before hand.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'm using this function to calculate temporal autocorrelations of neuronal activity in a webworker - see temporalcorr.js in github.com/d1manson/waveform.