Move worker into webpack build

The jscad worker code was hosted as a static asset, making it odd
javascript where we have to be conscious of what javascript features we
can use and if it will work on older browsers, plus it can't be
typescript like the rest of the codebase.

Since redwood 0.36 we using webpack 5 should make loading workers easy
https://webpack.js.org/guides/web-workers/
But I had trouble with this (see:
https://community.redwoodjs.com/t/has-anyone-tried-workers-with-webpack-5-rw0-36-x/2394)
and instead used the webpack 4 loader without any issues

This issue relates to #411 , and is a checklist item on #444
Resolves #494
This commit is contained in:
Kurt Hutten
2021-09-08 06:16:52 +10:00
parent 9ae1cd4aff
commit 22da074965
5 changed files with 53 additions and 169 deletions

10
app/web/config/worker-loader.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
declare module "worker-loader!*" {
// You need to change `Worker`, if you specified a different value for the `workerType` option
class WebpackWorker extends Worker {
constructor();
}
// Uncomment this if you set the `esModule` option to `false`
// export = WebpackWorker;
export default WebpackWorker;
}

View File

@@ -45,7 +45,8 @@
"react-tabs": "^3.2.2",
"rich-markdown-editor": "^11.0.2",
"styled-components": "^5.2.0",
"three": "^0.130.1"
"three": "^0.130.1",
"worker-loader": "^3.0.8"
},
"devDependencies": {
"@types/lodash": "^4.14.170",

View File

@@ -16,6 +16,7 @@ import {
Mesh,
} from 'three'
import { jsCadToCadhubParams } from './jscadParams'
import TheWorker from 'worker-loader!./jscadWorker'
const materials = {
mesh: {
@@ -134,19 +135,7 @@ export const render: DefaultKernelExport['render'] = async ({
}: RenderArgs) => {
if (!scriptWorker) {
const baseURI = document.baseURI.toString()
const scriptUrl = '/demo-worker.js'
const script = `let baseURI = '${baseURI}'
importScripts(new URL('${scriptUrl}',baseURI))
let worker = jscadWorker({
baseURI: baseURI,
scope:'worker',
convertToSolids: 'buffers',
callback:(params)=>self.postMessage(params),
})
self.addEventListener('message', (e)=>worker.postMessage(e.data))
`
const blob = new Blob([script], { type: 'text/javascript' })
scriptWorker = new Worker(window.URL.createObjectURL(blob))
scriptWorker = new TheWorker()
let parameterDefinitions = []
scriptWorker.addEventListener('message', ({ data }) => {
if (data.action == 'parameterDefinitions') {

View File

@@ -1,25 +1,3 @@
(function(f) {
if (typeof exports === "object" && typeof module !== "undefined") {
module.exports = f()
} else if (typeof define === "function" && define.amd) {
define([], f)
} else {
var g;
if (typeof window !== "undefined") {
g = window
} else if (typeof global !== "undefined") {
g = global
} else if (typeof self !== "undefined") {
g = self
} else {
g = this
}
g.jscadWorker = f()
}
})(function() {
// multi purpose module
const setPoints = (points, p, i)=>{
points[i++] = p[0]
@@ -176,56 +154,6 @@ require.cache = {}
require.alias = {}
const initCanvas = (canvas, callback)=>{
// convert HTML events (mouse movement) to viewer changes
let lastX = 0
let lastY = 0
let pointerDown = false
const moveHandler = (ev) => {
if(!pointerDown) return
const cmd = {
worker: 'render',
dx: lastX - ev.pageX,
dy: ev.pageY - lastY
}
const shiftKey = (ev.shiftKey === true) || (ev.touches && ev.touches.length > 2)
cmd.action = shiftKey ? 'pan':'rotate'
callback(cmd)
lastX = ev.pageX
lastY = ev.pageY
ev.preventDefault()
}
const downHandler = (ev) => {
pointerDown = true
lastX = ev.pageX
lastY = ev.pageY
canvas.setPointerCapture(ev.pointerId)
ev.preventDefault()
}
const upHandler = (ev) => {
pointerDown = false
canvas.releasePointerCapture(ev.pointerId)
ev.preventDefault()
}
const wheelHandler = (ev) => {
callback({action:'zoom', dy:ev.deltaY, worker: 'render'})
ev.preventDefault()
}
canvas.onpointermove = moveHandler
canvas.onpointerdown = downHandler
canvas.onpointerup = upHandler
canvas.onwheel = wheelHandler
}
const cmdHandler = (handlers)=>(cmd)=>{
const fn = handlers[cmd.action]
if (!fn) throw new Error('no handler for type: ' + cmd.action)
@@ -380,9 +308,8 @@ function parseDef(code, line){
const makeScriptWorker = ({callback, convertToSolids})=>{
let workerBaseURI, onInit
let onInit, main, scriptStats, entities
function runMain(params={}){
@@ -695,92 +622,41 @@ let perspectiveCamera
}
return (params)=>{
let { canvas, baseURI=(typeof document === 'undefined') ? '':document.location.toString(), scope='main', renderInWorker, render, callback=()=>{}, scriptUrl='demo-worker.js', alias, convertToSolids=false } = params
function start(params) {
let {
callback=()=>{},
convertToSolids=false
} = params
// by default 'render' messages go outside of this instance (result of modeling)
let sendToRender = callback
let scriptWorker, renderWorker
workerBaseURI = baseURI
let scriptWorker
const sendCmd = (params, transfer)=>{
if(params.worker === 'render')
sendToRender(params, transfer)
else if(params.worker === 'script')
scriptWorker.postMessage(params, transfer)
if(params.worker === 'script') scriptWorker.postMessage(params, transfer)
else{
// parameter definitions will arrive from scriptWorker
callback(params, transfer)
}
}
const updateSize = function({width,height}){
sendCmd({ action:'resize', worker:'render', width: canvas.offsetWidth, height: canvas.offsetHeight})
}
renderInWorker = !!(canvas && renderInWorker && canvas.transferControlToOffscreen)
const makeRenderWorkerHere = (scope === 'main' && canvas && !renderInWorker) || (scope === 'worker' && render)
// worker is in current thread
if(makeRenderWorkerHere){
renderWorker = makeRenderWorker({callback:sendCmd})
sendToRender = (params, transfer)=>renderWorker.postMessage(params, transfer)
}
if(scope === 'main'){
// let extraScript = renderInWorker ? `,'https://unpkg.com/@jscad/regl-renderer'`:''
let script =`let baseURI = '${baseURI}'
importScripts(new URL('${scriptUrl}',baseURI))
let worker = jscadWorker({
baseURI: baseURI,
convertToSolids: ${convertToSolids},
scope:'worker',
callback:(params)=>self.postMessage(params),
render:${renderInWorker}
})
self.addEventListener('message', (e)=>worker.postMessage(e.data))
`
let blob = new Blob([script],{type: 'text/javascript'})
scriptWorker = new Worker(window.URL.createObjectURL(blob))
scriptWorker.addEventListener('message',(e)=>sendCmd(e.data))
scriptWorker.postMessage({action:'init', baseURI, alias})
if(renderInWorker) renderWorker = scriptWorker
if(canvas){
initCanvas(canvas, sendCmd)
window.addEventListener('resize',updateSize)
}
}else{
scriptWorker = makeScriptWorker({callback:sendCmd, convertToSolids})
callback({action:'workerInit',worker:'main'})
}
if(canvas){
// redirect 'render' messages to renderWorker
sendToRender = (params, transfer)=>renderWorker.postMessage(params, transfer)
let width = canvas.width = canvas.clientWidth
let height = canvas.height = canvas.clientHeight
if(scope == 'main'){
const offscreen = renderInWorker ? canvas.transferControlToOffscreen() : canvas
renderWorker.postMessage({action:'init', worker:'render', canvas:offscreen, width, height}, [offscreen])
}
}
return {
updateSize,
updateParams:({params={}})=>sendCmd({ action:'updateParams', worker:'script', params}),
runScript: ({script,url=''})=>sendCmd({ action:'runScript', worker:'script', script, url}),
postMessage: sendCmd,
}
}
const init = start({
convertToSolids: 'buffers',
callback:(params)=>self.postMessage(params),
})
// multi purpose module
});
self.onmessage = ({data}) => {
if (data.action === 'init') {
workerBaseURI = data.baseURI
}
init.postMessage(data, null)
};

View File

@@ -19057,6 +19057,14 @@ worker-farm@^1.7.0:
dependencies:
errno "~0.1.7"
worker-loader@^3.0.8:
version "3.0.8"
resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-3.0.8.tgz#5fc5cda4a3d3163d9c274a4e3a811ce8b60dbb37"
integrity sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==
dependencies:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
worker-rpc@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5"