cvm-rs: version 0.2.0

- switch to napi-rs. this mostly affects only the backend side of things, but IMO napi-rs is better (also, way less boilerplate is needed compared to neon).

- jpeg encoding no longer clones the input buffer internally (or wraps it in a Mutex as well), thanks to napi-rs not sucking in this regard. this is *probably* a micro-optimization, but will make it easier to later on do parallel encoding of all rectangles

- guac encoding is weird. This is kind of a painpoint of napi-rs but it's bearable
This commit is contained in:
modeco80
2024-08-20 06:14:08 -04:00
parent 55566fbd3a
commit 17191b0917
6 changed files with 257 additions and 158 deletions

239
cvm-rs/Cargo.lock generated
View File

@@ -2,12 +2,57 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "addr2line"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.86" version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "backtrace"
version = "0.3.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.99" version = "1.0.99"
@@ -29,6 +74,15 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "crossbeam-deque" name = "crossbeam-deque"
version = "0.8.5" version = "0.8.5"
@@ -55,11 +109,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]] [[package]]
name = "cvm-rs" name = "ctor"
version = "0.1.1" version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
dependencies = [ dependencies = [
"quote",
"syn",
]
[[package]]
name = "cvm-rs"
version = "0.2.0"
dependencies = [
"anyhow",
"libc", "libc",
"neon", "napi",
"napi-build",
"napi-derive",
"once_cell", "once_cell",
"rayon", "rayon",
"turbojpeg-sys", "turbojpeg-sys",
@@ -72,15 +139,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]] [[package]]
name = "getrandom" name = "gimli"
version = "0.2.15" version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]] [[package]]
name = "libc" name = "libc"
@@ -99,29 +161,86 @@ dependencies = [
] ]
[[package]] [[package]]
name = "neon" name = "memchr"
version = "1.0.0" version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d75440242411c87dc39847b0e33e961ec1f10326a9d8ecf9c1ea64a3b3c13dc" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
dependencies = [ dependencies = [
"getrandom", "adler",
"libloading",
"neon-macros",
"once_cell",
"semver",
"send_wrapper",
"smallvec",
] ]
[[package]] [[package]]
name = "neon-macros" name = "napi"
version = "1.0.0" version = "2.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6813fde79b646e47e7ad75f480aa80ef76a5d9599e2717407961531169ee38b" checksum = "1277600d452e570cc83cf5f4e8efb389cc21e5cbefadcfba7239f4551e2e3e99"
dependencies = [ dependencies = [
"anyhow",
"bitflags",
"ctor",
"napi-derive",
"napi-sys",
"once_cell",
"tokio",
]
[[package]]
name = "napi-build"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a"
[[package]]
name = "napi-derive"
version = "2.16.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "150d87c4440b9f4815cb454918db498b5aae9a57aa743d20783fe75381181d01"
dependencies = [
"cfg-if",
"convert_case",
"napi-derive-backend",
"proc-macro2",
"quote", "quote",
"syn", "syn",
"syn-mid", ]
[[package]]
name = "napi-derive-backend"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cd81b794fc1d6051acf8c4f3cb4f82833b0621272a232b4ff0cf3df1dbddb61"
dependencies = [
"convert_case",
"once_cell",
"proc-macro2",
"quote",
"regex",
"semver",
"syn",
]
[[package]]
name = "napi-sys"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3"
dependencies = [
"libloading",
]
[[package]]
name = "object"
version = "0.36.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
dependencies = [
"memchr",
] ]
[[package]] [[package]]
@@ -130,6 +249,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "pin-project-lite"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.30" version = "0.3.30"
@@ -174,24 +299,47 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "regex"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.23" version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "send_wrapper"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.66" version = "2.0.66"
@@ -204,14 +352,13 @@ dependencies = [
] ]
[[package]] [[package]]
name = "syn-mid" name = "tokio"
version = "0.6.0" version = "1.39.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5dc35bb08dd1ca3dfb09dce91fd2d13294d6711c88897d9a9d60acf39bce049" checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
dependencies = [ dependencies = [
"proc-macro2", "backtrace",
"quote", "pin-project-lite",
"syn",
] ]
[[package]] [[package]]
@@ -233,10 +380,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]] [[package]]
name = "wasi" name = "unicode-segmentation"
version = "0.11.0+wasi-snapshot-preview1" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "cvm-rs" name = "cvm-rs"
description = "Rust utility library for cvmts. Runs all the high performance code" description = "Rust utility library for cvmts. Runs all the high performance code"
version = "0.1.1" version = "0.2.0"
edition = "2021" edition = "2021"
exclude = ["index.node"] exclude = ["index.node"]
@@ -9,10 +9,17 @@ exclude = ["index.node"]
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
neon = "1"
libc = "0.2.155" libc = "0.2.155"
# Required for JPEG # Required for JPEG
once_cell = "1.19.0" once_cell = "1.19.0"
turbojpeg-sys = "1.0.0" turbojpeg-sys = "1.0.0"
rayon = "1.10.0" rayon = "1.10.0"
# node sex
napi = { version = "2.16.9", features = [ "async", "napi8", "error_anyhow" ] }
napi-derive = "2.16.11"
anyhow = "1.0.86"
[build-dependencies]
napi-build = "2.1.3"

