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.
This commit is contained in:
modeco80
2024-04-23 19:43:23 -04:00
parent 59d5331b68
commit db97a62046
5 changed files with 97 additions and 31 deletions

View File

@@ -5,22 +5,17 @@
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"build": "parcel build src/index.ts --target node", "build": "tsc -outDir dist -rootDir src/",
"serve": "node dist/index.js" "serve": "node dist/index.js"
}, },
"author": "Elijah R, modeco80", "author": "Elijah R, modeco80",
"license": "GPL-3.0", "license": "GPL-3.0",
"targets": {
"node": {
"context": "node",
"outputFormat": "esmodule"
}
},
"dependencies": { "dependencies": {
"@computernewb/jpeg-turbo": "*", "@computernewb/jpeg-turbo": "*",
"@cvmts/qemu": "*", "@cvmts/qemu": "*",
"execa": "^8.0.1", "execa": "^8.0.1",
"mnemonist": "^0.39.5", "mnemonist": "^0.39.5",
"piscina": "^4.4.0",
"sharp": "^0.33.3", "sharp": "^0.33.3",
"toml": "^3.0.0", "toml": "^3.0.0",
"ws": "^8.14.1" "ws": "^8.14.1"
@@ -28,7 +23,6 @@
"devDependencies": { "devDependencies": {
"@types/node": "^20.12.5", "@types/node": "^20.12.5",
"@types/ws": "^8.5.5", "@types/ws": "^8.5.5",
"parcel": "^2.12.0",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"typescript": "^5.4.4" "typescript": "^5.4.4"
} }

View File

@@ -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);
}

View File

@@ -13,22 +13,20 @@ import { isIP } from 'node:net';
import { QemuVM, QemuVmDefinition } from '@cvmts/qemu'; import { QemuVM, QemuVmDefinition } from '@cvmts/qemu';
import { IPData, IPDataManager } from './IPData.js'; import { IPData, IPDataManager } from './IPData.js';
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'url'; import path from 'node:path';
import path from 'path';
import AuthManager from './AuthManager.js'; import AuthManager from './AuthManager.js';
import { Size, Rect, Logger } from '@cvmts/shared'; import { Size, Rect, Logger } from '@cvmts/shared';
import jpegTurbo from "@computernewb/jpeg-turbo";
import sharp from 'sharp'; import sharp from 'sharp';
import Piscina from 'piscina';
// @ts-expect-error (I know, but this is already ugly) // Instead of strange hacks we can just use nodejs provided
// really wish I didn't have to do it like this.. // import.meta properties, which have existed since LTS if not before
const __dirname = fileURLToPath(new __parcel__URL__('..')); 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 = { type ChatHistory = {
user: string, user: string,
msg: 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<Buffer> { async function EncodeJpeg(canvas: Buffer, displaySize: Size, rect: Rect): Promise<Buffer> {
let offset = (rect.y * displaySize.width + rect.x) * 4; 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, // have to manually turn it back into a buffer because
width: rect.width, // Piscina for some reason turns it into a Uint8Array
height: rect.height, return Buffer.from(res);
subsampling: jpegTurbo.SAMP_422,
stride: displaySize.width,
quality: kJpegQuality
});
} }
export default class WSServer { export default class WSServer {
@@ -125,8 +131,8 @@ export default class WSServer {
this.voteCooldown = 0; this.voteCooldown = 0;
this.turnsAllowed = true; this.turnsAllowed = true;
this.screenHidden = false; this.screenHidden = false;
this.screenHiddenImg = readFileSync(__dirname + "/../assets/screenhidden.jpeg").toString("base64"); this.screenHiddenImg = readFileSync(path.join(kCVMTSAssetsRoot, "screenhidden.jpeg")).toString("base64");
this.screenHiddenThumb = readFileSync(__dirname + "/../assets/screenhiddenthumb.jpeg").toString("base64"); this.screenHiddenThumb = readFileSync(path.join(kCVMTSAssetsRoot, "screenhiddenthumb.jpeg")).toString("base64");
this.indefiniteTurn = null; this.indefiniteTurn = null;
this.ModPerms = Utilities.MakeModPerms(this.Config.collabvm.moderatorPermissions); this.ModPerms = Utilities.MakeModPerms(this.Config.collabvm.moderatorPermissions);
@@ -937,7 +943,7 @@ export default class WSServer {
// TODO: pass custom options to Sharp.resize() probably // TODO: pass custom options to Sharp.resize() probably
let out = await sharp(display.Buffer(), {raw: GetRawSharpOptions(display.Size())}) let out = await sharp(display.Buffer(), {raw: GetRawSharpOptions(display.Size())})
.resize(400, 300) .resize(400, 300, { fit: 'fill' })
.toFormat('jpeg') .toFormat('jpeg')
.toBuffer(); .toBuffer();
@@ -1000,4 +1006,4 @@ export default class WSServer {
}); });
return {yes:yes,no:no}; return {yes:yes,no:no};
} }
} }

View File

@@ -1 +0,0 @@
../tsconfig.json

7
cvmts/tsconfig.json Normal file
View File

@@ -0,0 +1,7 @@
{
"extends": "../tsconfig.json",
"include": [ "src/**/*" ],
"compilerOptions": {
"resolveJsonModule": true,
}
}

View File

@@ -67,6 +67,7 @@ __metadata:
execa: "npm:^8.0.1" execa: "npm:^8.0.1"
mnemonist: "npm:^0.39.5" mnemonist: "npm:^0.39.5"
parcel: "npm:^2.12.0" parcel: "npm:^2.12.0"
piscina: "npm:^4.4.0"
prettier: "npm:^3.2.5" prettier: "npm:^3.2.5"
sharp: "npm:^0.33.3" sharp: "npm:^0.33.3"
toml: "npm:^3.0.0" toml: "npm:^3.0.0"
@@ -3155,6 +3156,26 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "node-addon-api@npm:^6.0.0, node-addon-api@npm:^6.1.0":
version: 6.1.0 version: 6.1.0
resolution: "node-addon-api@npm:6.1.0" resolution: "node-addon-api@npm:6.1.0"
@@ -3204,6 +3225,17 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "node-gyp@npm:latest":
version: 10.1.0 version: 10.1.0
resolution: "node-gyp@npm:10.1.0" resolution: "node-gyp@npm:10.1.0"
@@ -3408,6 +3440,18 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "postcss-value-parser@npm:^4.2.0":
version: 4.2.0 version: 4.2.0
resolution: "postcss-value-parser@npm:4.2.0" resolution: "postcss-value-parser@npm:4.2.0"