Skip to content

Commit

Permalink
fix: replace webworker-promise with comlink
Browse files Browse the repository at this point in the history
webworker-promise's package esm export is not correct. This causes
issues with vite. The package is deprecated.
  • Loading branch information
thewtex committed Nov 6, 2023
1 parent 16f8300 commit 99670a7
Show file tree
Hide file tree
Showing 24 changed files with 493 additions and 281 deletions.
326 changes: 239 additions & 87 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,13 @@
"@thewtex/zstddec": "^0.2.0",
"@types/emscripten": "^1.39.6",
"axios": "^1.4.0",
"comlink": "^4.4.1",
"commander": "^9.4.0",
"fs-extra": "^10.0.0",
"glob": "^8.1.0",
"markdown-table": "^3.0.3",
"mime-types": "^2.1.35",
"wasm-feature-detect": "^1.5.1",
"webworker-promise": "^0.5.1"
"wasm-feature-detect": "^1.5.1"
},
"bin": {
"itk-wasm": "./src/itk-wasm-cli.js"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,44 @@
import axios from 'axios'
import WebworkerPromise from 'webworker-promise'
import * as Comlink from 'comlink'

import WorkerProxy from './web-workers/worker-proxy.js'
import config from '../itkConfig.js'

interface createWebWorkerPromiseResult {
webworkerPromise: typeof WebworkerPromise
worker: Worker
interface ItkWorker extends Worker {
workerProxy: WorkerProxy
originalTerminate: () => void
}

interface itkWorker extends Worker {
workerPromise?: typeof WebworkerPromise
interface createWorkerProxyResult {
workerProxy: WorkerProxy
worker: ItkWorker
}

function workerToWorkerProxy (worker: Worker): createWorkerProxyResult {
const workerProxy = Comlink.wrap(worker) as WorkerProxy
const itkWebWorker = worker as ItkWorker
itkWebWorker.workerProxy = workerProxy
itkWebWorker.originalTerminate = itkWebWorker.terminate
itkWebWorker.terminate = () => {
itkWebWorker.workerProxy[Comlink.releaseProxy]()
itkWebWorker.originalTerminate()
}
return { workerProxy, worker: itkWebWorker }
}

// Internal function to create a web worker promise
async function createWebWorkerPromise (existingWorker: Worker | null, pipelineWorkerUrl?: string | null): Promise<createWebWorkerPromiseResult> {
let workerPromise: typeof WebworkerPromise
async function createWorkerProxy (existingWorker: Worker | null, pipelineWorkerUrl?: string | null): Promise<createWorkerProxyResult> {
let workerProxy: WorkerProxy
if (existingWorker != null) {
// See if we have a worker promise attached the worker, if so reuse it. This ensures
// that we can safely reuse the worker without issues.
const itkWebWorker = existingWorker as itkWorker
if (itkWebWorker.workerPromise !== undefined) {
workerPromise = itkWebWorker.workerPromise
const itkWebWorker = existingWorker as ItkWorker
if (itkWebWorker.workerProxy !== undefined) {
workerProxy = itkWebWorker.workerProxy
return { workerProxy, worker: itkWebWorker }
} else {
workerPromise = new WebworkerPromise(existingWorker)
return workerToWorkerProxy(existingWorker)
}

return await Promise.resolve({ webworkerPromise: workerPromise, worker: existingWorker })
}

const workerUrl = typeof pipelineWorkerUrl === 'undefined' ? config.pipelineWorkerUrl : pipelineWorkerUrl
Expand Down Expand Up @@ -61,13 +74,7 @@ async function createWebWorkerPromise (existingWorker: Worker | null, pipelineWo
}
}

const webworkerPromise = new WebworkerPromise(worker)

// Attach the worker promise to the worker, so if the worker is reused we can
// also reuse the the worker promise.
const itkWebWorker = (worker as itkWorker)
itkWebWorker.workerPromise = webworkerPromise
return { webworkerPromise, worker: itkWebWorker }
return workerToWorkerProxy(worker)
}

export default createWebWorkerPromise
export default createWorkerProxy
File renamed without changes.
4 changes: 2 additions & 2 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@ export { default as WorkerPool } from './WorkerPool.js'
export { default as WorkerPoolFunction } from './WorkerPoolFunction.js'
export { default as WorkerPoolProgressCallback } from './WorkerPoolProgressCallback.js'
export { default as WorkerPoolRunTasksResult } from './WorkerPoolRunTasksResult.js'
export { default as getTransferables } from './getTransferables.js'
export { default as createWebWorkerPromise } from './createWebWorkerPromise.js'
export { default as getTransferables } from './get-transferables.js'
export { default as createWorkerProxy } from './create-worker-proxy.js'
77 changes: 50 additions & 27 deletions src/core/web-workers/itk-wasm-pipeline.worker.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,58 @@
import registerWebworker from 'webworker-promise/lib/register.js'
import * as Comlink from 'comlink'

import ITKConfig from './itk-config.js'
import PipelineOutput from '../../pipeline/PipelineOutput.js'
import PipelineInput from '../../pipeline/PipelineInput.js'
import MeshToPolyDataPipelineResult from './mesh-to-poly-data-pipeline-result.js'
import PolyDataToMeshPipelineResult from './poly-data-to-mesh-pipeline-result.js'
import ReadImagePipelineResult from './read-image-pipeline-result.js'
import WriteImagePipelineResult from './write-image-pipeline-result.js'
import ReadMeshPipelineResult from './read-mesh-pipeline-result.js'
import WriteMeshPipelineResult from './write-mesh-pipeline-result.js'
import RunPipelineWorkerResult from './run-pipeline-worker-result.js'
import loadPipelineModule from './load-pipeline-module.js'
import loadImageIOPipelineModule from './load-image-io-pipeline-module.js'
import loadMeshIOPipelineModule from './load-mesh-io-pipeline-module.js'
import runPipeline from './run-pipeline.js'
import RunPipelineInput from './run-pipeline-input.js'
import IOInput from './io-input.js'

registerWebworker(async function (input: RunPipelineInput | IOInput) {
let pipelineModule = null
if (input.operation === 'runPipeline') {
const pipelineBaseUrl = typeof input.config[input.pipelineBaseUrl] === 'undefined' ? input.pipelineBaseUrl : input.config[input.pipelineBaseUrl] as string
pipelineModule = await loadPipelineModule(input.pipelinePath, pipelineBaseUrl)
} else if (input.operation === 'readImage') {
pipelineModule = await loadImageIOPipelineModule(input as IOInput, '-read-image')
} else if (input.operation === 'writeImage') {
pipelineModule = await loadImageIOPipelineModule(input as IOInput, '-write-image')
} else if (input.operation === 'readMesh') {
pipelineModule = await loadMeshIOPipelineModule(input as IOInput, '-read-mesh')
} else if (input.operation === 'writeMesh') {
pipelineModule = await loadMeshIOPipelineModule(input as IOInput, '-write-mesh')
} else if (input.operation === 'meshToPolyData') {
pipelineModule = await loadPipelineModule('mesh-to-polydata', input.config.meshIOUrl)
} else if (input.operation === 'polyDataToMesh') {
pipelineModule = await loadPipelineModule('polydata-to-mesh', input.config.meshIOUrl)
} else if (input.operation === 'readDICOMImageSeries') {
pipelineModule = await loadPipelineModule('read-image-dicom-file-series', input.config.imageIOUrl)
} else if (input.operation === 'readDICOMTags') {
pipelineModule = await loadPipelineModule('read-dicom-tags', input.config.imageIOUrl)
} else {
throw new Error('Unknown worker operation')
}
return runPipeline(pipelineModule, input.args, input.outputs, input.inputs)
})
const workerOperations = {
meshToPolyData: async function (config: ITKConfig, args: string[], outputs: PipelineOutput[], inputs: PipelineInput[]): Promise<MeshToPolyDataPipelineResult> {
const pipelineModule = await loadPipelineModule('mesh-to-polydata', config.meshIOUrl)
return runPipeline(pipelineModule, args, outputs, inputs)
},

polyDataToMesh: async function (config: ITKConfig, args: string[], outputs: PipelineOutput[], inputs: PipelineInput[]): Promise<PolyDataToMeshPipelineResult> {
const pipelineModule = await loadPipelineModule('polydata-to-mesh', config.meshIOUrl)
return runPipeline(pipelineModule, args, outputs, inputs)
},

readImage: async function (config: ITKConfig, mimeType: string, fileName: string, args: string[], outputs: PipelineOutput[], inputs: PipelineInput[]): Promise<ReadImagePipelineResult> {
const pipelineModule = await loadImageIOPipelineModule({ fileName, mimeType, config, args, outputs, inputs } as IOInput, '-read-image')
return runPipeline(pipelineModule, args, outputs, inputs)
},

writeImage: async function (config: ITKConfig, mimeType: string, fileName: string, args: string[], outputs: PipelineOutput[], inputs: PipelineInput[]): Promise<WriteImagePipelineResult> {
const pipelineModule = await loadImageIOPipelineModule({ fileName, mimeType, config, args, outputs, inputs } as IOInput, '-write-image')
return runPipeline(pipelineModule, args, outputs, inputs)
},

readMesh: async function (config: ITKConfig, mimeType: string, fileName: string, args: string[], outputs: PipelineOutput[], inputs: PipelineInput[]): Promise<ReadMeshPipelineResult> {
const pipelineModule = await loadMeshIOPipelineModule({ fileName, mimeType, config, args, outputs, inputs } as IOInput, '-read-mesh')
return runPipeline(pipelineModule, args, outputs, inputs)
},

writeMesh: async function (config: ITKConfig, mimeType: string, fileName: string, args: string[], outputs: PipelineOutput[], inputs: PipelineInput[]): Promise<WriteMeshPipelineResult> {
const pipelineModule = await loadMeshIOPipelineModule({ fileName, mimeType, config, args, outputs, inputs } as IOInput, '-write-mesh')
return runPipeline(pipelineModule, args, outputs, inputs)
},

runPipeline: async function (config: ITKConfig, pipelinePath: string, pipelineBaseUrl: string, args: string[], outputs: PipelineOutput[] | null, inputs: PipelineInput[] | null): Promise<RunPipelineWorkerResult> {
const resolvedPipelineBaseUrl = typeof config[pipelineBaseUrl] === 'undefined' ? pipelineBaseUrl : config[pipelineBaseUrl] as string
const pipelineModule = await loadPipelineModule(pipelinePath, resolvedPipelineBaseUrl)
return runPipeline(pipelineModule, args, outputs, inputs)
},
}

Comlink.expose(workerOperations)
5 changes: 5 additions & 0 deletions src/core/web-workers/mesh-to-poly-data-pipeline-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
interface MeshToPolyDataPipelineResult {
outputs: any[]
}

export default MeshToPolyDataPipelineResult
5 changes: 5 additions & 0 deletions src/core/web-workers/poly-data-to-mesh-pipeline-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
interface PolyDataToMeshPipelineResult {
outputs: any[]
}

export default PolyDataToMeshPipelineResult
7 changes: 7 additions & 0 deletions src/core/web-workers/read-image-pipeline-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
interface ReadImagePipelineResult {
stdout: string
stderr: string
outputs: any[]
}

export default ReadImagePipelineResult
7 changes: 7 additions & 0 deletions src/core/web-workers/read-mesh-pipeline-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
interface ReadMeshPipelineResult {
stdout: string
stderr: string
outputs: any[]
}

export default ReadMeshPipelineResult
9 changes: 9 additions & 0 deletions src/core/web-workers/run-pipeline-worker-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import PipelineOutput from '../../pipeline/PipelineOutput.js'
interface RunPipelineWorkerResult {
returnValue: number
stdout: string
stderr: string
outputs: PipelineOutput[]
}

export default RunPipelineWorkerResult
11 changes: 4 additions & 7 deletions src/core/web-workers/run-pipeline.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import registerWebworker from 'webworker-promise/lib/register.js'
import * as Comlink from 'comlink'

import PipelineEmscriptenModule from '../../pipeline/PipelineEmscriptenModule.js'
import runPipelineEmscripten from '../../pipeline/internal/runPipelineEmscripten.js'
import IOTypes from '../IOTypes.js'
import getTransferables from '../getTransferables.js'
import getTransferables from '../get-transferables.js'

import PipelineInput from '../../pipeline/PipelineInput.js'
import PipelineOutput from '../../pipeline/PipelineOutput.js'
Expand All @@ -17,7 +17,7 @@ import imageTransferables from '../internal/imageTransferables.js'
import meshTransferables from '../internal/meshTransferables.js'
import polyDataTransferables from '../internal/polyDataTransferables.js'

async function runPipeline(pipelineModule: PipelineEmscriptenModule, args: string[], outputs: PipelineOutput[], inputs: PipelineInput[]) {
async function runPipeline(pipelineModule: PipelineEmscriptenModule, args: string[], outputs: PipelineOutput[] | null, inputs: PipelineInput[] | null) {
const result = runPipelineEmscripten(pipelineModule, args, outputs, inputs)

const transferables: (ArrayBuffer | TypedArray | null)[] = []
Expand Down Expand Up @@ -53,10 +53,7 @@ async function runPipeline(pipelineModule: PipelineEmscriptenModule, args: strin
})
}

return new registerWebworker.TransferableResponse(
result,
getTransferables(transferables)
)
return Comlink.transfer(result, getTransferables(transferables))
}

export default runPipeline
2 changes: 1 addition & 1 deletion src/core/web-workers/web-worker-input.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ITKConfig from './itk-config.js'

interface WebWorkerInput {
operation: 'runPipeline' | 'runPolyDataIOPipeline' | 'readImage' | 'writeImage' | 'readMesh' | 'writeMesh' | 'meshToPolyData' | 'polyDataToMesh' | 'readDICOMImageSeries' | 'readDICOMTags'
operation: 'runPipeline' | 'runPolyDataIOPipeline' | 'readImage' | 'writeImage' | 'readMesh' | 'writeMesh' | 'meshToPolyData' | 'polyDataToMesh'
config: ITKConfig
}

Expand Down
22 changes: 22 additions & 0 deletions src/core/web-workers/worker-operations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import ITKConfig from './itk-config.js'
import PipelineOutput from '../../pipeline/PipelineOutput.js'
import PipelineInput from '../../pipeline/PipelineInput.js'
import MeshToPolyDataPipelineResult from './mesh-to-poly-data-pipeline-result.js'
import PolyDataToMeshPipelineResult from './poly-data-to-mesh-pipeline-result.js'
import ReadImagePipelineResult from './read-image-pipeline-result.js'
import WriteImagePipelineResult from './write-image-pipeline-result.js'
import ReadMeshPipelineResult from './read-mesh-pipeline-result.js'
import WriteMeshPipelineResult from './write-mesh-pipeline-result.js'
import RunPipelineWorkerResult from './run-pipeline-worker-result.js'

interface WorkerOperations {
meshToPolyData: (config: ITKConfig, args: string[], outputs: PipelineOutput[], inputs: PipelineInput[]) => MeshToPolyDataPipelineResult
polyDataToMesh: (config: ITKConfig, args: string[], outputs: PipelineOutput[], inputs: PipelineInput[]) => PolyDataToMeshPipelineResult
readImage: (config: ITKConfig, mimeType: string, fileName: string, args: string[], outputs: PipelineOutput[], inputs: PipelineInput[]) => ReadImagePipelineResult
writeImage: (config: ITKConfig, mimeType: string, fileName: string, args: string[], outputs: PipelineOutput[], inputs: PipelineInput[]) => WriteImagePipelineResult
readMesh: (config: ITKConfig, mimeType: string, fileName: string, args: string[], outputs: PipelineOutput[], inputs: PipelineInput[]) => ReadMeshPipelineResult
writeMesh: (config: ITKConfig, mimeType: string, fileName: string, args: string[], outputs: PipelineOutput[], inputs: PipelineInput[]) => WriteMeshPipelineResult
runPipeline: (config: ITKConfig, pipelinePath: string, pipelineBaseUrl: string, args: string[], outputs: PipelineOutput[] | null, inputs: PipelineInput[] | null) => RunPipelineWorkerResult
}

export default WorkerOperations
7 changes: 7 additions & 0 deletions src/core/web-workers/worker-proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as Comlink from 'comlink'

import WorkerOperations from './worker-operations.js'

type WorkerProxy = Comlink.Remote<WorkerOperations>

export default WorkerProxy
7 changes: 7 additions & 0 deletions src/core/web-workers/write-image-pipeline-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
interface WriteImagePipelineResult {
stdout: string
stderr: string
outputs: any[]
}

export default WriteImagePipelineResult
7 changes: 7 additions & 0 deletions src/core/web-workers/write-mesh-pipeline-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
interface WriteMeshPipelineResult {
stdout: string
stderr: string
outputs: any[]
}

export default WriteMeshPipelineResult
27 changes: 11 additions & 16 deletions src/io/meshToPolyData.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import createWebWorkerPromise from '../core/createWebWorkerPromise.js'
import * as Comlink from 'comlink'

import createWorkerProxy from '../core/create-worker-proxy.js'
import Mesh from '../core/interface-types/mesh.js'
import InterfaceTypes from '../core/InterfaceTypes.js'
import PolyData from '../core/interface-types/poly-data.js'
import meshTransferables from '../core/internal/meshTransferables.js'
import PipelineInput from '../pipeline/PipelineInput.js'
import MeshToPolyDataPipelineResult from '../core/web-workers/mesh-to-poly-data-pipeline-result.js'
import config from '../itkConfig.js'
import getTransferables from '../core/getTransferables.js'
import getTransferables from '../core/get-transferables.js'

async function meshToPolyData (webWorker: Worker | null, mesh: Mesh): Promise<{ polyData: PolyData, webWorker: Worker }> {
let worker = webWorker
const { webworkerPromise, worker: usedWorker } = await createWebWorkerPromise(worker, null)
const { workerProxy, worker: usedWorker } = await createWorkerProxy(worker, null)
worker = usedWorker

const args = ['0', '0', '--memory-io']
Expand All @@ -21,20 +24,12 @@ async function meshToPolyData (webWorker: Worker | null, mesh: Mesh): Promise<{
] as PipelineInput[]

const transferables = meshTransferables(mesh)
interface RunMeshToPolyDataPipelineResult {
outputs: any[]
}

const result: RunMeshToPolyDataPipelineResult = await webworkerPromise.postMessage(
{
operation: 'meshToPolyData',
config: config,
pipelinePath: 'mesh-to-polydata', // placeholder
args,
outputs,
inputs
},
getTransferables(transferables)
const result: MeshToPolyDataPipelineResult = await workerProxy.meshToPolyData(
config,
args,
outputs,
Comlink.transfer(inputs, getTransferables(transferables))
)
return { polyData: result.outputs[0].data as PolyData, webWorker: worker }
}
Expand Down
Loading

0 comments on commit 99670a7

Please sign in to comment.