Merge pull request #1110 from styfle/libsquoosh-ts-remaining

This commit is contained in:
Surma
2021-08-10 12:38:11 +01:00
committed by GitHub
4 changed files with 185 additions and 71 deletions

View File

@@ -10,7 +10,7 @@ import { builtinModules } from 'module';
/** @type {import('rollup').RollupOptions} */ /** @type {import('rollup').RollupOptions} */
export default { export default {
input: 'src/index.js', input: 'src/index.ts',
output: { output: {
dir: 'build', dir: 'build',
format: 'cjs', format: 'cjs',

View File

@@ -9,6 +9,12 @@ import { cpus } from 'os';
hardwareConcurrency: cpus().length, hardwareConcurrency: cpus().length,
}; };
interface DecodeModule extends EmscriptenWasm.Module {
decode: (data: Uint8Array) => ImageData;
}
type DecodeModuleFactory = EmscriptenWasm.ModuleFactory<DecodeModule>;
interface RotateModuleInstance { interface RotateModuleInstance {
exports: { exports: {
memory: WebAssembly.Memory; memory: WebAssembly.Memory;
@@ -33,7 +39,7 @@ interface ResizeInstantiateOptions {
declare global { declare global {
// Needed for being able to use ImageData as type in codec types // Needed for being able to use ImageData as type in codec types
type ImageData = typeof import('./image_data.js'); type ImageData = import('./image_data.js').default;
// Needed for being able to assign to `globalThis.ImageData` // Needed for being able to assign to `globalThis.ImageData`
var ImageData: ImageData['constructor']; var ImageData: ImageData['constructor'];
} }
@@ -41,18 +47,21 @@ declare global {
import type { QuantizerModule } from '../../codecs/imagequant/imagequant.js'; import type { QuantizerModule } from '../../codecs/imagequant/imagequant.js';
// MozJPEG // MozJPEG
import type { MozJPEGModule as MozJPEGEncodeModule } from '../../codecs/mozjpeg/enc/mozjpeg_enc';
import mozEnc from '../../codecs/mozjpeg/enc/mozjpeg_node_enc.js'; import mozEnc from '../../codecs/mozjpeg/enc/mozjpeg_node_enc.js';
import mozEncWasm from 'asset-url:../../codecs/mozjpeg/enc/mozjpeg_node_enc.wasm'; import mozEncWasm from 'asset-url:../../codecs/mozjpeg/enc/mozjpeg_node_enc.wasm';
import mozDec from '../../codecs/mozjpeg/dec/mozjpeg_node_dec.js'; import mozDec from '../../codecs/mozjpeg/dec/mozjpeg_node_dec.js';
import mozDecWasm from 'asset-url:../../codecs/mozjpeg/dec/mozjpeg_node_dec.wasm'; import mozDecWasm from 'asset-url:../../codecs/mozjpeg/dec/mozjpeg_node_dec.wasm';
// WebP // WebP
import type { WebPModule as WebPEncodeModule } from '../../codecs/webp/enc/webp_enc';
import webpEnc from '../../codecs/webp/enc/webp_node_enc.js'; import webpEnc from '../../codecs/webp/enc/webp_node_enc.js';
import webpEncWasm from 'asset-url:../../codecs/webp/enc/webp_node_enc.wasm'; import webpEncWasm from 'asset-url:../../codecs/webp/enc/webp_node_enc.wasm';
import webpDec from '../../codecs/webp/dec/webp_node_dec.js'; import webpDec from '../../codecs/webp/dec/webp_node_dec.js';
import webpDecWasm from 'asset-url:../../codecs/webp/dec/webp_node_dec.wasm'; import webpDecWasm from 'asset-url:../../codecs/webp/dec/webp_node_dec.wasm';
// AVIF // AVIF
import type { AVIFModule as AVIFEncodeModule } from '../../codecs/avif/enc/avif_enc';
import avifEnc from '../../codecs/avif/enc/avif_node_enc.js'; import avifEnc from '../../codecs/avif/enc/avif_node_enc.js';
import avifEncWasm from 'asset-url:../../codecs/avif/enc/avif_node_enc.wasm'; import avifEncWasm from 'asset-url:../../codecs/avif/enc/avif_node_enc.wasm';
import avifEncMt from '../../codecs/avif/enc/avif_node_enc_mt.js'; import avifEncMt from '../../codecs/avif/enc/avif_node_enc_mt.js';
@@ -62,12 +71,14 @@ import avifDec from '../../codecs/avif/dec/avif_node_dec.js';
import avifDecWasm from 'asset-url:../../codecs/avif/dec/avif_node_dec.wasm'; import avifDecWasm from 'asset-url:../../codecs/avif/dec/avif_node_dec.wasm';
// JXL // JXL
import type { JXLModule as JXLEncodeModule } from '../../codecs/jxl/enc/jxl_enc';
import jxlEnc from '../../codecs/jxl/enc/jxl_node_enc.js'; import jxlEnc from '../../codecs/jxl/enc/jxl_node_enc.js';
import jxlEncWasm from 'asset-url:../../codecs/jxl/enc/jxl_node_enc.wasm'; import jxlEncWasm from 'asset-url:../../codecs/jxl/enc/jxl_node_enc.wasm';
import jxlDec from '../../codecs/jxl/dec/jxl_node_dec.js'; import jxlDec from '../../codecs/jxl/dec/jxl_node_dec.js';
import jxlDecWasm from 'asset-url:../../codecs/jxl/dec/jxl_node_dec.wasm'; import jxlDecWasm from 'asset-url:../../codecs/jxl/dec/jxl_node_dec.wasm';
// WP2 // WP2
import type { WP2Module as WP2EncodeModule } from '../../codecs/wp2/enc/wp2_enc';
import wp2Enc from '../../codecs/wp2/enc/wp2_node_enc.js'; import wp2Enc from '../../codecs/wp2/enc/wp2_node_enc.js';
import wp2EncWasm from 'asset-url:../../codecs/wp2/enc/wp2_node_enc.wasm'; import wp2EncWasm from 'asset-url:../../codecs/wp2/enc/wp2_node_enc.wasm';
import wp2Dec from '../../codecs/wp2/dec/wp2_node_dec.js'; import wp2Dec from '../../codecs/wp2/dec/wp2_node_dec.js';
@@ -257,15 +268,20 @@ export const preprocessors = {
numRotations: 0, numRotations: 0,
}, },
}, },
}; } as const;
export const codecs = { export const codecs = {
mozjpeg: { mozjpeg: {
name: 'MozJPEG', name: 'MozJPEG',
extension: 'jpg', extension: 'jpg',
detectors: [/^\xFF\xD8\xFF/], detectors: [/^\xFF\xD8\xFF/],
dec: () => instantiateEmscriptenWasm(mozDec, mozDecWasm), dec: () =>
enc: () => instantiateEmscriptenWasm(mozEnc, mozEncWasm), instantiateEmscriptenWasm(mozDec as DecodeModuleFactory, mozDecWasm),
enc: () =>
instantiateEmscriptenWasm(
mozEnc as EmscriptenWasm.ModuleFactory<MozJPEGEncodeModule>,
mozEncWasm,
),
defaultEncoderOptions: { defaultEncoderOptions: {
quality: 75, quality: 75,
baseline: false, baseline: false,
@@ -294,8 +310,13 @@ export const codecs = {
name: 'WebP', name: 'WebP',
extension: 'webp', extension: 'webp',
detectors: [/^RIFF....WEBPVP8[LX ]/s], detectors: [/^RIFF....WEBPVP8[LX ]/s],
dec: () => instantiateEmscriptenWasm(webpDec, webpDecWasm), dec: () =>
enc: () => instantiateEmscriptenWasm(webpEnc, webpEncWasm), instantiateEmscriptenWasm(webpDec as DecodeModuleFactory, webpDecWasm),
enc: () =>
instantiateEmscriptenWasm(
webpEnc as EmscriptenWasm.ModuleFactory<WebPEncodeModule>,
webpEncWasm,
),
defaultEncoderOptions: { defaultEncoderOptions: {
quality: 75, quality: 75,
target_size: 0, target_size: 0,
@@ -335,16 +356,20 @@ export const codecs = {
name: 'AVIF', name: 'AVIF',
extension: 'avif', extension: 'avif',
detectors: [/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/], detectors: [/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/],
dec: () => instantiateEmscriptenWasm(avifDec, avifDecWasm), dec: () =>
instantiateEmscriptenWasm(avifDec as DecodeModuleFactory, avifDecWasm),
enc: async () => { enc: async () => {
if (await threads()) { if (await threads()) {
return instantiateEmscriptenWasm( return instantiateEmscriptenWasm(
avifEncMt, avifEncMt as EmscriptenWasm.ModuleFactory<AVIFEncodeModule>,
avifEncMtWasm, avifEncMtWasm,
avifEncMtWorker, avifEncMtWorker,
); );
} }
return instantiateEmscriptenWasm(avifEnc, avifEncWasm); return instantiateEmscriptenWasm(
avifEnc as EmscriptenWasm.ModuleFactory<AVIFEncodeModule>,
avifEncWasm,
);
}, },
defaultEncoderOptions: { defaultEncoderOptions: {
cqLevel: 33, cqLevel: 33,
@@ -368,8 +393,13 @@ export const codecs = {
name: 'JPEG-XL', name: 'JPEG-XL',
extension: 'jxl', extension: 'jxl',
detectors: [/^\xff\x0a/], detectors: [/^\xff\x0a/],
dec: () => instantiateEmscriptenWasm(jxlDec, jxlDecWasm), dec: () =>
enc: () => instantiateEmscriptenWasm(jxlEnc, jxlEncWasm), instantiateEmscriptenWasm(jxlDec as DecodeModuleFactory, jxlDecWasm),
enc: () =>
instantiateEmscriptenWasm(
jxlEnc as EmscriptenWasm.ModuleFactory<JXLEncodeModule>,
jxlEncWasm,
),
defaultEncoderOptions: { defaultEncoderOptions: {
speed: 4, speed: 4,
quality: 75, quality: 75,
@@ -389,8 +419,13 @@ export const codecs = {
name: 'WebP2', name: 'WebP2',
extension: 'wp2', extension: 'wp2',
detectors: [/^\xF4\xFF\x6F/], detectors: [/^\xF4\xFF\x6F/],
dec: () => instantiateEmscriptenWasm(wp2Dec, wp2DecWasm), dec: () =>
enc: () => instantiateEmscriptenWasm(wp2Enc, wp2EncWasm), instantiateEmscriptenWasm(wp2Dec as DecodeModuleFactory, wp2DecWasm),
enc: () =>
instantiateEmscriptenWasm(
wp2Enc as EmscriptenWasm.ModuleFactory<WP2EncodeModule>,
wp2EncWasm,
),
defaultEncoderOptions: { defaultEncoderOptions: {
quality: 75, quality: 75,
alpha_quality: 75, alpha_quality: 75,
@@ -421,7 +456,7 @@ export const codecs = {
await oxipngPromise; await oxipngPromise;
return { return {
encode: ( encode: (
buffer: Uint8Array, buffer: Uint8ClampedArray | ArrayBuffer,
width: number, width: number,
height: number, height: number,
opts: { level: number }, opts: { level: number },
@@ -444,4 +479,4 @@ export const codecs = {
max: 1, max: 1,
}, },
}, },
}; } as const;

View File

@@ -5,10 +5,18 @@ import { promises as fsp } from 'fs';
import { codecs as encoders, preprocessors } from './codecs.js'; import { codecs as encoders, preprocessors } from './codecs.js';
import WorkerPool from './worker_pool.js'; import WorkerPool from './worker_pool.js';
import { autoOptimize } from './auto-optimizer.js'; import { autoOptimize } from './auto-optimizer.js';
import type ImageData from './image_data';
export { ImagePool, encoders, preprocessors }; export { ImagePool, encoders, preprocessors };
type EncoderKey = keyof typeof encoders;
type PreprocessorKey = keyof typeof preprocessors;
type FileLike = Buffer | ArrayBuffer | string | ArrayBufferView;
async function decodeFile({ file }) { async function decodeFile({
file,
}: {
file: FileLike;
}): Promise<{ bitmap: ImageData; size: number }> {
let buffer; let buffer;
if (ArrayBuffer.isView(file)) { if (ArrayBuffer.isView(file)) {
buffer = Buffer.from(file.buffer); buffer = Buffer.from(file.buffer);
@@ -16,8 +24,9 @@ async function decodeFile({ file }) {
} else if (file instanceof ArrayBuffer) { } else if (file instanceof ArrayBuffer) {
buffer = Buffer.from(file); buffer = Buffer.from(file);
file = 'Binary blob'; file = 'Binary blob';
} else if (file instanceof Buffer) { } else if ((file as unknown) instanceof Buffer) {
buffer = file; // TODO: Check why we need type assertions here.
buffer = (file as unknown) as Buffer;
file = 'Binary blob'; file = 'Binary blob';
} else if (typeof file === 'string') { } else if (typeof file === 'string') {
buffer = await fsp.readFile(file); buffer = await fsp.readFile(file);
@@ -28,23 +37,33 @@ async function decodeFile({ file }) {
const firstChunkString = Array.from(firstChunk) const firstChunkString = Array.from(firstChunk)
.map((v) => String.fromCodePoint(v)) .map((v) => String.fromCodePoint(v))
.join(''); .join('');
const key = Object.entries(encoders).find(([name, { detectors }]) => const key = Object.entries(encoders).find(([_name, { detectors }]) =>
detectors.some((detector) => detector.exec(firstChunkString)), detectors.some((detector) => detector.exec(firstChunkString)),
)?.[0]; )?.[0] as EncoderKey | undefined;
if (!key) { if (!key) {
throw Error(`${file} has an unsupported format`); throw Error(`${file} has an unsupported format`);
} }
const rgba = (await encoders[key].dec()).decode(new Uint8Array(buffer)); const encoder = encoders[key];
const mod = await encoder.dec();
const rgba = mod.decode(new Uint8Array(buffer));
return { return {
bitmap: rgba, bitmap: rgba,
size: buffer.length, size: buffer.length,
}; };
} }
async function preprocessImage({ preprocessorName, options, image }) { async function preprocessImage({
preprocessorName,
options,
image,
}: {
preprocessorName: PreprocessorKey;
options: any;
image: { bitmap: ImageData };
}) {
const preprocessor = await preprocessors[preprocessorName].instantiate(); const preprocessor = await preprocessors[preprocessorName].instantiate();
image.bitmap = await preprocessor( image.bitmap = await preprocessor(
image.bitmap.data, Uint8Array.from(image.bitmap.data),
image.bitmap.width, image.bitmap.width,
image.bitmap.height, image.bitmap.height,
options, options,
@@ -58,26 +77,39 @@ async function encodeImage({
encConfig, encConfig,
optimizerButteraugliTarget, optimizerButteraugliTarget,
maxOptimizerRounds, maxOptimizerRounds,
}: {
bitmap: ImageData;
encName: EncoderKey;
encConfig: any;
optimizerButteraugliTarget: number;
maxOptimizerRounds: number;
}) { }) {
let binary; let binary: Uint8Array;
let optionsUsed = encConfig; let optionsUsed = encConfig;
const encoder = await encoders[encName].enc(); const encoder = await encoders[encName].enc();
if (encConfig === 'auto') { if (encConfig === 'auto') {
const optionToOptimize = encoders[encName].autoOptimize.option; const optionToOptimize = encoders[encName].autoOptimize.option;
const decoder = await encoders[encName].dec(); const decoder = await encoders[encName].dec();
const encode = (bitmapIn, quality) => const encode = (bitmapIn: ImageData, quality: number) =>
encoder.encode( encoder.encode(
bitmapIn.data, bitmapIn.data,
bitmapIn.width, bitmapIn.width,
bitmapIn.height, bitmapIn.height,
Object.assign({}, encoders[encName].defaultEncoderOptions, { Object.assign({}, encoders[encName].defaultEncoderOptions as any, {
[optionToOptimize]: quality, [optionToOptimize]: quality,
}), }),
); );
const decode = (binary) => decoder.decode(binary); const decode = (binary: Uint8Array) => decoder.decode(binary);
const nonNullEncode = (bitmap: ImageData, quality: number): Uint8Array => {
const result = encode(bitmap, quality);
if (!result) {
throw new Error('There was an error while encoding');
}
return result;
};
const { binary: optimizedBinary, quality } = await autoOptimize( const { binary: optimizedBinary, quality } = await autoOptimize(
bitmapIn, bitmapIn,
encode, nonNullEncode,
decode, decode,
{ {
min: encoders[encName].autoOptimize.min, min: encoders[encName].autoOptimize.min,
@@ -92,12 +124,18 @@ async function encodeImage({
[optionToOptimize]: Math.round(quality * 10000) / 10000, [optionToOptimize]: Math.round(quality * 10000) / 10000,
}; };
} else { } else {
binary = encoder.encode( const result = encoder.encode(
bitmapIn.data.buffer, bitmapIn.data.buffer,
bitmapIn.width, bitmapIn.width,
bitmapIn.height, bitmapIn.height,
encConfig, encConfig,
); );
if (!result) {
throw new Error('There was an error while encoding');
}
binary = result;
} }
return { return {
optionsUsed, optionsUsed,
@@ -107,10 +145,15 @@ async function encodeImage({
}; };
} }
// both decoding and encoding go through the worker pool type EncodeParams = { operation: 'encode' } & Parameters<typeof encodeImage>[0];
function handleJob(params) { type DecodeParams = { operation: 'decode' } & Parameters<typeof decodeFile>[0];
const { operation } = params; type PreprocessParams = { operation: 'preprocess' } & Parameters<
switch (operation) { typeof preprocessImage
>[0];
type JobMessage = EncodeParams | DecodeParams | PreprocessParams;
function handleJob(params: JobMessage) {
switch (params.operation) {
case 'encode': case 'encode':
return encodeImage(params); return encodeImage(params);
case 'decode': case 'decode':
@@ -118,7 +161,7 @@ function handleJob(params) {
case 'preprocess': case 'preprocess':
return preprocessImage(params); return preprocessImage(params);
default: default:
throw Error(`Invalid job "${operation}"`); throw Error(`Invalid job "${(params as any).operation}"`);
} }
} }
@@ -126,7 +169,12 @@ function handleJob(params) {
* Represents an ingested image. * Represents an ingested image.
*/ */
class Image { class Image {
constructor(workerPool, file) { public file: FileLike;
public workerPool: WorkerPool<JobMessage, any>;
public decoded: Promise<{ bitmap: ImageData }>;
public encodedWith: { [key: string]: any };
constructor(workerPool: WorkerPool<JobMessage, any>, file: FileLike) {
this.file = file; this.file = file;
this.workerPool = workerPool; this.workerPool = workerPool;
this.decoded = workerPool.dispatchJob({ operation: 'decode', file }); this.decoded = workerPool.dispatchJob({ operation: 'decode', file });
@@ -143,14 +191,15 @@ class Image {
if (!Object.keys(preprocessors).includes(name)) { if (!Object.keys(preprocessors).includes(name)) {
throw Error(`Invalid preprocessor "${name}"`); throw Error(`Invalid preprocessor "${name}"`);
} }
const preprocessorName = name as PreprocessorKey;
const preprocessorOptions = Object.assign( const preprocessorOptions = Object.assign(
{}, {},
preprocessors[name].defaultOptions, preprocessors[preprocessorName].defaultOptions,
options, options,
); );
this.decoded = this.workerPool.dispatchJob({ this.decoded = this.workerPool.dispatchJob({
operation: 'preprocess', operation: 'preprocess',
preprocessorName: name, preprocessorName,
image: await this.decoded, image: await this.decoded,
options: preprocessorOptions, options: preprocessorOptions,
}); });
@@ -161,14 +210,22 @@ class Image {
/** /**
* Define one or several encoders to use on the image. * Define one or several encoders to use on the image.
* @param {object} encodeOptions - An object with encoders to use, and their settings. * @param {object} encodeOptions - An object with encoders to use, and their settings.
* @returns {Promise<undefined>} - A promise that resolves when the image has been encoded with all the specified encoders. * @returns {Promise<void>} - A promise that resolves when the image has been encoded with all the specified encoders.
*/ */
async encode(encodeOptions = {}) { async encode(
encodeOptions: {
optimizerButteraugliTarget?: number;
maxOptimizerRounds?: number;
} & {
[key in EncoderKey]?: any; // any is okay for now
} = {},
): Promise<void> {
const { bitmap } = await this.decoded; const { bitmap } = await this.decoded;
for (const [encName, options] of Object.entries(encodeOptions)) { for (const [name, options] of Object.entries(encodeOptions)) {
if (!Object.keys(encoders).includes(encName)) { if (!Object.keys(encoders).includes(name)) {
continue; continue;
} }
const encName = name as EncoderKey;
const encRef = encoders[encName]; const encRef = encoders[encName];
const encConfig = const encConfig =
typeof options === 'string' typeof options === 'string'
@@ -193,28 +250,30 @@ class Image {
* A pool where images can be ingested and squooshed. * A pool where images can be ingested and squooshed.
*/ */
class ImagePool { class ImagePool {
public workerPool: WorkerPool<JobMessage, any>;
/** /**
* Create a new pool. * Create a new pool.
* @param {number} [threads] - Number of concurrent image processes to run in the pool. Defaults to the number of CPU cores in the system. * @param {number} [threads] - Number of concurrent image processes to run in the pool. Defaults to the number of CPU cores in the system.
*/ */
constructor(threads) { constructor(threads: number) {
this.workerPool = new WorkerPool(threads || cpus().length, __filename); this.workerPool = new WorkerPool(threads || cpus().length, __filename);
} }
/** /**
* Ingest an image into the image pool. * Ingest an image into the image pool.
* @param {string | Buffer | URL | object} image - The image or path to the image that should be ingested and decoded. * @param {FileLike} image - The image or path to the image that should be ingested and decoded.
* @returns {Image} - A custom class reference to the decoded image. * @returns {Image} - A custom class reference to the decoded image.
*/ */
ingestImage(image) { ingestImage(image: FileLike): Image {
return new Image(this.workerPool, image); return new Image(this.workerPool, image);
} }
/** /**
* Closes the underlying image processing pipeline. The already processed images will still be there, but no new processing can start. * Closes the underlying image processing pipeline. The already processed images will still be there, but no new processing can start.
* @returns {Promise<undefined>} - A promise that resolves when the underlying pipeline has closed. * @returns {Promise<void>} - A promise that resolves when the underlying pipeline has closed.
*/ */
async close() { async close(): Promise<void> {
await this.workerPool.join(); await this.workerPool.join();
} }
} }

View File

@@ -7,26 +7,19 @@ function uuid() {
).join(''); ).join('');
} }
function jobPromise(worker, msg) { interface Job<I> {
return new Promise((resolve, reject) => { msg: I;
const id = uuid(); resolve: Function;
worker.postMessage({ msg, id }); reject: Function;
worker.on('message', function f({ error, result, id: rid }) {
if (rid !== id) {
return;
}
if (error) {
reject(error);
return;
}
worker.off('message', f);
resolve(result);
});
});
} }
export default class WorkerPool { export default class WorkerPool<I, O> {
constructor(numWorkers, workerFile) { public numWorkers: number;
public jobQueue: TransformStream<Job<I>, Job<I>>;
public workerQueue: TransformStream<Worker, Worker>;
public done: Promise<void>;
constructor(numWorkers: number, workerFile: string) {
this.numWorkers = numWorkers; this.numWorkers = numWorkers;
this.jobQueue = new TransformStream(); this.jobQueue = new TransformStream();
this.workerQueue = new TransformStream(); this.workerQueue = new TransformStream();
@@ -48,9 +41,14 @@ export default class WorkerPool {
await this._terminateAll(); await this._terminateAll();
return; return;
} }
if (!value) {
throw new Error('Reader did not return any value');
}
const { msg, resolve, reject } = value; const { msg, resolve, reject } = value;
const worker = await this._nextWorker(); const worker = await this._nextWorker();
jobPromise(worker, msg) this.jobPromise(worker, msg)
.then((result) => resolve(result)) .then((result) => resolve(result))
.catch((reason) => reject(reason)) .catch((reason) => reject(reason))
.finally(() => { .finally(() => {
@@ -66,6 +64,10 @@ export default class WorkerPool {
const reader = this.workerQueue.readable.getReader(); const reader = this.workerQueue.readable.getReader();
const { value } = await reader.read(); const { value } = await reader.read();
reader.releaseLock(); reader.releaseLock();
if (!value) {
throw new Error('No worker left');
}
return value; return value;
} }
@@ -82,7 +84,7 @@ export default class WorkerPool {
await this.done; await this.done;
} }
dispatchJob(msg) { dispatchJob(msg: I): Promise<O> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const writer = this.jobQueue.writable.getWriter(); const writer = this.jobQueue.writable.getWriter();
writer.write({ msg, resolve, reject }); writer.write({ msg, resolve, reject });
@@ -90,14 +92,32 @@ export default class WorkerPool {
}); });
} }
static useThisThreadAsWorker(cb) { private jobPromise(worker: Worker, msg: I) {
parentPort.on('message', async (data) => { return new Promise((resolve, reject) => {
const id = uuid();
worker.postMessage({ msg, id });
worker.on('message', function f({ error, result, id: rid }) {
if (rid !== id) {
return;
}
if (error) {
reject(error);
return;
}
worker.off('message', f);
resolve(result);
});
});
}
static useThisThreadAsWorker<I, O>(cb: (msg: I) => O) {
parentPort!.on('message', async (data) => {
const { msg, id } = data; const { msg, id } = data;
try { try {
const result = await cb(msg); const result = await cb(msg);
parentPort.postMessage({ result, id }); parentPort!.postMessage({ result, id });
} catch (e) { } catch (e) {
parentPort.postMessage({ error: e.message, id }); parentPort!.postMessage({ error: e.message, id });
} }
}); });
} }