View File

@@ -2,5 +2,11 @@
import { createRequire } from 'module'; import { createRequire } from 'module';
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
export let {guacDecode, guacEncode, jpegEncode} = require('./index.node'); let {guacDecode, guacEncodeImpl, jpegEncode} = require('./index.node');
export { guacDecode, jpegEncode };
// shim for js->rust interop, because napi-rs kind of blows in this regard
export function guacEncode(...args) {
return guacEncodeImpl(args);
}

View File

@@ -1,47 +1,20 @@
use crate::guac; use crate::guac;
use neon::prelude::*;
fn guac_decode_impl<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsArray> { use napi_derive::napi;
let input = cx.argument::<JsString>(0)?.value(cx);
#[napi(js_name = "guacDecode")]
#[allow(unused)]
pub fn guac_decode(input: String) -> napi::anyhow::Result<Vec<String>> {
match guac::decode_instruction(&input) { match guac::decode_instruction(&input) {
Ok(data) => { Ok(elements) => Ok(elements),
let array = JsArray::new(cx, data.len());
let conv = data Err(err) => Err(anyhow::anyhow!("Error decoding Guacamole frame: {}", err)),
.iter()
.map(|v| cx.string(v))
.collect::<Vec<Handle<JsString>>>();
for (i, str) in conv.iter().enumerate() {
array.set(cx, i as u32, *str)?;
}
return Ok(array);
}
Err(e) => {
return cx.throw_error(format!("{}", e));
}
} }
} }
fn guac_encode_impl<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsString> { // ... this is ugly, but works
let mut elements: Vec<String> = Vec::with_capacity(cx.len()); #[napi(js_name = "guacEncodeImpl")]
#[allow(unused)]
// Capture varadic arguments pub fn guac_encode(items: Vec<String>) -> napi::anyhow::Result<String> {
for i in 0..cx.len() { Ok(guac::encode_instruction(&items))
let input = cx.argument::<JsString>(i)?.value(cx);
elements.push(input);
}
Ok(cx.string(guac::encode_instruction(&elements)))
}
pub fn guac_decode(mut cx: FunctionContext) -> JsResult<JsArray> {
guac_decode_impl(&mut cx)
}
pub fn guac_encode(mut cx: FunctionContext) -> JsResult<JsString> {
guac_encode_impl(&mut cx)
} }

View File

