From db97a62046672c3e88ae5b50fe7a81a8ec566c62 Mon Sep 17 00:00:00 2001 From: modeco80 Date: Tue, 23 Apr 2024 19:43:23 -0400 Subject: [PATCH] move jpeg encoding to a worker thread pool this also switches cvmts back to building with tsc, mostly because I couldn't get parcel's worker interop to work at all. --- cvmts/package.json | 10 ++----- cvmts/src/JPEGEncoderWorker.ts | 16 +++++++++++ cvmts/src/WSServer.ts | 50 +++++++++++++++++++--------------- cvmts/tsconfig.json | 8 +++++- yarn.lock | 44 ++++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 31 deletions(-) create mode 100644 cvmts/src/JPEGEncoderWorker.ts mode change 120000 => 100644 cvmts/tsconfig.json diff --git a/cvmts/package.json b/cvmts/package.json index fd626fc..909254f 100644 --- a/cvmts/package.json +++ b/cvmts/package.json @@ -5,22 +5,17 @@ "type": "module", "main": "dist/index.js", "scripts": { - "build": "parcel build src/index.ts --target node", + "build": "tsc -outDir dist -rootDir src/", "serve": "node dist/index.js" }, "author": "Elijah R, modeco80", "license": "GPL-3.0", - "targets": { - "node": { - "context": "node", - "outputFormat": "esmodule" - } - }, "dependencies": { "@computernewb/jpeg-turbo": "*", "@cvmts/qemu": "*", "execa": "^8.0.1", "mnemonist": "^0.39.5", + "piscina": "^4.4.0", "sharp": "^0.33.3", "toml": "^3.0.0", "ws": "^8.14.1" @@ -28,7 +23,6 @@ "devDependencies": { "@types/node": "^20.12.5", "@types/ws": "^8.5.5", - "parcel": "^2.12.0", "prettier": "^3.2.5", "typescript": "^5.4.4" } diff --git a/cvmts/src/JPEGEncoderWorker.ts b/cvmts/src/JPEGEncoderWorker.ts new file mode 100644 index 0000000..25bc123 --- /dev/null +++ b/cvmts/src/JPEGEncoderWorker.ts @@ -0,0 +1,16 @@ + +import jpegTurbo from "@computernewb/jpeg-turbo"; +import Piscina from "piscina"; + +export default async (opts: any) => { + let res = await jpegTurbo.compress(opts.buffer, { + format: jpegTurbo.FORMAT_RGBA, + width: opts.width, + height: opts.height, + subsampling: jpegTurbo.SAMP_422, + stride: opts.stride, + quality: opts.quality + }); + + return Piscina.move(res); +} \ No newline at end of file diff --git a/cvmts/src/WSServer.ts b/cvmts/src/WSServer.ts index 101d454..a789dc5 100644 --- a/cvmts/src/WSServer.ts +++ b/cvmts/src/WSServer.ts @@ -13,22 +13,20 @@ import { isIP } from 'node:net'; import { QemuVM, QemuVmDefinition } from '@cvmts/qemu'; import { IPData, IPDataManager } from './IPData.js'; import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'url'; -import path from 'path'; +import path from 'node:path'; import AuthManager from './AuthManager.js'; import { Size, Rect, Logger } from '@cvmts/shared'; -import jpegTurbo from "@computernewb/jpeg-turbo"; import sharp from 'sharp'; +import Piscina from 'piscina'; -// @ts-expect-error (I know, but this is already ugly) -// really wish I didn't have to do it like this.. -const __dirname = fileURLToPath(new __parcel__URL__('..')); +// Instead of strange hacks we can just use nodejs provided +// import.meta properties, which have existed since LTS if not before +const __filename = import.meta.filename; +const __dirname = import.meta.dirname; +const kCVMTSAssetsRoot = path.resolve(__dirname, '../../assets'); -console.log(__dirname); - -// ejla this exist. Useing it. type ChatHistory = { user: string, msg: string @@ -46,19 +44,27 @@ function GetRawSharpOptions(size: Size): sharp.CreateRaw { } } +const kJpegPool = new Piscina({ + filename: path.join(import.meta.dirname + '/JPEGEncoderWorker.js'), + minThreads: 4, + maxThreads: 4 +}); + async function EncodeJpeg(canvas: Buffer, displaySize: Size, rect: Rect): Promise { let offset = (rect.y * displaySize.width + rect.x) * 4; - //console.log('encoding rect', rect, 'with byteoffset', offset, '(size ', displaySize, ')'); + let res = await kJpegPool.run({ + buffer: canvas.subarray(offset), + width: rect.width, + height: rect.height, + stride: displaySize.width, + quality: kJpegQuality + }); - return jpegTurbo.compress(canvas.subarray(offset), { - format: jpegTurbo.FORMAT_RGBA, - width: rect.width, - height: rect.height, - subsampling: jpegTurbo.SAMP_422, - stride: displaySize.width, - quality: kJpegQuality - }); + + // have to manually turn it back into a buffer because + // Piscina for some reason turns it into a Uint8Array + return Buffer.from(res); } export default class WSServer { @@ -125,8 +131,8 @@ export default class WSServer { this.voteCooldown = 0; this.turnsAllowed = true; this.screenHidden = false; - this.screenHiddenImg = readFileSync(__dirname + "/../assets/screenhidden.jpeg").toString("base64"); - this.screenHiddenThumb = readFileSync(__dirname + "/../assets/screenhiddenthumb.jpeg").toString("base64"); + this.screenHiddenImg = readFileSync(path.join(kCVMTSAssetsRoot, "screenhidden.jpeg")).toString("base64"); + this.screenHiddenThumb = readFileSync(path.join(kCVMTSAssetsRoot, "screenhiddenthumb.jpeg")).toString("base64"); this.indefiniteTurn = null; this.ModPerms = Utilities.MakeModPerms(this.Config.collabvm.moderatorPermissions); @@ -937,7 +943,7 @@ export default class WSServer { // TODO: pass custom options to Sharp.resize() probably let out = await sharp(display.Buffer(), {raw: GetRawSharpOptions(display.Size())}) - .resize(400, 300) + .resize(400, 300, { fit: 'fill' }) .toFormat('jpeg') .toBuffer(); @@ -1000,4 +1006,4 @@ export default class WSServer { }); return {yes:yes,no:no}; } -} +} \ No newline at end of file diff --git a/cvmts/tsconfig.json b/cvmts/tsconfig.json deleted file mode 120000 index 4ec6ff6..0000000 --- a/cvmts/tsconfig.json +++ /dev/null @@ -1 +0,0 @@ -../tsconfig.json \ No newline at end of file diff --git a/cvmts/tsconfig.json b/cvmts/tsconfig.json new file mode 100644 index 0000000..baa2339 --- /dev/null +++ b/cvmts/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "include": [ "src/**/*" ], + "compilerOptions": { + "resolveJsonModule": true, + } +} diff --git a/yarn.lock b/yarn.lock index fe2b342..b8671e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -67,6 +67,7 @@ __metadata: execa: "npm:^8.0.1" mnemonist: "npm:^0.39.5" parcel: "npm:^2.12.0" + piscina: "npm:^4.4.0" prettier: "npm:^3.2.5" sharp: "npm:^0.33.3" toml: "npm:^3.0.0" @@ -3155,6 +3156,26 @@ __metadata: languageName: node linkType: hard +"nice-napi@npm:^1.0.2": + version: 1.0.2 + resolution: "nice-napi@npm:1.0.2" + dependencies: + node-addon-api: "npm:^3.0.0" + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.2.2" + conditions: "!os=win32" + languageName: node + linkType: hard + +"node-addon-api@npm:^3.0.0": + version: 3.2.1 + resolution: "node-addon-api@npm:3.2.1" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/41f21c9d12318875a2c429befd06070ce367065a3ef02952cfd4ea17ef69fa14012732f510b82b226e99c254da8d671847ea018cad785f839a5366e02dd56302 + languageName: node + linkType: hard + "node-addon-api@npm:^6.0.0, node-addon-api@npm:^6.1.0": version: 6.1.0 resolution: "node-addon-api@npm:6.1.0" @@ -3204,6 +3225,17 @@ __metadata: languageName: node linkType: hard +"node-gyp-build@npm:^4.2.2": + version: 4.8.0 + resolution: "node-gyp-build@npm:4.8.0" + bin: + node-gyp-build: bin.js + node-gyp-build-optional: optional.js + node-gyp-build-test: build-test.js + checksum: 10c0/85324be16f81f0235cbbc42e3eceaeb1b5ab94c8d8f5236755e1435b4908338c65a4e75f66ee343cbcb44ddf9b52a428755bec16dcd983295be4458d95c8e1ad + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 10.1.0 resolution: "node-gyp@npm:10.1.0" @@ -3408,6 +3440,18 @@ __metadata: languageName: node linkType: hard +"piscina@npm:^4.4.0": + version: 4.4.0 + resolution: "piscina@npm:4.4.0" + dependencies: + nice-napi: "npm:^1.0.2" + dependenciesMeta: + nice-napi: + optional: true + checksum: 10c0/df6c2a2b673b0633a625f8dfc32f4519155e74ee24e31be9e69d2937e76d6cec8640278b4a50195652a943cccf8c634ed406f08598933c57e959d242b5fe5d1d + languageName: node + linkType: hard + "postcss-value-parser@npm:^4.2.0": version: 4.2.0 resolution: "postcss-value-parser@npm:4.2.0"