From dbc7b184581950f0cbf96b1b353c6d5122e86551 Mon Sep 17 00:00:00 2001 From: fallenoak Date: Thu, 28 Dec 2023 16:45:56 -0600 Subject: [PATCH] feat: add SceneWorker (#12) --- src/lib/SceneWorker.ts | 80 ++++++++++++++++++++++++++++++++++++++++++ src/lib/index.ts | 1 + 2 files changed, 81 insertions(+) create mode 100644 src/lib/SceneWorker.ts diff --git a/src/lib/SceneWorker.ts b/src/lib/SceneWorker.ts new file mode 100644 index 0000000..62da20b --- /dev/null +++ b/src/lib/SceneWorker.ts @@ -0,0 +1,80 @@ +const workerMessageHandler = (event: MessageEvent) => { + const id: number = event.data.id; + const functionName: string = event.data.functionName; + const functionArgs: any[] = event.data.functionArgs; + const transfer: Transferable[] = []; + + try { + const fn = self[functionName]; + const result = fn(...functionArgs); + + if (ArrayBuffer.isView(result)) { + transfer.push(result.buffer); + } else if (result instanceof ArrayBuffer) { + transfer.push(result); + } + + self.postMessage({ id, result, success: true }, { transfer }); + } catch (error) { + self.postMessage({ id, result: error, success: false }); + } +}; + +const contextToInlineUrl = (context: Record) => { + const initializer = []; + for (const [rawKey, rawValue] of Object.entries(context)) { + let key = `'${rawKey.toString()}'`; + + let value = rawValue; + if (typeof rawValue === 'function') { + value = rawValue.toString(); + } else if (typeof rawValue === 'string' || typeof rawValue === 'object') { + value = JSON.stringify(rawValue); + } + + initializer.push(`self[${key}] = `, value, ';', '\n\n'); + } + + initializer.push(`self['onmessage'] = `, workerMessageHandler.toString(), ';', '\n'); + + const blob = new Blob(initializer, { type: 'text/javascript' }); + return URL.createObjectURL(blob); +}; + +class SceneWorker { + #worker: Worker; + #pending = new Map void; reject: (reason: any) => void }>(); + #nextId = 0; + + constructor(name: string, context: Record) { + this.#worker = new Worker(contextToInlineUrl(context), { name }); + + this.#worker.onmessage = (event: MessageEvent) => { + const id: number = event.data.id; + const pending = this.#pending.get(id); + + if (event.data.success) { + pending.resolve(event.data.result); + } else { + pending.reject(event.data.result); + } + + this.#pending.delete(id); + }; + } + + call(name: string, ...args: any[]): Promise { + const id = this.#nextId++; + + const promise = new Promise((resolve, reject) => { + this.#pending.set(id, { resolve, reject }); + }); + + this.#worker.postMessage({ id, functionName: name, functionArgs: args }); + + return promise; + } +} + +export default SceneWorker; +export { SceneWorker }; diff --git a/src/lib/index.ts b/src/lib/index.ts index 50f2b92..4b0728e 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -3,3 +3,4 @@ export * from './controls/OrbitControls.js'; export * from './AssetManager.js'; export * from './FormatManager.js'; export * from './TextureManager.js'; +export * from './SceneWorker.js';