img2num 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/dist/browser/__vite-browser-external-CSF28Lpk.js +1 -0
- package/dist/browser/img2num.js +179 -0
- package/dist/browser/wasmWorker-BVnzGxvB.js +2 -0
- package/{wasmWorker.js → dist/browser/wasmWorker.js} +31 -5
- package/dist/node/img2num.js +514 -0
- package/dist/node/index.js +2 -0
- package/dist/node/index.wasm +0 -0
- package/dist/node/wasmWorker-CpNpexYK.js +4471 -0
- package/dist/node/wasmWorker.js +198 -0
- package/dist/node/webgpu-BjVEVfI9.js +23 -0
- package/package.json +23 -13
- package/{safeWasmWrappers.js → src/safeWasmWrappers.js} +9 -7
- package/src/target/browser/worker.js +10 -0
- package/src/target/node/webgpu.js +35 -0
- package/src/target/node/worker.js +20 -0
- package/{wasmClient.js → src/wasmClient.js} +30 -15
- package/src/workers/wasmWorker.js +243 -0
- package/tsconfig.typedoc.json +23 -0
- package/vite.config.js +74 -0
- package/build-wasm/index.js +0 -14
- package/build-wasm/index.wasm +0 -0
- /package/{imageToUint8ClampedArray.js → src/imageToUint8ClampedArray.js} +0 -0
- /package/{index.js → src/index.js} +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { t as createImg2NumModule } from "./wasmWorker-CpNpexYK.js";
|
|
2
|
+
//#region src/workers/wasmWorker.js
|
|
3
|
+
/**
|
|
4
|
+
* @file wasmWorker.js
|
|
5
|
+
* @description
|
|
6
|
+
* Worker for calling WASM functions (Img2Num module) with proper memory handling.
|
|
7
|
+
*
|
|
8
|
+
* Notes:
|
|
9
|
+
* - WASM exposes its linear memory via typed array views such as HEAP32 (Int32) or HEAPU8 (byte).
|
|
10
|
+
* - When adding a new TypedArray type:
|
|
11
|
+
* 1. Add the corresponding HEAP view to EXPORTED_RUNTIME_METHODS in the Emscripten CMakeLists.txt.
|
|
12
|
+
* 2. Allocate memory with `_malloc`.
|
|
13
|
+
* 3. Copy data into the appropriate HEAP view. HEAP views are important because they allow the raw data to be read correctly.
|
|
14
|
+
* 4. After the call, read data back from the same HEAP view.
|
|
15
|
+
* 5. Free the allocated memory. This is important! We are using C-style memory since the code interfaces with the C ABI, so there is no GC.
|
|
16
|
+
* - This ensures JavaScript arrays correctly map to WASM memory.
|
|
17
|
+
*
|
|
18
|
+
* Important:
|
|
19
|
+
* - The `args` object passed to all convenience wrapper functions must match the order
|
|
20
|
+
* of the C++ function parameters exactly. For example:
|
|
21
|
+
* ```js
|
|
22
|
+
* args = { a: 1, b: 2 };
|
|
23
|
+
* ```
|
|
24
|
+
* corresponds to
|
|
25
|
+
* ```cpp
|
|
26
|
+
* int add(int a, int b);
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* - Async functions:
|
|
30
|
+
* WebGPU operations inside WASM can be asynchronous. Use Emscripten Asyncify
|
|
31
|
+
* (via `ccall` with `{ async: true }`) to properly pause and resume execution.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* self.postMessage({
|
|
35
|
+
* id: 1,
|
|
36
|
+
* funcName: "bilateral_filter_gpu",
|
|
37
|
+
* args: { input: myArray },
|
|
38
|
+
* bufferKeys: [{ key: "input", type: "Uint8Array" }],
|
|
39
|
+
* returnType: "void"
|
|
40
|
+
* });
|
|
41
|
+
*/
|
|
42
|
+
var wasmModule;
|
|
43
|
+
/**
|
|
44
|
+
* Promise that resolves when WASM module is ready.
|
|
45
|
+
* @type {Promise<void>}
|
|
46
|
+
*/
|
|
47
|
+
var readyPromise = createImg2NumModule().then((mod) => {
|
|
48
|
+
wasmModule = mod;
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* Handlers for allocating, reading, and freeing different WASM types.
|
|
52
|
+
* @type {Record<string, {alloc: Function, read: Function}>}
|
|
53
|
+
*/
|
|
54
|
+
var WASM_TYPES = {
|
|
55
|
+
void: {
|
|
56
|
+
alloc: () => null,
|
|
57
|
+
read: () => void 0
|
|
58
|
+
},
|
|
59
|
+
Int32Array: {
|
|
60
|
+
/**
|
|
61
|
+
* Allocate an Int32Array in WASM memory.
|
|
62
|
+
* @param {Int32Array} arr
|
|
63
|
+
* @returns {number} Pointer to allocated memory
|
|
64
|
+
*/
|
|
65
|
+
alloc: (arr) => {
|
|
66
|
+
const ptr = wasmModule._malloc(arr.byteLength);
|
|
67
|
+
wasmModule.HEAP32.set(arr, ptr >> 2);
|
|
68
|
+
return ptr;
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* Read an Int32Array from WASM memory.
|
|
72
|
+
* @param {number} ptr
|
|
73
|
+
* @param {number} len
|
|
74
|
+
* @returns {Int32Array}
|
|
75
|
+
*/
|
|
76
|
+
read: (ptr, len) => new Int32Array(wasmModule.HEAP32.buffer, ptr, len).slice()
|
|
77
|
+
},
|
|
78
|
+
Uint8Array: {
|
|
79
|
+
alloc: (arr) => {
|
|
80
|
+
const ptr = wasmModule._malloc(arr.byteLength);
|
|
81
|
+
wasmModule.HEAPU8.set(arr, ptr);
|
|
82
|
+
return ptr;
|
|
83
|
+
},
|
|
84
|
+
read: (ptr, len) => wasmModule.HEAPU8.slice(ptr, ptr + len)
|
|
85
|
+
},
|
|
86
|
+
Uint8ClampedArray: {
|
|
87
|
+
alloc: (arr) => {
|
|
88
|
+
const ptr = wasmModule._malloc(arr.byteLength);
|
|
89
|
+
wasmModule.HEAPU8.set(arr, ptr);
|
|
90
|
+
return ptr;
|
|
91
|
+
},
|
|
92
|
+
read: (ptr, len) => new Uint8ClampedArray(wasmModule.HEAPU8.slice(ptr, ptr + len))
|
|
93
|
+
},
|
|
94
|
+
string: {
|
|
95
|
+
/**
|
|
96
|
+
* Allocate a string in WASM memory.
|
|
97
|
+
* @param {string} str
|
|
98
|
+
* @returns {number} Pointer to allocated memory
|
|
99
|
+
*/
|
|
100
|
+
alloc: (str) => {
|
|
101
|
+
const len = wasmModule.lengthBytesUTF8(str) + 1;
|
|
102
|
+
const ptr = wasmModule._malloc(len);
|
|
103
|
+
wasmModule.stringToUTF8(str, ptr, len);
|
|
104
|
+
return ptr;
|
|
105
|
+
},
|
|
106
|
+
/**
|
|
107
|
+
* Read a string from WASM memory.
|
|
108
|
+
* @param {number} ptr
|
|
109
|
+
* @returns {string|null}
|
|
110
|
+
*/
|
|
111
|
+
read: (ptr) => ptr ? wasmModule.UTF8ToString(ptr) : null
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Call a WASM function via ccall, handling asyncify automatically.
|
|
116
|
+
* @param {string} funcName
|
|
117
|
+
* @param {Map<string, number>} argsMap - Map of argument names to WASM pointers or numbers
|
|
118
|
+
* @param {string} returnType - WASM return type (e.g., 'void', 'Int32Array', 'string')
|
|
119
|
+
* @returns {Promise<number>} Result pointer or numeric return value
|
|
120
|
+
*/
|
|
121
|
+
async function callWasm(funcName, argsMap, returnType) {
|
|
122
|
+
const argTypes = Array(argsMap.size).fill("number");
|
|
123
|
+
const retType = returnType !== "void" ? "number" : null;
|
|
124
|
+
return await wasmModule.ccall(funcName, retType, argTypes, [...argsMap.values()], { async: true });
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Handle messages from main thread.
|
|
128
|
+
* Expects `data` to contain:
|
|
129
|
+
* - id: unique message ID
|
|
130
|
+
* - funcName: WASM export to call
|
|
131
|
+
* - args: object of input arguments to WASM export
|
|
132
|
+
* - bufferKeys: array of {key, type} defining memory buffers for WASM export args - JS doesn't have pointers, so we must do this
|
|
133
|
+
* - returnType: expected return type of the WASM export
|
|
134
|
+
* @param {MessageEvent} event
|
|
135
|
+
*/
|
|
136
|
+
async function handleMessage(data) {
|
|
137
|
+
await readyPromise;
|
|
138
|
+
const { id, funcName, args, bufferKeys, returnType } = data;
|
|
139
|
+
const pointers = /* @__PURE__ */ new Map();
|
|
140
|
+
try {
|
|
141
|
+
if (!funcName) throw new Error("Missing funcName");
|
|
142
|
+
if (!args) throw new Error("Missing args");
|
|
143
|
+
if (!bufferKeys) throw new Error("Missing bufferKeys");
|
|
144
|
+
if (!returnType) throw new Error("Missing returnType");
|
|
145
|
+
const argsMap = new Map(Object.entries(args));
|
|
146
|
+
for (const { key, type } of bufferKeys) {
|
|
147
|
+
const handler = WASM_TYPES[type];
|
|
148
|
+
if (!handler) throw new Error(`Unsupported type: ${type}`);
|
|
149
|
+
const val = argsMap.get(key);
|
|
150
|
+
const ptr = handler.alloc(val);
|
|
151
|
+
pointers.set(key, {
|
|
152
|
+
ptr,
|
|
153
|
+
type,
|
|
154
|
+
length: val?.length
|
|
155
|
+
});
|
|
156
|
+
argsMap.set(key, ptr);
|
|
157
|
+
}
|
|
158
|
+
let result = await callWasm(funcName, argsMap, returnType);
|
|
159
|
+
/** @type {Record<string, any>} */
|
|
160
|
+
const output = Object.create(null);
|
|
161
|
+
for (const { key, type } of bufferKeys) {
|
|
162
|
+
const { ptr, length } = pointers.get(key);
|
|
163
|
+
output[key] = WASM_TYPES[type].read(ptr, length);
|
|
164
|
+
}
|
|
165
|
+
let returnValue = result;
|
|
166
|
+
if (returnType !== "void") returnValue = WASM_TYPES[returnType].read(result);
|
|
167
|
+
if (returnType === "string" && result) wasmModule._free(result);
|
|
168
|
+
globalThis.postMessage({
|
|
169
|
+
id,
|
|
170
|
+
output,
|
|
171
|
+
returnValue
|
|
172
|
+
});
|
|
173
|
+
} catch (error) {
|
|
174
|
+
globalThis.postMessage({
|
|
175
|
+
id,
|
|
176
|
+
error: error.message
|
|
177
|
+
});
|
|
178
|
+
} finally {
|
|
179
|
+
for (const { ptr } of pointers.values()) wasmModule._free(ptr);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
{
|
|
183
|
+
const { parentPort } = await import("node:worker_threads");
|
|
184
|
+
const { initWebGPU, destroyWebGPU } = await import("./webgpu-BjVEVfI9.js");
|
|
185
|
+
try {
|
|
186
|
+
await initWebGPU();
|
|
187
|
+
} catch (err) {
|
|
188
|
+
console.error(`[Img2Num node/worker.js] Error: ${err}`);
|
|
189
|
+
}
|
|
190
|
+
globalThis.postMessage = (data) => parentPort.postMessage(data);
|
|
191
|
+
parentPort.on("message", async (data) => {
|
|
192
|
+
await handleMessage(data);
|
|
193
|
+
await destroyWebGPU();
|
|
194
|
+
parentPort.close();
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { create } from "webgpu";
|
|
2
|
+
//#region src/target/node/webgpu.js
|
|
3
|
+
var gpuInitPromise;
|
|
4
|
+
async function initWebGPU() {
|
|
5
|
+
if (globalThis.navigator?.gpu) return globalThis.navigator.gpu;
|
|
6
|
+
if (!gpuInitPromise) gpuInitPromise = Promise.resolve().then(() => {
|
|
7
|
+
const nativeGpu = create(["backend=vulkan"]);
|
|
8
|
+
globalThis.navigator ??= {};
|
|
9
|
+
globalThis.navigator.gpu = nativeGpu;
|
|
10
|
+
return nativeGpu;
|
|
11
|
+
});
|
|
12
|
+
return gpuInitPromise;
|
|
13
|
+
}
|
|
14
|
+
async function destroyWebGPU() {
|
|
15
|
+
if (globalThis.navigator?.gpu) {
|
|
16
|
+
delete globalThis.navigator.gpu;
|
|
17
|
+
if (Object.keys(globalThis.navigator).length === 0) delete globalThis.navigator;
|
|
18
|
+
}
|
|
19
|
+
gpuInitPromise = null;
|
|
20
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { destroyWebGPU, initWebGPU };
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "img2num",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Img2Num is a raster vectorization library - it converts images to SVGs",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
5
8
|
"keywords": [
|
|
6
9
|
"img2num",
|
|
7
10
|
"computer-vision",
|
|
@@ -20,21 +23,28 @@
|
|
|
20
23
|
"license": "MIT",
|
|
21
24
|
"author": "Ryan-Millard",
|
|
22
25
|
"type": "module",
|
|
23
|
-
"main": "./
|
|
26
|
+
"main": "./dist/browser/img2num.js",
|
|
24
27
|
"engines": {
|
|
25
28
|
"node": ">=14"
|
|
26
29
|
},
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"node": "./dist/node/img2num.js",
|
|
33
|
+
"browser": "./dist/browser/img2num.js",
|
|
34
|
+
"import": "./dist/browser/img2num.js",
|
|
35
|
+
"default": "./dist/browser/img2num.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"optionalDependencies": {
|
|
39
|
+
"webgpu": "^0.4.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"cross-env": "^10.1.0",
|
|
43
|
+
"vite": "^8.0.14"
|
|
44
|
+
},
|
|
37
45
|
"scripts": {
|
|
38
|
-
"
|
|
46
|
+
"build:browser": "cross-env TARGET=browser vite build",
|
|
47
|
+
"build:node": "cross-env TARGET=node vite build",
|
|
48
|
+
"build": "pnpm build:browser && pnpm build:node"
|
|
39
49
|
}
|
|
40
50
|
}
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
* Each function handles memory management and exposes a JavaScript-friendly API.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { callWasm, initWasmWorker } from "./wasmClient.js";
|
|
20
20
|
|
|
21
21
|
// Ensure worker is ready as soon as this module is imported
|
|
22
|
-
initWasmWorker();
|
|
22
|
+
await initWasmWorker(); //it's an async function as of #433
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* @summary Apply a Gaussian blur to an image using FFT in WASM.
|
|
@@ -184,17 +184,18 @@ export const kmeans = async ({
|
|
|
184
184
|
* @param {number} options.width - Image width.
|
|
185
185
|
* @param {number} options.height - Image height.
|
|
186
186
|
* @param {number} [options.min_area=100] - Minimum area of a region to be considered a contour.
|
|
187
|
-
* @
|
|
187
|
+
* @param {number} [options.min_thickness=10] - Minimum thickness of a region to be considered a contour.
|
|
188
|
+
* @returns {Promise<{svg: string}>} Generated SVG.
|
|
188
189
|
* @throws {Error} If the WASM function fails or input labels are invalid.
|
|
189
190
|
* @example
|
|
190
191
|
* const { svg } = await findContours({ pixels, labels, width, height });
|
|
191
192
|
* @variation Converts labeled (from a clustering algorithm, e.g. K-Means) image into an SVG.
|
|
192
193
|
* @since 0.0.0
|
|
193
194
|
*/
|
|
194
|
-
export const findContours = async ({ pixels, labels, width, height, min_area = 100 }) => {
|
|
195
|
+
export const findContours = async ({ pixels, labels, width, height, min_area = 100, min_thickness = 10 }) => {
|
|
195
196
|
const result = await callWasm({
|
|
196
197
|
funcName: "labels_to_svg",
|
|
197
|
-
args: { pixels, labels, width, height, min_area },
|
|
198
|
+
args: { pixels, labels, width, height, min_area, min_thickness },
|
|
198
199
|
bufferKeys: [
|
|
199
200
|
{ key: "pixels", type: "Uint8ClampedArray" },
|
|
200
201
|
{ key: "labels", type: "Int32Array" },
|
|
@@ -221,6 +222,7 @@ export const findContours = async ({ pixels, labels, width, height, min_area = 1
|
|
|
221
222
|
* @param {number} [options.num_colors=16] - Number of color clusters.
|
|
222
223
|
* @param {number} [options.max_iter=100] - Maximum number of iterations.
|
|
223
224
|
* @param {number} [options.min_area=100] - Minimum area of a region to be considered a contour.
|
|
225
|
+
* @param {number} [options.min_thickness=10] - Minimum thickness of a region to be considered a contour.
|
|
224
226
|
* @param {number} [options.color_space=0] - Color space mode.
|
|
225
227
|
* @returns {Promise<{svg: string}>} Generated SVG.
|
|
226
228
|
* @throws {Error} If the WASM function fails or input labels are invalid.
|
|
@@ -229,10 +231,10 @@ export const findContours = async ({ pixels, labels, width, height, min_area = 1
|
|
|
229
231
|
* @variation Convert a raster image (e.g., PNG, JPG) into an SVG.
|
|
230
232
|
* @since 0.0.0
|
|
231
233
|
*/
|
|
232
|
-
export const imageToSvg = async ({ pixels, width, height, sigma_spatial = 3, sigma_range = 50, num_colors = 16, max_iter = 100, min_area = 100, color_space = 0 }) => {
|
|
234
|
+
export const imageToSvg = async ({ pixels, width, height, sigma_spatial = 3, sigma_range = 50, num_colors = 16, max_iter = 100, min_area = 100, min_thickness = 10, color_space = 0 }) => {
|
|
233
235
|
const result = await callWasm({
|
|
234
236
|
funcName: "image_to_svg",
|
|
235
|
-
args: { pixels, width, height, sigma_spatial, sigma_range, num_colors, max_iter, min_area, color_space },
|
|
237
|
+
args: { pixels, width, height, sigma_spatial, sigma_range, num_colors, max_iter, min_area, min_thickness, color_space },
|
|
236
238
|
bufferKeys: [{ key: "pixels", type: "Uint8ClampedArray" }],
|
|
237
239
|
returnType: "string",
|
|
238
240
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export async function createWorker() {
|
|
2
|
+
const worker = new Worker(new URL("@workers/wasmWorker.js", import.meta.url), { type: "module" });
|
|
3
|
+
|
|
4
|
+
return {
|
|
5
|
+
postMessage: (msg) => worker.postMessage(msg),
|
|
6
|
+
onMessage: (fn) => (worker.onmessage = (e) => fn(e.data)),
|
|
7
|
+
onError: (fn) => (worker.onerror = fn),
|
|
8
|
+
terminate: () => worker.terminate(),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { create } from "webgpu";
|
|
2
|
+
|
|
3
|
+
let gpuInitPromise;
|
|
4
|
+
|
|
5
|
+
export async function initWebGPU() {
|
|
6
|
+
if (globalThis.navigator?.gpu) return globalThis.navigator.gpu;
|
|
7
|
+
if (!gpuInitPromise) {
|
|
8
|
+
gpuInitPromise = Promise.resolve().then(() => {
|
|
9
|
+
const nativeGpu = create(["backend=vulkan"]);
|
|
10
|
+
globalThis.navigator ??= {};
|
|
11
|
+
globalThis.navigator.gpu = nativeGpu;
|
|
12
|
+
return nativeGpu;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return gpuInitPromise;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Drop-in companion deallocation handler
|
|
19
|
+
export async function destroyWebGPU() {
|
|
20
|
+
// 2. Sever the native reference links so Dawn can drop its ref counts
|
|
21
|
+
if (globalThis.navigator?.gpu) {
|
|
22
|
+
delete globalThis.navigator.gpu;
|
|
23
|
+
if (Object.keys(globalThis.navigator).length === 0) {
|
|
24
|
+
delete globalThis.navigator;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 3. Reset our internal module-level promise cache
|
|
29
|
+
gpuInitPromise = null;
|
|
30
|
+
|
|
31
|
+
// 4. CRITICAL: Yield to the event loop. This gives Dawn's native engine
|
|
32
|
+
// a small time window to notice the reference count hit 0, dismantle its
|
|
33
|
+
// background threads, and flush outstanding callbacks BEFORE the process terminates.
|
|
34
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
35
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
export async function createWorker() {
|
|
8
|
+
// Reference the worker by path relative to this file at runtime,
|
|
9
|
+
// not via import.meta.url which Vite will try to bundle/inline.
|
|
10
|
+
const workerPath = resolve(__dirname, "./wasmWorker.js");
|
|
11
|
+
|
|
12
|
+
const worker = new Worker(workerPath);
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
postMessage: (msg) => worker.postMessage(msg),
|
|
16
|
+
onMessage: (fn) => worker.on("message", fn),
|
|
17
|
+
onError: (fn) => worker.on("error", fn),
|
|
18
|
+
terminate: () => worker.terminate(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* This allows more granular control.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
import { createWorker } from "@__TARGET__/worker.js";
|
|
21
|
+
|
|
20
22
|
/**
|
|
21
23
|
* Worker instance that handles communication with the WASM module.
|
|
22
24
|
* @type {Worker | null}
|
|
@@ -42,6 +44,22 @@ const callbacks = new Map();
|
|
|
42
44
|
*/
|
|
43
45
|
let initialized = false;
|
|
44
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Handle response messages from the WASM worker.
|
|
49
|
+
* @param {{ id: number, output?: any, returnValue?: any, error?: string }} data
|
|
50
|
+
*/
|
|
51
|
+
function handleMessage(data) {
|
|
52
|
+
const cb = callbacks.get(data.id);
|
|
53
|
+
if (!cb) return;
|
|
54
|
+
callbacks.delete(data.id);
|
|
55
|
+
|
|
56
|
+
if (data.error) {
|
|
57
|
+
cb.reject(new Error(data.error));
|
|
58
|
+
} else {
|
|
59
|
+
cb.resolve({ output: data.output, returnValue: data.returnValue });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
45
63
|
/**
|
|
46
64
|
* @summary Initialize the WASM worker.
|
|
47
65
|
*
|
|
@@ -59,27 +77,21 @@ let initialized = false;
|
|
|
59
77
|
*
|
|
60
78
|
* @since 0.0.0
|
|
61
79
|
*/
|
|
62
|
-
export function initWasmWorker() {
|
|
63
|
-
if (initialized) return;
|
|
64
|
-
|
|
65
|
-
worker = new Worker(new URL("./wasmWorker.js", import.meta.url), { type: "module" });
|
|
66
80
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const cb = callbacks.get(id);
|
|
70
|
-
if (!cb) return;
|
|
81
|
+
export async function initWasmWorker() {
|
|
82
|
+
if (initialized) return;
|
|
71
83
|
|
|
72
|
-
|
|
73
|
-
callbacks.delete(id);
|
|
74
|
-
};
|
|
84
|
+
worker = await createWorker();
|
|
75
85
|
|
|
76
|
-
worker.
|
|
77
|
-
|
|
86
|
+
worker.onMessage(handleMessage);
|
|
87
|
+
worker.onError((event) => {
|
|
88
|
+
const output = event.message || "WASM worker error";
|
|
89
|
+
const err = new Error(`[Img2Num wasmClient] Error: ${output}`);
|
|
78
90
|
for (const [_id, cb] of callbacks) {
|
|
79
91
|
cb.reject(err);
|
|
80
92
|
}
|
|
81
93
|
callbacks.clear();
|
|
82
|
-
};
|
|
94
|
+
});
|
|
83
95
|
|
|
84
96
|
initialized = true;
|
|
85
97
|
}
|
|
@@ -120,10 +132,13 @@ export function initWasmWorker() {
|
|
|
120
132
|
*/
|
|
121
133
|
export async function callWasm({ funcName, args = {}, bufferKeys = [], returnType = "void" }) {
|
|
122
134
|
if (!initialized) throw new Error("WASM worker not initialized. Call initWasmWorker() first.");
|
|
135
|
+
|
|
123
136
|
const id = idCounter++;
|
|
137
|
+
|
|
124
138
|
return new Promise((resolve, reject) => {
|
|
125
139
|
callbacks.set(id, { resolve, reject });
|
|
126
|
-
|
|
140
|
+
|
|
141
|
+
worker.postMessage({ id, funcName, args, bufferKeys, returnType });
|
|
127
142
|
});
|
|
128
143
|
}
|
|
129
144
|
|