{"id":22120252,"url":"https://github.com/joshbrew/magicworker","last_synced_at":"2025-09-10T05:13:22.919Z","repository":{"id":133634749,"uuid":"426380485","full_name":"joshbrew/MagicWorker","owner":"joshbrew","description":"Use Web Workers i.e. threads conveniently to create sophisticated single-file multithreaded applications. No need to understand the rest!","archived":false,"fork":false,"pushed_at":"2022-05-25T23:49:45.000Z","size":6996,"stargazers_count":4,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-02T07:51:11.140Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joshbrew.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-09T20:41:34.000Z","updated_at":"2025-01-24T03:30:15.000Z","dependencies_parsed_at":null,"dependency_job_id":"944bd876-f1f1-47cd-a204-0bd447501f84","html_url":"https://github.com/joshbrew/MagicWorker","commit_stats":null,"previous_names":["brainsatplay/magicworker"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/joshbrew/MagicWorker","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshbrew%2FMagicWorker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshbrew%2FMagicWorker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshbrew%2FMagicWorker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshbrew%2FMagicWorker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joshbrew","download_url":"https://codeload.github.com/joshbrew/MagicWorker/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshbrew%2FMagicWorker/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267007598,"owners_count":24020261,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-25T02:00:09.625Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-01T14:21:52.697Z","updated_at":"2025-07-25T12:33:10.888Z","avatar_url":"https://github.com/joshbrew.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## MagicWorker\n\n![magicworkerv](https://img.shields.io/npm/v/magicworker)\n![magicworkerx](https://img.shields.io/npm/dt/magicworker)\n![magicworkerz](https://img.shields.io/npm/l/magicworker)\n![magicworkers](https://img.shields.io/github/size/joshbrew/MagicWorker/dist/magicworker.js)\n\n`npm i magicworker`\n\n'webpacked' with [tinybuild](https://github.com/brainsatplay/tinybuild)\n\n## Major Features\n\n- Use Web Workers i.e. threads conveniently to create sophisticated single-file multithreaded applications. No need to understand the rest!\n- Includes several utilities for on-the-fly transferring functions, data, typed arrays, and class objects to execute on the thread.\n- Do bulk math operations, use gpujs to create gpu threads with intuitive code-writing. Includes FFT examples.\n- Includes worker-based canvas and threejs rendering utilities (use node for easier threejs inclusion) for multithreaded rendering and basic proxy controls.\n- Create message channels between threads to keep all operations off of the main thread.\n\n## WorkerManager\n\nOn the frontend, you just need to \n```js\nimport {WorkerManager} from 'magicworker' //or its under 'magic' in html files when including the dist magicworker.js\n\nconst manager = new WorkerManager();\n```\nor include the webpacked `magicworker.js` in your html file, which instantiates the WorkerManager as `magic` with a default thread running.\n\nFrom there you can run the default functions, add your own by following the template, and easily build whole threading pipelines.\n\n## WorkerManager Major Functions\n\n#### Adding a thread:\n```js\n\nlet id = manager.addWorker(); //add a thread and return the id for passing callbacks to that thread\n\n```\n\n#### Running a function on the thread:\n```js \n\nmanager.run('ping',undefined,id); //the id is optional if you want to rotate through available threads instead \n\n//or\nlet result = await manager.run('ping');\n\n//or\nmanager.run('ping').then(res =\u003e console.log(res)).catch(console.error);\n```\n\n#### Adding a function to the thread\n```js\n\nmanager.addFunction(\n    'add', //function name\n    function add(self,args,origin){return args[0] + args[1];}, //the function, self references the thread's scope, where you can access saved values or objects etc.\n    id //the id of the thread to write to, leave blank to write to all threads\n);\n\nmanager.run('add',[5,7],id).then(console.log).catch(console.error); //there is a transfer buffer for typedarrays you can use in the function too for faster operations with GIANT datasets\n\n```\n\n##### Set Local Variables in the thread scope\n```js\n\nmanager.setValues({x:1},id); //sets values on all workers if no id provided, there is a transfer buffer for typedarrays you can use in the function too\n\n```\n\n#### Terminate workers\n```js\n\nmanager.terminate(id); //leave id blank to terminate all workers\n\n```\n\n#### Establish a message channel between two threads (avoids the main thread)\n\nThis is the most advanced usage of the worker API for creating complex pipelines. See below for a multithreaded particle system that passes data to a render thread without touching the main thread.\n\n```js\n\nmanager.establishMessageChannel(\n    eventName,\n    worker1Id,\n    worker2Id,\n    worker2Response=(self,args,origin)=\u003e{}, //response on the second thread\n    functionName, //This tags the function on the first thread that triggers the message channel to send its result to the 2nd worker instead of back to the main trhead\n    origin //add an origin tag for more specificity of when to trigger the channel\n)\n\n```\n\n\n### WorkerManager Utilities\n\n#### Events\nAllows subscribing to function outputs or outputs from specific origin points (or both) to automate the pipeline\n\n```js\n\nmanager.addEvent('threadprocess',worker1Id,'add');\n\nlet sub = manager.subEvent('threadprocess',(result)=\u003e{console.log(result);});\n\nmanager.unsubEvent('threadprocess',sub); //or leave sub blank to unsubsribe all\n\n```\n\n#### ThreadedCanvas\nThis class has macros for creating a canvas on a worker, which handles the rendering loop itself. See below for basic and advanced usage\n\n#### ProxyElement\nThis class lets you mirror element inputs to a proxy, mainly for canvas/threejs operations. From a ThreeJS tutorial.\n\n#### GPU.js integration\nWe have some demonstrations of our gpujs integration on-hand for testing threaded kernels:\n\nFFT:\n```js\n\n\nlet arr = new Array(100).fill(1);\n\nconsole.time('fft (kernel writing to gpu)');\nmanager.run('fft',[arr,1]).then(\n    (res) =\u003e {\n\n        console.timeEnd('fft (kernel writing to gpu)')    \n        console.time('fft (kernel saved on gpu this time)')\n        manager.run('fft',[arr,1]).then(\n        (res2) =\u003e {\n            console.timeEnd('fft (kernel saved on gpu this time)');\n            console.log('fft',res);\n        }).catch(console.error);\n    }).catch(console.error);\n\n```\n\nAdd functions/kernels:\n```js\n\nfunction rms(arr, mean, len) { //root mean square error\n    var est = 0;\n    var vari = 0;\n    for (var i = 0; i \u003c len; i++) {\n        vari = arr[i]-mean;\n        est += vari*vari;\n    }\n    return Math.sqrt(est/len);\n}\n\nmanager.run('addgpufunc',[rms.toString()]);\n\nfunction transpose2DKern(mat2) { //Transpose a 2D matrix, meant to be combined\n    return mat2[this.thread.y][this.thread.x];\n}\n\nmanager.run('addkernel',['transpose',transpose2DKern.toString()]\n\nlet mat2 = [[1,2,3,4],[5,6,7,8],[8,9,10,11],[12,13,14,15]];\n\nlet result = await manager.run('callkernel',['transpose', [mat2]]);\n\n```\n\n\n\n## Basic usage in an html file, with the browser library:\n```html\n\n\u003cscript src='./dist/magicworker.js' type='module'\u003e \n\n//ping the worker to that it's working. These are async functions and can be awaited or can use .then() promises to keep threads synchronized\nlet manager = new magic.WorkerManager(1);\nmanager.run('ping').then(console.log).catch(console.error);\n\n//lists available function on the worker\nmanager.run('list')\n.then(result =\u003e {result.forEach((r) =\u003e {document.body.innerHTML+=`${r}\u003cbr\u003e`})})\n.catch(console.error);\n\nlet threadId = manager.workers[0].id; //get the specific id of the thread\nlet origin = 0; //can name the source of the thread call\n\nmanager.addFunction( \n            'add',\n            function add(self,args,origin){return args[0]+args[1];},\n            threadId//, //can add functions to a specific thread, or all of them if blank\n            //origin //optionally identifies source of the call (e.g. between threads or programs)\n).then(console.log).catch(console.error);\n\n//creates a subscribable event for specific functions on threads and/or for specific origins of thread calls\nmanager.addEvent(\n    'threadresult',\n    threadId,\n    'add', // set a specific function call to fire an event for\n    origin //and/or set a source location to control event triggers, need at least one of these two\n).then(console.log);\n\n//subscribe to events on the frontend\nmanager.subEvent('threadresult',(res)=\u003e{\n    console.log(\"add result\", res);\n});\n\nmanager.run('add',[5,4],threadId); //now call the new function on the thread and get the result in the event or you can await this call or do .then()\n\n\u003c/script\u003e\n\n```\n\n\n## Canvas usage (via a nodejs-based web app)\n```js\n\nimport {WorkerManager, ThreadedCanvas} from 'magicworker'\n\nlet workers = new WorkerManager(undefined,0);\n\nlet canvasWorkerId = workers.addWorker();\n\nlet canvas = document.querySelector('canvas'); //canvas in the html page\n\nlet draw = (self, args, origin) =\u003e {\n    let cWidth = self.canvas.width;\n    let cHeight = self.canvas.height;\n        // style the background\n    let gradient = self.ctx.createRadialGradient(cWidth*0.5,cHeight*0.5,2,cWidth*0.5,cHeight*0.5,100*self.angle*self.angle);\n    gradient.addColorStop(0,\"purple\");\n    gradient.addColorStop(0.25,\"dodgerblue\");\n    gradient.addColorStop(0.32,\"skyblue\");\n    gradient.addColorStop(1,self.bgColor ?? 'black');\n    self.ctx.fillStyle = gradient;\n    self.ctx.fillRect(0,0,cWidth,cHeight);\n    \n    // draw the circle\n    self.ctx.beginPath();\n\n    self.angle += self.angleChange;\n\n    let radius = cHeight*0.04 + (cHeight*0.46) * Math.abs(Math.cos(self.angle));\n    self.ctx.arc(cWidth*0.5, cHeight*0.5, radius, 0, Math.PI * 2, false);\n    self.ctx.closePath();\n    \n    // color in the circle\n    self.ctx.fillStyle = self.cColor;\n    self.ctx.fill();\n    \n}\n\nlet canvasWorker = new ThreadedCanvas(       /\n    workers,\n    canvas,                                  //canvas element to transfer to offscreencanvas\n    '2d',                                    //canvas context setting       \n    draw,                                    //pass the custom draw function\n    {angle:0,angleChange:0.000,bgColor:'black',cColor:'red'}, //'self' values, canvas and context/ctx are also available under 'self' for now, these can be mutated like uniforms on the thread with the 'setValues' command\n    canvasWorkerId                           //worker id to use, if undefined it sets up its own worker\n);\n\ncanvasWorker.startAnimation();\n\ncanvasWorker.setValues({angleChange:0.001}); //set the rate of change for the circle\n\n```\n\n\n\n## Advanced ThreeJS usage with several threads doing their own calculations, using the threejs bundled dist (works in browser!!)\n- [Example (takes a few seconds to load ThreeJS)](https://app.brainsatplay.com#Multithreaded)\n\n```js\n\nimport {WorkerManager, ThreadedCanvas, ProxyElement} from 'node_modules/magicworker/dist/magicworker.three'\nimport {DynamicParticles} from 'dynamicparticles' //another library for this example\n\nlet workers = new WorkerManager();\n\nlet worker1Id = workers.addWorker();\nlet worker2Id = workers.addWorker();\nlet canvasWorkerId = workers.addWorker();\n\nlet canvas = document.querySelector('canvas'); //canvas in the html page\nlet origin = 0; //main thread Id\n\nlet canvasWorker = new ThreadedCanvas(   \n    workers,\n    canvas,        //canvas element to transfer to offscreencanvas\n    undefined,   //canvas context setting       \n    undefined,  //pass the custom draw function\n    undefined,  //'this' values, canvas and context/ctx are also available under 'self' for now, these can be mutated like uniforms with the 'setValues' command\n    canvasWorkerId,   //worker id to use, if undefined it sets up its own worker\n    origin,     \n    undefined //transfer values\n);\n\n//create a proxy for the canvas on the worker thread to mirror key inputs \nlet proxy = initElementProxy(\n    canvas,\n    canvasWorkerId,\n    origin\n);\n\n\n//these functions run on the worker scope and have expected parameters\nfunction particleSetup(self, args, origin){\n    //console.log(self);\n    self.particleObj = new self.particleClass(undefined,undefined,false,false);\n    self.particleObj.setupRules(args[0]);\n\n    if(typeof args[1] === 'object') self.particleObj.updateGroupProperties(args[4],args[1],args[2],args[3]); //can set some initial properties\n    //use an arraybuffer system for MUCH FASTER transfers\n    //https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects\n    //https://developers.google.com/web/updates/2011/12/Transferable-Objects-Lightning-Fast\n\n    let groups = [];\n    let positionbuffer = [];\n    let bufferidx = -1;\n    let p = 0;\n    self.particleObj.particles.map((group,j) =\u003e {\n        // if(j \u003e bufferidx) {\n        //     positionbuffer.push(...new Array(group.particles.length*3));\n        //     bufferidx = j;\n        // }\n        groups.push(new Array(group.length));\n        group.particles.map((particle, k) =\u003e {\n            groups[j][k]=[particle.position.x,particle.position.y,particle.position.z];\n            // positionbuffer[p]=particle.position.x;\n            // positionbuffer[p+1]=particle.position.y;\n            // groups[p+2]=particle.position.z;\n            // p+=3;\n        });\n    });\n    //console.log(groups)\n    return groups;\n    //return Float32Array.from(positionbuffer);\n}\n\nfunction particleStep(self, args, origin){\n    self.particleObj.frame(args[0]);\n    //use an arraybuffer system for MUCH FASTER transfers\n    //https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects\n    //https://developers.google.com/web/updates/2011/12/Transferable-Objects-Lightning-Fast\n    //let groups = [];\n    let positionbuffer = [];\n    let bufferidx = -1;\n    let p = 0;\n    self.particleObj.particles.map((group,j) =\u003e {\n        if(j \u003e bufferidx) {\n            positionbuffer.push(...new Array(group.particles.length*3));\n            bufferidx = j;\n        }\n        group.particles.map((particle, k) =\u003e {\n            positionbuffer[p]=particle.position.x;\n            positionbuffer[p+1]=particle.position.y;\n            positionbuffer[p+2]=particle.position.z;\n            p+=3;\n        });\n    });\n\n    return {pos:Float32Array.from(positionbuffer),time:self.particleObj.currFrame}; //will automatically be transferred as our worker checks for TypedArrays\n}\n\nfunction setGroupProperties(self, args, origin){\n    if(typeof args[0] === 'object') {\n        if(!args[3]) {\n            self.particleObj.particles.forEach((p,i) =\u003e {\n                self.particleObj.updateGroupProperties(i,args[0],args[1],args[2]);\n            });\n        } else {\n            self.particleObj.updateGroupProperties(args[3],args[0],args[1],args[2]);\n        }\n        return true;\n    }\n    return false;\n}\n\n\nconst boidsSetup = (self, args, origin) =\u003e {\n    if(!self.boids) {console.error('need to add boids to the worker first setValues({boids:[[{x,y,z}],[etc.]]')}\n    let three = self.threeUtil;\n    const THREE = self.THREE;\n\n    if(three.ANIMATING) {\n        three.clear(self, args, origin);\n    }\n\n    three.scene = new THREE.Scene();\n    three.camera = new THREE.PerspectiveCamera(75, three.proxy.clientWidth / three.proxy.clientHeight, 0.01, 1000);\n    three.camera.position.z = 5\n    \n    three.renderer = new THREE.WebGLRenderer({canvas:self.canvas, antialias: true });\n    three.renderer.setPixelRatio(Math.min(three.proxy.clientWidth / three.proxy.clientHeight,2));\n    three.renderer.shadowMap.enabled = true;\n\n    three.resizeRendererToDisplaySize(three.renderer,three.proxy,three.camera);\n    // three.renderer.domElement.style.width = '100%';\n    // three.renderer.domElement.style.height = '100%';\n    // three.renderer.domElement.id = `canvas`;\n    // three.renderer.domElement.style.opacity = '0';\n    // three.renderer.domElement.style.transition = 'opacity 1s';\n\n    //use proxy instead of domElement\n    three.controls = new three.OrbitControls(three.camera, three.proxy);\n    three.controls.enablePan = true\n    three.controls.enableDamping = true\n    three.controls.enabled = true;\n    // three.controls.minPolarAngle = 2*Math.PI/6; // radians\n    // three.controls.maxPolarAngle = 4*Math.PI/6; // radians\n    // three.controls.minDistance = 0; // radians\n    // three.controls.maxDistance = 1000; // radians\n\n    three.nBoids = self.maxParticles;\n    //console.log(self.boids)\n    //array of position arrays input\n\n    let vertices = [];\n\n    let color = new THREE.Color();\n    let colors = [];\n    self.boids.forEach((group,i)=\u003e {\n\n        group.forEach((boid)=\u003e{\n\n            let x = boid[0];\n            let y = boid[1];\n            let z = -boid[2];\n\n            vertices.push( x, y, z );\n\n            let roll = Math.random();\n            if(i==0){\n                if(roll \u003c= 0.3){\n                    color.set('lightseagreen');\n                } else if (roll \u003c= 0.85){\n                    color.set('blue');\n                } else {\n                    color.set('turquoise');\n                }\n                colors.push(color.r,color.g,color.b);\n            }\n            else if (i==1) {\n                if(roll \u003c= 0.3){\n                    color.set('pink');\n                } else if (roll \u003c= 0.85){\n                    color.set('red');\n                } else {\n                    color.set('orange');\n                }\n                colors.push(color.r,color.g,color.b);\n            }\n            else {\n                color.setRGB(Math.random(),Math.random(),Math.random());\n                colors.push(color.r,color.g,color.b);\n            }\n        });\n    });\n\n    self.boids = new Array(self.maxParticles);\n\n    let geometry = new THREE.BufferGeometry();\n    geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );\n    \n    // for (let i = 0; i \u003c three.nBoids; i++) {     \n    // }\n\n    geometry.setAttribute('color', new THREE.Float32BufferAttribute( colors, 3));\n\n    let pointmat = new THREE.PointsMaterial( \n        // { color: 0xffffff },\n        { \n            vertexColors: THREE.VertexColors,\n            opacity:0.99\n        });\n\n    /*\n    var spriteUrl = 'https://i.ibb.co/NsRgxZc/star.png';\n\n    var textureLoader = new THREE.TextureLoader()\n    textureLoader.crossOrigin = \"Anonymous\"\n    var myTexture = textureLoader.load(spriteUrl);\n    pointmat.map = myTexture;\n    */\n    three.points = new THREE.Points( geometry, pointmat );\n\n    three.points.position.y -=225;\n    three.points.position.x -=225\n    three.points.position.z +=75;\n\n    three.scene.add( three.points );\n    \n    if(!three.ANIMATING) {\n        three.ANIMATING = true;\n        three.animate(self, args, origin);\n    }\n    \n    for(let j = 0; j \u003c self.nGroups; j++) {\n        let portj = self['particleSetup'+j+'port'];\n        if(portj) {\n            requestAnimationFrame( //let the particle thread know that the render thread is ready for more data (throttled by framerate)\n                ()=\u003e{\n                    portj.postMessage({foo:'particleStep',input:[performance.now()*0.001],origin:origin});\n                }\n            ); \n        }\n    }\n}\n\nconst boidsRender = (self, args, origin) =\u003e {\n\n        let three = self.threeUtil;\n\n        three.resizeRendererToDisplaySize(three.renderer,three.proxy,three.camera);\n        \n        //console.log(self.boids)\n        if(self.boids.length === self.maxParticles*3) {\n        \n            let positions = three.points.geometry.attributes.position.array;\n            let count = 0;\n        \n            //console.log(self.boids);\n            let positionArray = self.boids;//Array.from(self.boids); //convert float32array\n\n            //updated with setValues\n            for(let count = 0; count\u003c positionArray.length; count+=3 ) {\n                positions[count]   =  positionArray[count];\n                positions[count+1] =  positionArray[count+1];\n                positions[count+2] = -positionArray[count+2];\n            }\n\n            three.points.geometry.attributes.position.needsUpdate = true; \n        }   \n\n        three.controls.update();\n        three.renderer.render(three.scene, three.camera);\n}\n\n\nlet maxParticles = 10000;\n            \nlet particleSettings = [\n    ['boids',4000,[450,450,450]],\n    ['boids',5000,[450,450,450]],\n    ['boids',1000,[450,450,450]]\n];\n\n\n\ncanvasWorker.setValues({\n    boids:[],\n    particleSettings:particleSettings,\n    maxParticles:maxParticles,\n    nGroups:particleSettings.length,\n    groupsSetup:0,\n    proxyId: proxy.id,\n    setupfstring: boidsSetup.toString(),\n    renderfstring: boidsRender.toString()\n});\n\n\n//now we're going to create a worker for each particle system\nlet particleWorkers = [];\nparticleSettings.forEach((s,i) =\u003e {\n    let workerId = workers.addWorker();\n    particleWorkers.push(workerId);\n    \n    workers.runWorkerFunction('transferClassObject',{particleClass:DynamicParticles.toString()},origin,workerId);\n    // //add some custom functions to the threads\n    workers.addWorkerFunction(\n        'particleSetup',\n        particleSetup,\n        origin,\n        workerId\n    );\n    \n    //add some custom functions to the threads\n    workers.addWorkerFunction(\n        'particleStep',\n        particleStep,\n        origin,\n        workerId\n    );\n\n    //add some custom functions to the threads\n    workers.addWorkerFunction(\n        'setGroupProperties',\n        setGroupProperties,\n        origin,\n        workerId\n    );\n\n    //direct communication channel between particle and render threads\n    workers.establishMessageChannel(\n        'particleSetup'+i,\n        workerId,\n        canvasWorkerId,\n        function worker2Response(self,args,origin,port,eventName){\n            //args = [float32array] from particle1Step output\n\n            //console.log(args,eventName);\n            args.output.forEach((arr) =\u003e {\n                self.boids[parseInt(eventName[eventName.length-1])] = arr;\n                self.groupsSetup++;\n            })\n            if(self.groupsSetup === self.nGroups) {\n                //console.log(self.boids);\n                self.runCallback( //init once we've received the initial boids data \n                    'initThree',\n                    [\n                        self.proxyId,\n                        undefined,\n                        self.setupfstring, //CONVERT TO STRING\n                        //undefined,\n                        self.renderfstring,\n                        undefined\n                    ],\n                    origin\n                );\n                //console.log(self)\n                //need to dispatch to all ports to begin animating\n                \n            }\n\n            \n        },\n        'particleSetup',\n        origin\n    );\n\n    //direct communication channel between particle and render threads\n    workers.establishMessageChannel(\n        'particleStep'+i,\n        workerId,\n        canvasWorkerId,\n        function worker2Response(self,args,origin,port,eventName){\n            //args = [float32array] from particle1Step output\n            //console.log(args.output,output.length);\n            let output = Array.from(args.output.pos);\n            let idx = parseInt(eventName[eventName.length-1]);\n            let offset = 0;\n            let j = 0;\n\n            while(j \u003c idx) {\n                offset+=self.particleSettings[j][1]*3;\n                j++;\n            }\n\n            self.boids.splice(offset, output.length, ...output );\n            \n            //console.log(offset,output.length);\n\n            //if(idx === 2) console.log(self.boids);\n            if(port) {\n                requestAnimationFrame( //let the particle thread know that the render thread is ready for more data (throttled by framerate)\n                    ()=\u003e{\n                        port.postMessage({foo:'particleStep',input:[args.output.time],origin:origin});\n                    }\n                ); \n            }\n        },\n        'particleStep',\n        origin\n    );\n\n\n    workers.runWorkerFunction('particleSetup',[[particleSettings[i]]],origin,workerId);\n    //window.workers.runWorkerFunction('particleSetup',particlesettings,origin,worker1Id);\n    \n\n});\n\n\n```\n\nTODO: bundle the threejs worker\n\n\nJoshua Brewster and Garrett Flynn\n\nLicense AGPL v3.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshbrew%2Fmagicworker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoshbrew%2Fmagicworker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshbrew%2Fmagicworker/lists"}