@@ -1,7 +1,5 @@
use std::sync::{Arc, Mutex}; use napi::Env;
use napi_derive::napi;
use neon::prelude::*;
use neon::types::buffer::TypedArray;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
@@ -12,23 +10,22 @@ use rayon::{ThreadPool, ThreadPoolBuilder};
use crate::jpeg_compressor::*; use crate::jpeg_compressor::*;
/// Gives a Rayon thread pool we use for parallelism /// Gives a Rayon thread pool we use for parallelism
fn rayon_pool<'a, C: Context<'a>>(cx: &mut C) -> NeonResult<&'static ThreadPool> { fn rayon_pool() -> &'static ThreadPool {
static RUNTIME: OnceCell<ThreadPool> = OnceCell::new(); static RUNTIME: OnceCell<ThreadPool> = OnceCell::new();
RUNTIME RUNTIME.get_or_init(|| {
.get_or_try_init(|| { // spawn at least 4 threads
// spawn at least 4 threads let mut nr_threads = std::thread::available_parallelism().expect("??").get() / 8;
let mut nr_threads = std::thread::available_parallelism().expect("??").get() / 8; if nr_threads == 0 {
if nr_threads == 0 { nr_threads = 4;
nr_threads = 4; }
}
ThreadPoolBuilder::new() ThreadPoolBuilder::new()
.num_threads(nr_threads) .num_threads(nr_threads)
.thread_name(|index| format!("cvmrs_jpeg_{}", index + 1)) .thread_name(|index| format!("cvmrs_jpeg_{}", index + 1))
.build() .build()
}) .unwrap()
.or_else(|err| cx.throw_error(&err.to_string())) })
} }
thread_local! { thread_local! {
@@ -38,43 +35,29 @@ thread_local! {
// TODO: We should probably allow passing an array of images to encode, which would // TODO: We should probably allow passing an array of images to encode, which would
// increase parallelism heavily. // increase parallelism heavily.
fn jpeg_encode_impl<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsPromise> { #[napi(object)]
let input = cx.argument::<JsObject>(0)?; pub struct JpegInputArgs {
pub width: u32,
pub height: u32,
pub stride: u32,
pub buffer: napi::JsBuffer,
}
// Get our input arguments here #[napi(js_name = "jpegEncode")]
let width: u64 = input.get::<JsNumber, _, _>(cx, "width")?.value(cx) as u64; #[allow(unused)]
let height: u64 = input.get::<JsNumber, _, _>(cx, "height")?.value(cx) as u64; pub fn jpeg_encode(env: Env, input: JpegInputArgs) -> napi::Result<napi::JsObject> {
let stride: u64 = input.get::<JsNumber, _, _>(cx, "stride")?.value(cx) as u64; let (deferred_resolver, promise) = env.create_deferred::<napi::JsUnknown, _>()?;
let buffer: Handle<JsBuffer> = input.get(cx, "buffer")?; let buf = input.buffer.into_ref()?;
let (deferred, promise) = cx.promise();
let channel = cx.channel();
let pool = rayon_pool(cx)?;
let buf = buffer.as_slice(cx);
let copy: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::with_capacity(buf.len())));
// Copy from the node buffer to our temporary buffer
{
let mut locked = copy.lock().unwrap();
let cap = locked.capacity();
locked.resize(cap, 0);
locked.copy_from_slice(buf);
}
// Spawn a task on the rayon pool that encodes the JPEG and fufills the promise // Spawn a task on the rayon pool that encodes the JPEG and fufills the promise
// once it is done encoding. // once it is done encoding.
pool.spawn_fifo(move || { rayon_pool().spawn_fifo(move || {
let clone = Arc::clone(&copy);
let locked = clone.lock().unwrap();
let image: Image = Image { let image: Image = Image {
buffer: locked.as_slice(), buffer: &buf,
width: width as u32, width: input.width as u32,
height: height as u32, height: input.height as u32,
stride: (stride * 4u64) as u32, // I think? stride: (input.stride as u64 * 4u64) as u32,
format: turbojpeg_sys::TJPF_TJPF_RGBA, format: turbojpeg_sys::TJPF_TJPF_RGBA,
}; };
@@ -85,18 +68,13 @@ fn jpeg_encode_impl<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsPromise>
b.compress_buffer(&image) b.compress_buffer(&image)
}); });
// Fulfill the Javascript promise with our encoded buffer deferred_resolver.resolve(move |env| {
deferred.settle_with(&channel, move |mut cx| { let buffer = env
let mut buf = cx.buffer(vec.len())?; .create_buffer_with_data(vec)
let slice = buf.as_mut_slice(&mut cx); .expect("Couldn't create node Buffer, things are probably very broken by this point");
slice.copy_from_slice(vec.as_slice()); Ok(buffer.into_unknown())
Ok(buf)
}); });
}); });
Ok(promise) Ok(promise)
} }
pub fn jpeg_encode(mut cx: FunctionContext) -> JsResult<JsPromise> {
jpeg_encode_impl(&mut cx)
}

View File

@@ -3,15 +3,3 @@ mod guac_js;
mod jpeg_compressor; mod jpeg_compressor;
mod jpeg_js; mod jpeg_js;
use neon::prelude::*;
#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
// Mostly transitionary, later on API should change
cx.export_function("jpegEncode", jpeg_js::jpeg_encode)?;
cx.export_function("guacDecode", guac_js::guac_decode)?;
cx.export_function("guacEncode", guac_js::guac_encode)?;
Ok(())
}