From 4719caa64377f7d926c92a4c1051474ae79036c4 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 20 Mar 2024 21:37:08 +0800 Subject: [PATCH] feat(napi): support `Return` generic of ThreadsafeFunction (#1997) * feat(napi): support to use tuple with either (#1993) `Either` uses `ValidateNapiValue` + `TypeName` to validate and report error on value not being matched. So there's no way to remove these super traits from it. So I implemented these types to `Tuple` types. * feat(napi): support `Return` generic of ThreadsafeFunction * depracate JsFunction * CalleeHandled tsfn should handle Result in callback * Pass env to call_with_return_value callback * Fix compile * clippy fix * Fix electron test * Function args --------- Co-authored-by: Hana --- bench/src/async_compute.rs | 2 +- bench/src/get_set_property.rs | 2 +- bench/src/lib.rs | 1 + crates/backend/src/typegen.rs | 24 +- crates/napi/src/async_work.rs | 8 +- .../src/bindgen_runtime/js_values/date.rs | 9 +- .../src/bindgen_runtime/js_values/function.rs | 275 +++++++-- .../src/bindgen_runtime/js_values/task.rs | 9 +- crates/napi/src/bindgen_runtime/mod.rs | 2 +- crates/napi/src/env.rs | 180 +++--- crates/napi/src/error.rs | 21 +- crates/napi/src/js_values/deferred.rs | 6 +- crates/napi/src/js_values/function.rs | 26 +- crates/napi/src/js_values/global.rs | 70 +-- crates/napi/src/js_values/mod.rs | 2 + crates/napi/src/js_values/object.rs | 2 +- crates/napi/src/js_values/ser.rs | 18 +- crates/napi/src/task.rs | 8 +- crates/napi/src/threadsafe_function.rs | 561 +++++++----------- .../__tests__/function.spec.ts | 2 +- examples/napi-compat-mode/src/class.rs | 11 +- examples/napi-compat-mode/src/function.rs | 15 +- examples/napi-compat-mode/src/global.rs | 4 +- examples/napi-compat-mode/src/lib.rs | 1 + examples/napi-compat-mode/src/napi4/mod.rs | 4 +- .../__snapshots__/typegen.spec.ts.md | 24 +- .../__snapshots__/typegen.spec.ts.snap | Bin 4764 -> 4810 bytes .../__tests__/__snapshots__/values.spec.ts.md | 1 + .../__snapshots__/values.spec.ts.snap | Bin 378 -> 389 bytes examples/napi/__tests__/tsfn-error.cjs | 10 +- examples/napi/__tests__/values.spec.ts | 29 +- examples/napi/electron.cjs | 2 +- examples/napi/index.cjs | 1 + examples/napi/index.d.cts | 24 +- examples/napi/package.json | 1 + examples/napi/src/callback.rs | 4 +- examples/napi/src/date.rs | 4 +- examples/napi/src/enum.rs | 1 + examples/napi/src/function.rs | 30 + examples/napi/src/lib.rs | 1 + examples/napi/src/threadsafe_function.rs | 79 ++- memory-testing/src/lib.rs | 4 +- yarn.lock | 1 + 43 files changed, 815 insertions(+), 664 deletions(-) diff --git a/bench/src/async_compute.rs b/bench/src/async_compute.rs index df0158ea..148ef394 100644 --- a/bench/src/async_compute.rs +++ b/bench/src/async_compute.rs @@ -37,7 +37,7 @@ fn bench_threadsafe_function(ctx: CallContext) -> Result { let tsfn = ctx.env.create_threadsafe_function( &callback, 0, - |mut ctx: ThreadSafeCallContext<(usize, Ref)>| { + |mut ctx: ThreadsafeCallContext<(usize, Ref)>| { ctx .env .create_uint32(ctx.value.0 as u32) diff --git a/bench/src/get_set_property.rs b/bench/src/get_set_property.rs index 96779789..5a0219b5 100644 --- a/bench/src/get_set_property.rs +++ b/bench/src/get_set_property.rs @@ -40,7 +40,7 @@ impl FromStr for LineJoin { } pub fn register_js(exports: &mut JsObject, env: &Env) -> Result<()> { - let test_class = env.define_class( + let test_class = env.define_class::( "TestClass", test_class_constructor, &[ diff --git a/bench/src/lib.rs b/bench/src/lib.rs index 01d38976..19d3c7ca 100644 --- a/bench/src/lib.rs +++ b/bench/src/lib.rs @@ -1,4 +1,5 @@ #![allow(clippy::uninlined_format_args)] +#![allow(deprecated)] #[macro_use] extern crate napi_derive; diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 4e853151..477c2fd4 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -212,6 +212,7 @@ static KNOWN_TYPES: Lazy> = La ("External", ("ExternalObject<{}>", false, false)), ("unknown", ("unknown", false, false)), ("Unknown", ("unknown", false, false)), + ("UnknownReturnValue", ("unknown", false, false)), ("JsUnknown", ("unknown", false, false)), ("This", ("this", false, false)), ("Rc", ("{}", false, false)), @@ -338,6 +339,7 @@ pub fn ty_to_ts_type( generic_ty, index == 1 && is_generic_function_type(&rust_ty), false, + // index == 2 is for ThreadsafeFunction with ErrorStrategy is_generic_function_type(&rust_ty), )) .map(|(mut ty, is_optional)| { @@ -346,6 +348,11 @@ pub fn ty_to_ts_type( } (ty, is_optional) }), + // const Generic for `ThreadsafeFunction` generic + syn::GenericArgument::Const(syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Bool(bo), + .. + })) => Some((bo.value.to_string(), false)), _ => None, }) .collect::>() @@ -408,15 +415,22 @@ pub fn ty_to_ts_type( { ts_ty = Some((t, false)); } else if rust_ty == TSFN_RUST_TY { - let fatal_tsfn = match args.get(1) { - Some((arg, _)) => arg == "Fatal", + let fatal_tsfn = match args.last() { + Some((arg, _)) => arg == "false", _ => false, }; - let args = args.first().map(|(arg, _)| arg).unwrap(); + let fn_args = args.first().map(|(arg, _)| arg).unwrap(); + let return_ty = args + .get(1) + .map(|(ty, _)| ty.clone()) + .unwrap_or("any".to_owned()); ts_ty = if fatal_tsfn { - Some((format!("({}) => any", args), false)) + Some((format!("({fn_args}) => {return_ty}"), false)) } else { - Some((format!("(err: Error | null, {}) => any", args), false)) + Some(( + format!("(err: Error | null, {fn_args}) => {return_ty}"), + false, + )) }; } else { // there should be runtime registered type in else diff --git a/crates/napi/src/async_work.rs b/crates/napi/src/async_work.rs index 7a9a43e3..eebd3d95 100644 --- a/crates/napi/src/async_work.rs +++ b/crates/napi/src/async_work.rs @@ -114,11 +114,9 @@ unsafe extern "C" fn complete( let value = match value_ptr { Ok(v) => { let output = unsafe { v.assume_init() }; - work - .inner_task - .resolve(unsafe { Env::from_raw(env) }, output) + work.inner_task.resolve(Env::from_raw(env), output) } - Err(e) => work.inner_task.reject(unsafe { Env::from_raw(env) }, e), + Err(e) => work.inner_task.reject(Env::from_raw(env), e), }; if status != sys::Status::napi_cancelled && work.status.load(Ordering::Relaxed) != 2 { match check_status!(status) @@ -144,7 +142,7 @@ unsafe extern "C" fn complete( } }; } - if let Err(e) = work.inner_task.finally(unsafe { Env::from_raw(env) }) { + if let Err(e) = work.inner_task.finally(Env::from_raw(env)) { debug_assert!(false, "Panic in Task finally fn: {:?}", e); } let delete_status = unsafe { sys::napi_delete_async_work(env, napi_async_work) }; diff --git a/crates/napi/src/bindgen_runtime/js_values/date.rs b/crates/napi/src/bindgen_runtime/js_values/date.rs index 689432fc..13fd4a73 100644 --- a/crates/napi/src/bindgen_runtime/js_values/date.rs +++ b/crates/napi/src/bindgen_runtime/js_values/date.rs @@ -32,7 +32,7 @@ impl ValidateNapiValue for DateTime { impl ToNapiValue for NaiveDateTime { unsafe fn to_napi_value(env: sys::napi_env, val: NaiveDateTime) -> Result { let mut ptr = std::ptr::null_mut(); - let millis_since_epoch_utc = val.timestamp_millis() as f64; + let millis_since_epoch_utc = val.and_utc().timestamp_millis() as f64; check_status!( unsafe { sys::napi_create_date(env, millis_since_epoch_utc, &mut ptr) }, @@ -145,11 +145,14 @@ impl FromNapiValue for DateTime { let milliseconds_since_epoch_utc = milliseconds_since_epoch_utc as i64; let timestamp_seconds = milliseconds_since_epoch_utc / 1_000; - let naive = NaiveDateTime::from_timestamp_opt( + let naive = DateTime::from_timestamp( timestamp_seconds, (milliseconds_since_epoch_utc % 1_000 * 1_000_000) as u32, ) .ok_or_else(|| Error::new(Status::DateExpected, "Found invalid date".to_owned()))?; - Ok(DateTime::::from_naive_utc_and_offset(naive, Utc)) + Ok(DateTime::::from_naive_utc_and_offset( + naive.naive_utc(), + Utc, + )) } } diff --git a/crates/napi/src/bindgen_runtime/js_values/function.rs b/crates/napi/src/bindgen_runtime/js_values/function.rs index 53f572c0..eab1b2e4 100644 --- a/crates/napi/src/bindgen_runtime/js_values/function.rs +++ b/crates/napi/src/bindgen_runtime/js_values/function.rs @@ -1,7 +1,11 @@ -use std::ptr; +#![allow(deprecated)] -use super::{FromNapiValue, ToNapiValue, TypeName, ValidateNapiValue}; +use std::{ptr, usize}; +use super::{FromNapiValue, ToNapiValue, TypeName, Unknown, ValidateNapiValue}; + +#[cfg(feature = "napi4")] +use crate::threadsafe_function::ThreadsafeFunction; pub use crate::JsFunction; use crate::{check_pending_exception, check_status, sys, Env, NapiRaw, Result, ValueType}; @@ -11,11 +15,82 @@ pub trait JsValuesTupleIntoVec { fn into_vec(self, env: sys::napi_env) -> Result>; } +impl JsValuesTupleIntoVec for T { + #[allow(clippy::not_unsafe_ptr_arg_deref)] + fn into_vec(self, env: sys::napi_env) -> Result> { + Ok(vec![unsafe { + ::to_napi_value(env, self)? + }]) + } +} + +pub trait TupleFromSliceValues { + #[allow(clippy::missing_safety_doc)] + unsafe fn from_slice_values(env: sys::napi_env, values: &[sys::napi_value]) -> Result + where + Self: Sized; +} + +macro_rules! impl_tuple_conversion { + ($($ident:ident),*) => { + impl<$($ident: ToNapiValue),*> JsValuesTupleIntoVec for ($($ident,)*) { + #[allow(clippy::not_unsafe_ptr_arg_deref)] + fn into_vec(self, env: sys::napi_env) -> Result> { + #[allow(non_snake_case)] + let ($($ident,)*) = self; + Ok(vec![$(unsafe { <$ident as ToNapiValue>::to_napi_value(env, $ident)? }),*]) + } + } + + impl<$($ident: FromNapiValue),*> TupleFromSliceValues for ($($ident,)*) { + unsafe fn from_slice_values(env: sys::napi_env, values: &[sys::napi_value]) -> $crate::Result { + #[allow(non_snake_case)] + let [$($ident),*] = values.try_into().map_err(|_| crate::Error::new( + crate::Status::InvalidArg, + "Invalid number of arguments", + ))?; + Ok(($( + unsafe { $ident::from_napi_value(env, $ident)?} + ,)*)) + } + } + }; +} + +impl_tuple_conversion!(A); +impl_tuple_conversion!(A, B); +impl_tuple_conversion!(A, B, C); +impl_tuple_conversion!(A, B, C, D); +impl_tuple_conversion!(A, B, C, D, E); +impl_tuple_conversion!(A, B, C, D, E, F); +impl_tuple_conversion!(A, B, C, D, E, F, G); +impl_tuple_conversion!(A, B, C, D, E, F, G, H); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X); +impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y); +impl_tuple_conversion!( + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z +); + /// A JavaScript function. /// It can only live in the scope of a function call. /// If you want to use it outside the scope of a function call, you can turn it into a reference. /// By calling the `create_ref` method. -pub struct Function<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> { +pub struct Function<'scope, Args: JsValuesTupleIntoVec = Unknown, Return = Unknown> { pub(crate) env: sys::napi_env, pub(crate) value: sys::napi_value, pub(crate) _args: std::marker::PhantomData, @@ -23,9 +98,7 @@ pub struct Function<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> { _scope: std::marker::PhantomData<&'scope ()>, } -impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> TypeName - for Function<'scope, Args, Return> -{ +impl<'scope, Args: JsValuesTupleIntoVec, Return> TypeName for Function<'scope, Args, Return> { fn type_name() -> &'static str { "Function" } @@ -35,17 +108,13 @@ impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> TypeName } } -impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> NapiRaw - for Function<'scope, Args, Return> -{ +impl<'scope, Args: JsValuesTupleIntoVec, Return> NapiRaw for Function<'scope, Args, Return> { unsafe fn raw(&self) -> sys::napi_value { self.value } } -impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> FromNapiValue - for Function<'scope, Args, Return> -{ +impl<'scope, Args: JsValuesTupleIntoVec, Return> FromNapiValue for Function<'scope, Args, Return> { unsafe fn from_napi_value(env: sys::napi_env, value: sys::napi_value) -> Result { Ok(Function { env, @@ -57,11 +126,70 @@ impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> FromNapiValue } } -impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> ValidateNapiValue +impl<'scope, Args: JsValuesTupleIntoVec, Return> ValidateNapiValue for Function<'scope, Args, Return> { } +impl<'scope, Args: JsValuesTupleIntoVec, Return> Function<'scope, Args, Return> { + /// Get the name of the JavaScript function. + pub fn name(&self) -> Result { + let mut name = ptr::null_mut(); + check_status!( + unsafe { + sys::napi_get_named_property(self.env, self.value, "name\0".as_ptr().cast(), &mut name) + }, + "Get function name failed" + )?; + unsafe { String::from_napi_value(self.env, name) } + } + + /// Create a reference to the JavaScript function. + pub fn create_ref(&self) -> Result> { + let mut reference = ptr::null_mut(); + check_status!( + unsafe { sys::napi_create_reference(self.env, self.value, 1, &mut reference) }, + "Create reference failed" + )?; + Ok(FunctionRef { + inner: reference, + env: self.env, + _args: std::marker::PhantomData, + _return: std::marker::PhantomData, + }) + } + + /// Create a new instance of the JavaScript Class. + pub fn new_instance(&self, args: Args) -> Result { + let mut raw_instance = ptr::null_mut(); + let mut args = args.into_vec(self.env)?; + check_status!( + unsafe { + sys::napi_new_instance( + self.env, + self.value, + args.len(), + args.as_mut_ptr().cast(), + &mut raw_instance, + ) + }, + "Create new instance failed" + )?; + unsafe { Unknown::from_napi_value(self.env, raw_instance) } + } + + #[cfg(feature = "napi4")] + /// Create a threadsafe function from the JavaScript function. + pub fn build_threadsafe_function(&self) -> ThreadsafeFunctionBuilder { + ThreadsafeFunctionBuilder { + env: self.env, + value: self.value, + _args: std::marker::PhantomData, + _return: std::marker::PhantomData, + } + } +} + impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> Function<'scope, Args, Return> { /// Call the JavaScript function. /// `this` in the JavaScript function will be `undefined`. @@ -97,8 +225,7 @@ impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> Function<'scope, let raw_this = unsafe { Context::to_napi_value(self.env, this) }?; let args_ptr = args.into_vec(self.env)?; let mut raw_return = ptr::null_mut(); - check_pending_exception!( - self.env, + check_status!( unsafe { sys::napi_call_function( self.env, @@ -113,35 +240,66 @@ impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> Function<'scope, )?; unsafe { Return::from_napi_value(self.env, raw_return) } } +} - /// Create a reference to the JavaScript function. - pub fn create_ref(&self) -> Result> { - let mut reference = ptr::null_mut(); - check_status!( - unsafe { sys::napi_create_reference(self.env, self.value, 1, &mut reference) }, - "Create reference failed" - )?; - Ok(FunctionRef { - inner: reference, +pub struct ThreadsafeFunctionBuilder< + Args: JsValuesTupleIntoVec, + Return, + const Weak: bool = false, + const MaxQueueSize: usize = 0, +> { + pub(crate) env: sys::napi_env, + pub(crate) value: sys::napi_value, + _args: std::marker::PhantomData, + _return: std::marker::PhantomData, +} + +impl< + Args: JsValuesTupleIntoVec, + Return: FromNapiValue, + const Weak: bool, + const MaxQueueSize: usize, + > ThreadsafeFunctionBuilder +{ + pub fn weak( + self, + ) -> ThreadsafeFunctionBuilder { + ThreadsafeFunctionBuilder { env: self.env, + value: self.value, _args: std::marker::PhantomData, _return: std::marker::PhantomData, - }) + } + } + + pub fn max_queue_size( + self, + ) -> ThreadsafeFunctionBuilder { + ThreadsafeFunctionBuilder { + env: self.env, + value: self.value, + _args: std::marker::PhantomData, + _return: std::marker::PhantomData, + } + } + + pub fn build(self) -> Result> { + unsafe { ThreadsafeFunction::from_napi_value(self.env, self.value) } } } /// A reference to a JavaScript function. /// It can be used to outlive the scope of the function. -pub struct FunctionRef { +pub struct FunctionRef { pub(crate) inner: sys::napi_ref, pub(crate) env: sys::napi_env, _args: std::marker::PhantomData, _return: std::marker::PhantomData, } -unsafe impl Sync for FunctionRef {} +unsafe impl Sync for FunctionRef {} -impl FunctionRef { +impl FunctionRef { pub fn borrow_back<'scope>(&self, env: &'scope Env) -> Result> { let mut value = ptr::null_mut(); check_status!( @@ -158,14 +316,14 @@ impl FunctionRef Drop for FunctionRef { +impl Drop for FunctionRef { fn drop(&mut self) { let status = unsafe { sys::napi_delete_reference(self.env, self.inner) }; debug_assert_eq!(status, sys::Status::napi_ok, "Drop FunctionRef failed"); } } -impl TypeName for FunctionRef { +impl TypeName for FunctionRef { fn type_name() -> &'static str { "Function" } @@ -175,9 +333,7 @@ impl TypeName for FunctionRef } } -impl FromNapiValue - for FunctionRef -{ +impl FromNapiValue for FunctionRef { unsafe fn from_napi_value(env: sys::napi_env, value: sys::napi_value) -> Result { let mut reference = ptr::null_mut(); check_status!( @@ -198,6 +354,55 @@ impl ValidateNapiValue { } +pub struct FunctionCallContext<'scope> { + pub(crate) args: &'scope [sys::napi_value], + pub(crate) this: sys::napi_value, + pub(crate) env: &'scope mut Env, +} + +impl FunctionCallContext<'_> { + /// Get the number of arguments from the JavaScript function call. + pub fn length(&self) -> usize { + self.args.len() + } + + /// Get the first argument from the JavaScript function call. + pub fn first_arg(&self) -> Result { + if self.args.is_empty() { + return Err(crate::Error::new( + crate::Status::InvalidArg, + "There is no arguments", + )); + } + unsafe { T::from_napi_value(self.env.0, self.args[0]) } + } + + /// Get the arguments from the JavaScript function call. + /// The arguments will be converted to a tuple. + /// If the number of arguments is not equal to the number of tuple elements, an error will be returned. + /// example: + /// ```rust + /// let (num, string) = ctx.args::<(u32, String)>()?; + /// ```` + pub fn args(&self) -> Result { + unsafe { Args::from_slice_values(self.env.0, self.args) } + } + + /// Get the arguments Vec from the JavaScript function call. + pub fn arguments(&self) -> Result> { + self + .args + .iter() + .map(|arg| unsafe { ::from_napi_value(self.env.0, *arg) }) + .collect::>>() + } + + /// Get the `this` from the JavaScript function call. + pub fn this(&self) -> Result { + unsafe { This::from_napi_value(self.env.0, self.this) } + } +} + macro_rules! impl_call_apply { ($fn_call_name:ident, $fn_apply_name:ident, $($ident:ident),*) => { #[allow(non_snake_case, clippy::too_many_arguments)] @@ -205,7 +410,7 @@ macro_rules! impl_call_apply { &self, $($ident: $ident),* ) -> Result { - let raw_this = unsafe { Env::from_raw(self.0.env) } + let raw_this = Env::from_raw(self.0.env) .get_undefined() .map(|u| unsafe { u.raw() })?; @@ -284,7 +489,7 @@ impl JsFunction { } pub fn call0(&self) -> Result { - let raw_this = unsafe { Env::from_raw(self.0.env) } + let raw_this = Env::from_raw(self.0.env) .get_undefined() .map(|u| unsafe { u.raw() })?; diff --git a/crates/napi/src/bindgen_runtime/js_values/task.rs b/crates/napi/src/bindgen_runtime/js_values/task.rs index f4facfa5..e3fa8aba 100644 --- a/crates/napi/src/bindgen_runtime/js_values/task.rs +++ b/crates/napi/src/bindgen_runtime/js_values/task.rs @@ -3,7 +3,7 @@ use std::ptr; use std::rc::Rc; use std::sync::atomic::{AtomicPtr, AtomicU8, Ordering}; -use super::{FromNapiValue, ToNapiValue, TypeName}; +use super::{FromNapiValue, ToNapiValue, TypeName, Unknown}; use crate::{ async_work, check_status, sys, Env, Error, JsError, JsObject, NapiValue, Status, Task, }; @@ -65,7 +65,7 @@ impl FromNapiValue for AbortSignal { raw_deferred: raw_promise.clone(), status: task_status.clone(), }; - let js_env = unsafe { Env::from_raw(env) }; + let js_env = Env::from_raw(env); check_status!(unsafe { sys::napi_wrap( env, @@ -76,7 +76,10 @@ impl FromNapiValue for AbortSignal { ptr::null_mut(), ) })?; - signal.set_named_property("onabort", js_env.create_function("onabort", on_abort)?)?; + signal.set_named_property( + "onabort", + js_env.create_function::("onabort", on_abort)?, + )?; Ok(AbortSignal { raw_work: async_work_inner, raw_deferred: raw_promise, diff --git a/crates/napi/src/bindgen_runtime/mod.rs b/crates/napi/src/bindgen_runtime/mod.rs index 97605869..b3acec2d 100644 --- a/crates/napi/src/bindgen_runtime/mod.rs +++ b/crates/napi/src/bindgen_runtime/mod.rs @@ -35,7 +35,7 @@ pub unsafe extern "C" fn raw_finalize_unchecked( _finalize_hint: *mut c_void, ) { let data: Box = unsafe { Box::from_raw(finalize_data.cast()) }; - if let Err(err) = data.finalize(unsafe { Env::from_raw(env) }) { + if let Err(err) = data.finalize(Env::from_raw(env)) { let e: JsError = err.into(); unsafe { e.throw_into(env) }; return; diff --git a/crates/napi/src/env.rs b/crates/napi/src/env.rs index 31526e76..83eb58cc 100644 --- a/crates/napi/src/env.rs +++ b/crates/napi/src/env.rs @@ -7,9 +7,26 @@ use std::mem; use std::os::raw::{c_char, c_void}; use std::ptr; -use crate::bindgen_runtime::FromNapiValue; +#[cfg(feature = "serde-json")] +use serde::de::DeserializeOwned; +#[cfg(feature = "serde-json")] +use serde::Serialize; + +#[cfg(feature = "napi8")] +use crate::async_cleanup_hook::AsyncCleanupHook; +#[cfg(feature = "napi5")] +use crate::bindgen_runtime::FunctionCallContext; #[cfg(feature = "napi4")] use crate::bindgen_runtime::ToNapiValue; +use crate::bindgen_runtime::{FromNapiValue, Function, JsValuesTupleIntoVec, Unknown}; +#[cfg(feature = "napi3")] +use crate::cleanup_env::{CleanupEnvHook, CleanupEnvHookData}; +#[cfg(feature = "serde-json")] +use crate::js_values::{De, Ser}; +#[cfg(feature = "napi4")] +use crate::threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunction}; +#[cfg(feature = "napi3")] +use crate::JsError; use crate::{ async_work::{self, AsyncWorkPromise}, check_status, @@ -19,21 +36,6 @@ use crate::{ Error, ExtendedErrorInfo, NodeVersion, Result, Status, ValueType, }; -#[cfg(feature = "napi8")] -use crate::async_cleanup_hook::AsyncCleanupHook; -#[cfg(feature = "napi3")] -use crate::cleanup_env::{CleanupEnvHook, CleanupEnvHookData}; -#[cfg(feature = "serde-json")] -use crate::js_values::{De, Ser}; -#[cfg(feature = "napi4")] -use crate::threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction}; -#[cfg(feature = "napi3")] -use crate::JsError; -#[cfg(feature = "serde-json")] -use serde::de::DeserializeOwned; -#[cfg(feature = "serde-json")] -use serde::Serialize; - pub type Callback = unsafe extern "C" fn(sys::napi_env, sys::napi_callback_info) -> sys::napi_value; pub(crate) static EMPTY_VEC: Vec = vec![]; @@ -58,7 +60,7 @@ impl From for Env { impl Env { #[allow(clippy::missing_safety_doc)] - pub unsafe fn from_raw(env: sys::napi_env) -> Self { + pub fn from_raw(env: sys::napi_env) -> Self { Env(env) } @@ -189,12 +191,7 @@ impl Env { pub fn create_string_latin1(&self, chars: &[u8]) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { - sys::napi_create_string_latin1( - self.0, - chars.as_ptr() as *const _, - chars.len(), - &mut raw_value, - ) + sys::napi_create_string_latin1(self.0, chars.as_ptr().cast(), chars.len(), &mut raw_value) })?; Ok(unsafe { JsString::from_raw_unchecked(self.0, raw_value) }) } @@ -570,14 +567,17 @@ impl Env { /// The newly created function is not automatically visible from script after this call. /// /// Instead, a property must be explicitly set on any object that is visible to JavaScript, in order for the function to be accessible from script. - pub fn create_function(&self, name: &str, callback: Callback) -> Result { + pub fn create_function( + &self, + name: &str, + callback: Callback, + ) -> Result> { let mut raw_result = ptr::null_mut(); let len = name.len(); - let name = CString::new(name)?; check_status!(unsafe { sys::napi_create_function( self.0, - name.as_ptr(), + name.as_ptr().cast(), len, Some(callback), ptr::null_mut(), @@ -585,26 +585,29 @@ impl Env { ) })?; - Ok(unsafe { JsFunction::from_raw_unchecked(self.0, raw_result) }) + unsafe { Function::::from_napi_value(self.0, raw_result) } } #[cfg(feature = "napi5")] - pub fn create_function_from_closure(&self, name: &str, callback: F) -> Result + pub fn create_function_from_closure( + &self, + name: &str, + callback: F, + ) -> Result> where - F: 'static + Fn(crate::CallContext<'_>) -> Result, - R: ToNapiValue, + Return: ToNapiValue, + F: 'static + Fn(FunctionCallContext) -> Result, { let closure_data_ptr = Box::into_raw(Box::new(callback)); let mut raw_result = ptr::null_mut(); let len = name.len(); - let name = CString::new(name)?; check_status!(unsafe { sys::napi_create_function( self.0, - name.as_ptr(), + name.as_ptr().cast(), len, - Some(trampoline::), + Some(trampoline::), closure_data_ptr.cast(), // We let it borrow the data here &mut raw_result, ) @@ -629,7 +632,7 @@ impl Env { ) })?; - Ok(unsafe { JsFunction::from_raw_unchecked(self.0, raw_result) }) + unsafe { Function::from_napi_value(self.0, raw_result) } } /// This API retrieves a napi_extended_error_info structure with information about the last error that occurred. @@ -740,12 +743,12 @@ impl Env { } /// Create JavaScript class - pub fn define_class( + pub fn define_class( &self, name: &str, constructor_cb: Callback, properties: &[Property], - ) -> Result { + ) -> Result> { let mut raw_result = ptr::null_mut(); let raw_properties = properties .iter() @@ -755,7 +758,7 @@ impl Env { check_status!(unsafe { sys::napi_define_class( self.0, - c_name.as_ptr() as *const c_char, + c_name.as_ptr().cast(), name.len(), Some(constructor_cb), ptr::null_mut(), @@ -765,7 +768,7 @@ impl Env { ) })?; - Ok(unsafe { JsFunction::from_raw_unchecked(self.0, raw_result) }) + unsafe { Function::from_napi_value(self.0, raw_result) } } #[allow(clippy::needless_pass_by_ref_mut)] @@ -1055,17 +1058,22 @@ impl Env { } #[cfg(feature = "napi4")] + #[deprecated( + since = "2.17.0", + note = "Please use `Function::build_threadsafe_function` instead" + )] + #[allow(deprecated)] pub fn create_threadsafe_function< T: Send, V: ToNapiValue, - R: 'static + Send + FnMut(ThreadSafeCallContext) -> Result>, + R: 'static + Send + FnMut(ThreadsafeCallContext) -> Result>, >( &self, func: &JsFunction, - max_queue_size: usize, + _max_queue_size: usize, callback: R, ) -> Result> { - ThreadsafeFunction::create(self.0, func.0.value, max_queue_size, callback) + ThreadsafeFunction::create(self.0, func.0.value, callback) } #[cfg(all(feature = "tokio_rt", feature = "napi4"))] @@ -1403,7 +1411,7 @@ unsafe extern "C" fn set_instance_finalize_callback( { let (value, callback) = unsafe { *Box::from_raw(finalize_data as *mut (TaggedObject, F)) }; let hint = unsafe { *Box::from_raw(finalize_hint as *mut Hint) }; - let env = unsafe { Env::from_raw(raw_env) }; + let env = Env::from_raw(raw_env); callback(FinalizeContext { value: value.object.unwrap(), hint, @@ -1425,7 +1433,7 @@ unsafe extern "C" fn raw_finalize_with_custom_callback( Finalize: FnOnce(Hint, Env), { let (hint, callback) = unsafe { *Box::from_raw(finalize_hint as *mut (Hint, Finalize)) }; - callback(hint, unsafe { Env::from_raw(env) }); + callback(hint, Env::from_raw(env)); } #[cfg(feature = "napi8")] @@ -1449,22 +1457,20 @@ unsafe extern "C" fn async_finalize( #[cfg(feature = "napi5")] pub(crate) unsafe extern "C" fn trampoline< - R: ToNapiValue, - F: Fn(crate::CallContext) -> Result, + Return: ToNapiValue, + F: Fn(FunctionCallContext) -> Result, >( raw_env: sys::napi_env, cb_info: sys::napi_callback_info, ) -> sys::napi_value { - use crate::CallContext; + // Fast path for 4 arguments or less. + let mut argc = 4; + let mut raw_args = Vec::with_capacity(4); + let mut raw_this = ptr::null_mut(); + let mut closure_data_ptr = ptr::null_mut(); - let (raw_this, raw_args, closure_data_ptr, argc) = { - // Fast path for 4 arguments or less. - let mut argc = 4; - let mut raw_args = Vec::with_capacity(4); - let mut raw_this = ptr::null_mut(); - let mut closure_data_ptr = ptr::null_mut(); - - let status = unsafe { + check_status!( + unsafe { sys::napi_get_cb_info( raw_env, cb_info, @@ -1473,45 +1479,45 @@ pub(crate) unsafe extern "C" fn trampoline< &mut raw_this, &mut closure_data_ptr, ) - }; - debug_assert!( - Status::from(status) == Status::Ok, - "napi_get_cb_info failed" - ); - + }, + "napi_get_cb_info failed" + ) + .and_then(|_| { // Arguments length greater than 4, resize the vector. if argc > 4 { raw_args = vec![ptr::null_mut(); argc]; - let status = unsafe { - sys::napi_get_cb_info( - raw_env, - cb_info, - &mut argc, - raw_args.as_mut_ptr(), - &mut raw_this, - &mut closure_data_ptr, - ) - }; - debug_assert!( - Status::from(status) == Status::Ok, + check_status!( + unsafe { + sys::napi_get_cb_info( + raw_env, + cb_info, + &mut argc, + raw_args.as_mut_ptr(), + &mut raw_this, + &mut closure_data_ptr, + ) + }, "napi_get_cb_info failed" - ); + )?; } else { unsafe { raw_args.set_len(argc) }; } - - (raw_this, raw_args, closure_data_ptr, argc) - }; - - let closure: &F = Box::leak(unsafe { Box::from_raw(closure_data_ptr.cast()) }); - let mut env = unsafe { Env::from_raw(raw_env) }; - let call_context = CallContext::new(&mut env, cb_info, raw_this, raw_args.as_slice(), argc); - closure(call_context) - .and_then(|ret: R| unsafe { ::to_napi_value(env.0, ret) }) - .unwrap_or_else(|e| { - unsafe { JsError::from(e).throw_into(raw_env) }; - ptr::null_mut() + Ok((raw_this, raw_args, closure_data_ptr, argc)) + }) + .and_then(|(raw_this, raw_args, closure_data_ptr, _argc)| { + let closure: &F = Box::leak(unsafe { Box::from_raw(closure_data_ptr.cast()) }); + let mut env = Env::from_raw(raw_env); + closure(FunctionCallContext { + env: &mut env, + this: raw_this, + args: raw_args.as_slice(), }) + }) + .and_then(|ret| unsafe { ::to_napi_value(raw_env, ret) }) + .unwrap_or_else(|e| { + unsafe { JsError::from(e).throw_into(raw_env) }; + ptr::null_mut() + }) } #[cfg(feature = "napi5")] @@ -1551,7 +1557,7 @@ pub(crate) unsafe extern "C" fn trampoline_setter< }; let closure: &F = Box::leak(unsafe { Box::from_raw(closure_data_ptr.cast()) }); - let env = unsafe { Env::from_raw(raw_env) }; + let env = Env::from_raw(raw_env); raw_args .first() .ok_or_else(|| Error::new(Status::InvalidArg, "Missing argument in property setter")) @@ -1602,7 +1608,7 @@ pub(crate) unsafe extern "C" fn trampoline_getter< }; let closure: &F = Box::leak(unsafe { Box::from_raw(closure_data_ptr.cast()) }); - let env = unsafe { Env::from_raw(raw_env) }; + let env = Env::from_raw(raw_env); closure(env, unsafe { crate::bindgen_runtime::Object::from_raw_unchecked(raw_env, raw_this) }) diff --git a/crates/napi/src/error.rs b/crates/napi/src/error.rs index ce4db4cc..db35ed08 100644 --- a/crates/napi/src/error.rs +++ b/crates/napi/src/error.rs @@ -20,7 +20,7 @@ pub type Result = std::result::Result>; /// Represent `JsError`. /// Return this Error in `js_function`, **napi-rs** will throw it as `JsError` for you. /// If you want throw it as `TypeError` or `RangeError`, you can use `JsTypeError/JsRangeError::from(Error).throw_into(env)` -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct Error = Status> { pub status: S, pub reason: String, @@ -28,6 +28,17 @@ pub struct Error = Status> { pub(crate) maybe_raw: sys::napi_ref, } +impl> std::fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Error {{ status: {:?}, reason: {:?} }}", + self.status.as_ref(), + self.reason + ) + } +} + impl> ToNapiValue for Error { unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { if val.maybe_raw.is_null() { @@ -358,7 +369,13 @@ macro_rules! check_status_and_type { let value_type = $crate::type_of!($env, $val)?; let error_msg = match value_type { ValueType::Function => { - let function_name = unsafe { JsFunction::from_raw_unchecked($env, $val).name()? }; + let function_name = unsafe { + $crate::bindgen_prelude::Function::< + $crate::bindgen_prelude::Unknown, + $crate::bindgen_prelude::Unknown, + >::from_napi_value($env, $val)? + .name()? + }; format!( $msg, format!( diff --git a/crates/napi/src/js_values/deferred.rs b/crates/napi/src/js_values/deferred.rs index 8cde0dc9..0e32e812 100644 --- a/crates/napi/src/js_values/deferred.rs +++ b/crates/napi/src/js_values/deferred.rs @@ -23,7 +23,7 @@ struct DeferredTrace(sys::napi_ref); #[cfg(feature = "deferred_trace")] impl DeferredTrace { fn new(raw_env: sys::napi_env) -> Result { - let env = unsafe { Env::from_raw(raw_env) }; + let env = Env::from_raw(raw_env); let reason = env.create_string("none").unwrap(); let mut js_error = ptr::null_mut(); @@ -42,7 +42,7 @@ impl DeferredTrace { } fn into_rejected(self, raw_env: sys::napi_env, err: Error) -> Result { - let env = unsafe { Env::from_raw(raw_env) }; + let env = Env::from_raw(raw_env); let mut raw = ptr::null_mut(); check_status!( unsafe { sys::napi_get_reference_value(raw_env, self.0, &mut raw) }, @@ -210,7 +210,7 @@ extern "C" fn napi_resolve_deferred let deferred_data: Box> = unsafe { Box::from_raw(data.cast()) }; let result = deferred_data .resolver - .and_then(|resolver| resolver(unsafe { Env::from_raw(env) })) + .and_then(|resolver| resolver(Env::from_raw(env))) .and_then(|res| unsafe { ToNapiValue::to_napi_value(env, res) }); if let Err(e) = result.and_then(|res| { diff --git a/crates/napi/src/js_values/function.rs b/crates/napi/src/js_values/function.rs index ce19fe6a..734131e5 100644 --- a/crates/napi/src/js_values/function.rs +++ b/crates/napi/src/js_values/function.rs @@ -4,12 +4,13 @@ use super::Value; #[cfg(feature = "napi4")] use crate::{ bindgen_runtime::ToNapiValue, - threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction}, + threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunction}, }; use crate::{bindgen_runtime::TypeName, JsString}; use crate::{check_pending_exception, ValueType}; use crate::{sys, Env, Error, JsObject, JsUnknown, NapiRaw, NapiValue, Result, Status}; +#[deprecated(since = "2.17.0", note = "Please use `Function` instead")] pub struct JsFunction(pub(crate) Value); impl TypeName for JsFunction { @@ -45,7 +46,7 @@ impl JsFunction { let raw_this = this .map(|v| unsafe { v.raw() }) .or_else(|| { - unsafe { Env::from_raw(self.0.env) } + Env::from_raw(self.0.env) .get_undefined() .ok() .map(|u| unsafe { u.raw() }) @@ -76,7 +77,7 @@ impl JsFunction { let raw_this = this .map(|v| unsafe { v.raw() }) .or_else(|| { - unsafe { Env::from_raw(self.0.env) } + Env::from_raw(self.0.env) .get_undefined() .ok() .map(|u| unsafe { u.raw() }) @@ -138,17 +139,24 @@ impl JsFunction { } #[cfg(feature = "napi4")] - pub fn create_threadsafe_function( + pub fn create_threadsafe_function< + T, + V, + Return, + F, + const ES: bool, + const Weak: bool, + const MaxQueueSize: usize, + >( &self, - max_queue_size: usize, callback: F, - ) -> Result> + ) -> Result> where T: 'static, + Return: crate::bindgen_runtime::FromNapiValue, V: ToNapiValue, - F: 'static + Send + FnMut(ThreadSafeCallContext) -> Result>, - ES: crate::threadsafe_function::ErrorStrategy::T, + F: 'static + Send + FnMut(ThreadsafeCallContext) -> Result>, { - ThreadsafeFunction::create(self.0.env, self.0.value, max_queue_size, callback) + ThreadsafeFunction::create(self.0.env, self.0.value, callback) } } diff --git a/crates/napi/src/js_values/global.rs b/crates/napi/src/js_values/global.rs index 12a58b94..61e37661 100644 --- a/crates/napi/src/js_values/global.rs +++ b/crates/napi/src/js_values/global.rs @@ -1,7 +1,8 @@ -use std::convert::TryInto; - use super::*; -use crate::{bindgen_runtime::FromNapiValue, Env}; +use crate::{ + bindgen_runtime::{FromNapiValue, Function}, + threadsafe_function::UnknownReturnValue, +}; pub struct JsGlobal(pub(crate) Value); @@ -21,56 +22,41 @@ impl FromNapiValue for JSON { impl JSON { pub fn stringify(&self, value: V) -> Result { - let func: JsFunction = self.get_named_property_unchecked("stringify")?; - let result = func - .call(None, &[value]) - .map(|ret| unsafe { ret.cast::() })?; - result.into_utf8()?.as_str().map(|s| s.to_owned()) + let func: Function = self.get_named_property_unchecked("stringify")?; + func.call(value) } } impl JsGlobal { - pub fn set_interval(&self, handler: JsFunction, interval: f64) -> Result { - let func: JsFunction = self.get_named_property_unchecked("setInterval")?; - func - .call( - None, - &[ - handler.into_unknown(), - unsafe { Env::from_raw(self.0.env) } - .create_double(interval)? - .into_unknown(), - ], - ) - .and_then(|ret| ret.try_into()) + pub fn set_interval( + &self, + handler: Function<(), UnknownReturnValue>, + interval: f64, + ) -> Result { + let func: Function<(Function<(), UnknownReturnValue>, f64), JsTimeout> = + self.get_named_property_unchecked("setInterval")?; + func.call((handler, interval)) } pub fn clear_interval(&self, timer: JsTimeout) -> Result { - let func: JsFunction = self.get_named_property_unchecked("clearInterval")?; - func - .call(None, &[timer.into_unknown()]) - .and_then(|ret| ret.try_into()) + let func: Function = + self.get_named_property_unchecked("clearInterval")?; + func.call(timer) } - pub fn set_timeout(&self, handler: JsFunction, interval: f64) -> Result { - let func: JsFunction = self.get_named_property_unchecked("setTimeout")?; - func - .call( - None, - &[ - handler.into_unknown(), - unsafe { Env::from_raw(self.0.env) } - .create_double(interval)? - .into_unknown(), - ], - ) - .and_then(|ret| ret.try_into()) + pub fn set_timeout( + &self, + handler: Function<(), UnknownReturnValue>, + interval: f64, + ) -> Result { + let func: Function<(Function<(), UnknownReturnValue>, f64), JsTimeout> = + self.get_named_property_unchecked("setTimeout")?; + func.call((handler, interval)) } pub fn clear_timeout(&self, timer: JsTimeout) -> Result { - let func: JsFunction = self.get_named_property_unchecked("clearTimeout")?; - func - .call(None, &[timer.into_unknown()]) - .and_then(|ret| ret.try_into()) + let func: Function = + self.get_named_property_unchecked("clearTimeout")?; + func.call(timer) } } diff --git a/crates/napi/src/js_values/mod.rs b/crates/napi/src/js_values/mod.rs index 2deeffa8..a660241a 100644 --- a/crates/napi/src/js_values/mod.rs +++ b/crates/napi/src/js_values/mod.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] + use std::convert::TryFrom; #[cfg(feature = "napi5")] use std::ffi::c_void; diff --git a/crates/napi/src/js_values/object.rs b/crates/napi/src/js_values/object.rs index 77507f00..eb1e05e9 100644 --- a/crates/napi/src/js_values/object.rs +++ b/crates/napi/src/js_values/object.rs @@ -76,7 +76,7 @@ unsafe extern "C" fn finalize_callback( let (value, callback, raw_ref) = unsafe { *Box::from_raw(finalize_data as *mut (T, F, sys::napi_ref)) }; let hint = unsafe { *Box::from_raw(finalize_hint as *mut Hint) }; - let env = unsafe { Env::from_raw(raw_env) }; + let env = Env::from_raw(raw_env); callback(FinalizeContext { env, value, hint }); if !raw_ref.is_null() { let status = unsafe { sys::napi_delete_reference(raw_env, raw_ref) }; diff --git a/crates/napi/src/js_values/ser.rs b/crates/napi/src/js_values/ser.rs index 75316d2f..9a53b2ba 100644 --- a/crates/napi/src/js_values/ser.rs +++ b/crates/napi/src/js_values/ser.rs @@ -308,7 +308,7 @@ impl ser::SerializeSeq for SeqSerializer { where T: Serialize, { - let env = unsafe { Env::from_raw(self.array.0.env) }; + let env = Env::from_raw(self.array.0.env); self.array.set_element( self.current_index as _, JsUnknown(value.serialize(Ser::new(&env))?), @@ -331,7 +331,7 @@ impl ser::SerializeTuple for SeqSerializer { where T: Serialize, { - let env = unsafe { Env::from_raw(self.array.0.env) }; + let env = Env::from_raw(self.array.0.env); self.array.set_element( self.current_index as _, JsUnknown(value.serialize(Ser::new(&env))?), @@ -354,7 +354,7 @@ impl ser::SerializeTupleStruct for SeqSerializer { where T: Serialize, { - let env = unsafe { Env::from_raw(self.array.0.env) }; + let env = Env::from_raw(self.array.0.env); self.array.set_element( self.current_index as _, JsUnknown(value.serialize(Ser::new(&env))?), @@ -377,7 +377,7 @@ impl ser::SerializeTupleVariant for SeqSerializer { where T: Serialize, { - let env = unsafe { Env::from_raw(self.array.0.env) }; + let env = Env::from_raw(self.array.0.env); self.array.set_element( self.current_index as _, JsUnknown(value.serialize(Ser::new(&env))?), @@ -405,7 +405,7 @@ impl ser::SerializeMap for MapSerializer { where T: Serialize, { - let env = unsafe { Env::from_raw(self.obj.0.env) }; + let env = Env::from_raw(self.obj.0.env); self.key = JsString(key.serialize(Ser::new(&env))?); Ok(()) } @@ -414,7 +414,7 @@ impl ser::SerializeMap for MapSerializer { where T: Serialize, { - let env = unsafe { Env::from_raw(self.obj.0.env) }; + let env = Env::from_raw(self.obj.0.env); self.obj.set_property( JsString(Value { env: self.key.0.env, @@ -435,7 +435,7 @@ impl ser::SerializeMap for MapSerializer { K: Serialize, V: Serialize, { - let env = unsafe { Env::from_raw(self.obj.0.env) }; + let env = Env::from_raw(self.obj.0.env); self.obj.set_property( JsString(key.serialize(Ser::new(&env))?), JsUnknown(value.serialize(Ser::new(&env))?), @@ -461,7 +461,7 @@ impl ser::SerializeStruct for StructSerializer { where T: Serialize, { - let env = unsafe { Env::from_raw(self.obj.0.env) }; + let env = Env::from_raw(self.obj.0.env); self .obj .set_named_property(key, JsUnknown(value.serialize(Ser::new(&env))?))?; @@ -482,7 +482,7 @@ impl ser::SerializeStructVariant for StructSerializer { where T: Serialize, { - let env = unsafe { Env::from_raw(self.obj.0.env) }; + let env = Env::from_raw(self.obj.0.env); self .obj .set_named_property(key, JsUnknown(value.serialize(Ser::new(&env))?))?; diff --git a/crates/napi/src/task.rs b/crates/napi/src/task.rs index 96ba80c9..1d5d9ce4 100644 --- a/crates/napi/src/task.rs +++ b/crates/napi/src/task.rs @@ -13,13 +13,15 @@ pub trait Task: Send + Sized { /// Into this method if `compute` return `Ok` fn resolve(&mut self, env: Env, output: Self::Output) -> Result; + #[allow(unused_variables)] /// Into this method if `compute` return `Err` - fn reject(&mut self, _env: Env, err: Error) -> Result { + fn reject(&mut self, env: Env, err: Error) -> Result { Err(err) } - // after resolve or reject - fn finally(&mut self, _env: Env) -> Result<()> { + #[allow(unused_variables)] + /// after resolve or reject + fn finally(&mut self, env: Env) -> Result<()> { Ok(()) } } diff --git a/crates/napi/src/threadsafe_function.rs b/crates/napi/src/threadsafe_function.rs index 41df54d4..c16e9097 100644 --- a/crates/napi/src/threadsafe_function.rs +++ b/crates/napi/src/threadsafe_function.rs @@ -1,21 +1,26 @@ #![allow(clippy::single_component_path_imports)] use std::convert::Into; -use std::ffi::CString; use std::marker::PhantomData; use std::os::raw::c_void; use std::ptr::{self, null_mut}; -use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; -use std::sync::{Arc, RwLock, RwLockWriteGuard, Weak}; +use std::sync::{ + self, + atomic::{AtomicBool, AtomicPtr, Ordering}, + Arc, RwLock, RwLockWriteGuard, +}; use crate::bindgen_runtime::{ - FromNapiValue, JsValuesTupleIntoVec, ToNapiValue, TypeName, ValidateNapiValue, + FromNapiValue, JsValuesTupleIntoVec, ToNapiValue, TypeName, Unknown, ValidateNapiValue, }; -use crate::{check_status, sys, Env, JsError, JsUnknown, Result, Status}; +use crate::{check_status, sys, Env, Error, JsError, Result, Status}; + +#[deprecated(since = "2.17.0", note = "Please use `ThreadsafeFunction` instead")] +pub type ThreadSafeCallContext = ThreadsafeCallContext; /// ThreadSafeFunction Context object /// the `value` is the value passed to `call` method -pub struct ThreadSafeCallContext { +pub struct ThreadsafeCallContext { pub env: Env, pub value: T, } @@ -36,68 +41,6 @@ impl From for sys::napi_threadsafe_function_call_mod } } -type_level_enum! { - /// Type-level `enum` to express how to feed [`ThreadsafeFunction`] errors to - /// the inner [`JsFunction`]. - /// - /// ### Context - /// - /// For callbacks that expect a `Result`-like kind of input, the convention is - /// to have the callback take an `error` parameter as its first parameter. - /// - /// This way receiving a `Result` can be modelled as follows: - /// - /// - In case of `Err(error)`, feed that `error` entity as the first parameter - /// of the callback; - /// - /// - Otherwise (in case of `Ok(_)`), feed `null` instead. - /// - /// In pseudo-code: - /// - /// ```rust,ignore - /// match result_args { - /// Ok(args) => { - /// let js_null = /* … */; - /// callback.call( - /// // this - /// None, - /// // args… - /// &iter::once(js_null).chain(args).collect::>(), - /// ) - /// }, - /// Err(err) => callback.call(None, &[JsError::from(err)]), - /// } - /// ``` - /// - /// **Note that the `Err` case can stem from a failed conversion from native - /// values to js values when calling the callback!** - /// - /// That's why: - /// - /// > **[This][`ErrorStrategy::CalleeHandled`] is the default error strategy**. - /// - /// In order to opt-out of it, [`ThreadsafeFunction`] has an optional second - /// generic parameter (of "kind" [`ErrorStrategy::T`]) that defines whether - /// this behavior ([`ErrorStrategy::CalleeHandled`]) or a non-`Result` one - /// ([`ErrorStrategy::Fatal`]) is desired. - pub enum ErrorStrategy { - /// Input errors (including conversion errors) are left for the callee to - /// handle: - /// - /// The callee receives an extra `error` parameter (the first one), which is - /// `null` if no error occurred, and the error payload otherwise. - CalleeHandled, - - /// Input errors (including conversion errors) are deemed fatal: - /// - /// they can thus cause a `panic!` or abort the process. - /// - /// The callee thus is not expected to have to deal with [that extra `error` - /// parameter][CalleeHandled], which is thus not added. - Fatal, - } -} - struct ThreadsafeFunctionHandle { raw: AtomicPtr, aborted: RwLock, @@ -178,10 +121,10 @@ enum ThreadsafeFunctionCallVariant { WithCallback, } -struct ThreadsafeFunctionCallJsBackData { +struct ThreadsafeFunctionCallJsBackData { data: T, call_variant: ThreadsafeFunctionCallVariant, - callback: Box) -> Result<()>>, + callback: Box, Env) -> Result<()>>, } /// Communicate with the addon's main thread by invoking a JavaScript function from other threads. @@ -190,58 +133,70 @@ struct ThreadsafeFunctionCallJsBackData { /// An example of using `ThreadsafeFunction`: /// /// ```rust -/// #[macro_use] -/// extern crate napi_derive; -/// /// use std::thread; /// /// use napi::{ /// threadsafe_function::{ /// ThreadSafeCallContext, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode, /// }, -/// CallContext, Error, JsFunction, JsNumber, JsUndefined, Result, Status, /// }; +/// use napi_derive::napi; /// -/// #[js_function(1)] -/// pub fn test_threadsafe_function(ctx: CallContext) -> Result { -/// let func = ctx.get::(0)?; -/// -/// let tsfn = -/// ctx -/// .env -/// .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext>| { -/// ctx.value -/// .iter() -/// .map(|v| ctx.env.create_uint32(*v)) -/// .collect::>>() -/// })?; -/// +/// #[napi] +/// pub fn call_threadsafe_function(callback: ThreadsafeFunction<(u32, bool, String), ()>) { /// let tsfn_cloned = tsfn.clone(); /// /// thread::spawn(move || { /// let output: Vec = vec![0, 1, 2, 3]; /// // It's okay to call a threadsafe function multiple times. -/// tsfn.call(Ok(output.clone()), ThreadsafeFunctionCallMode::Blocking); +/// tsfn.call(Ok((1, false, "NAPI-RS".into())), ThreadsafeFunctionCallMode::Blocking); +/// tsfn.call(Ok((2, true, "NAPI-RS".into())), ThreadsafeFunctionCallMode::NonBlocking); /// }); /// /// thread::spawn(move || { -/// let output: Vec = vec![3, 2, 1, 0]; -/// // It's okay to call a threadsafe function multiple times. -/// tsfn_cloned.call(Ok(output.clone()), ThreadsafeFunctionCallMode::NonBlocking); +/// tsfn_cloned.call((3, false, "NAPI-RS".into())), ThreadsafeFunctionCallMode::NonBlocking); /// }); -/// -/// ctx.env.get_undefined() /// } /// ``` -pub struct ThreadsafeFunction { +pub struct ThreadsafeFunction< + T: 'static, + Return: FromNapiValue + 'static = Unknown, + const CalleeHandled: bool = true, + const Weak: bool = false, + const MaxQueueSize: usize = 0, +> { handle: Arc, - _phantom: PhantomData<(T, ES)>, + _phantom: PhantomData<(T, Return)>, } -unsafe impl Send for ThreadsafeFunction {} -unsafe impl Sync for ThreadsafeFunction {} +unsafe impl< + T: 'static, + Return: FromNapiValue, + const CalleeHandled: bool, + const Weak: bool, + const MaxQueueSize: usize, + > Send for ThreadsafeFunction +{ +} -impl Clone for ThreadsafeFunction { +unsafe impl< + T: 'static, + Return: FromNapiValue, + const CalleeHandled: bool, + const Weak: bool, + const MaxQueueSize: usize, + > Sync for ThreadsafeFunction +{ +} + +impl< + T: 'static, + Return: FromNapiValue, + const CalleeHandled: bool, + const Weak: bool, + const MaxQueueSize: usize, + > Clone for ThreadsafeFunction +{ fn clone(&self) -> Self { self.handle.with_read_aborted(|aborted| { if aborted { @@ -256,86 +211,67 @@ impl Clone for ThreadsafeFunction { } } -impl JsValuesTupleIntoVec for T { - #[allow(clippy::not_unsafe_ptr_arg_deref)] - fn into_vec(self, env: sys::napi_env) -> Result> { - Ok(vec![unsafe { - ::to_napi_value(env, self)? - }]) - } -} - -macro_rules! impl_js_value_tuple_to_vec { - ($($ident:ident),*) => { - impl<$($ident: ToNapiValue),*> JsValuesTupleIntoVec for ($($ident,)*) { - #[allow(clippy::not_unsafe_ptr_arg_deref)] - fn into_vec(self, env: sys::napi_env) -> Result> { - #[allow(non_snake_case)] - let ($($ident,)*) = self; - Ok(vec![$(unsafe { <$ident as ToNapiValue>::to_napi_value(env, $ident)? }),*]) - } - } - }; -} - -impl_js_value_tuple_to_vec!(A); -impl_js_value_tuple_to_vec!(A, B); -impl_js_value_tuple_to_vec!(A, B, C); -impl_js_value_tuple_to_vec!(A, B, C, D); -impl_js_value_tuple_to_vec!(A, B, C, D, E); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W); -impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X); -impl_js_value_tuple_to_vec!( - A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y -); -impl_js_value_tuple_to_vec!( - A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z -); - -impl FromNapiValue - for ThreadsafeFunction +impl< + T: JsValuesTupleIntoVec + 'static, + Return: FromNapiValue, + const CalleeHandled: bool, + const Weak: bool, + const MaxQueueSize: usize, + > FromNapiValue for ThreadsafeFunction { unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { - Self::create(env, napi_val, 0, |ctx| ctx.value.into_vec(ctx.env.0)) + Self::create(env, napi_val, |ctx| ctx.value.into_vec(ctx.env.0)) } } -impl ThreadsafeFunction { - /// See [napi_create_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function) - /// for more information. +impl< + T: 'static, + Return: FromNapiValue, + const CalleeHandled: bool, + const Weak: bool, + const MaxQueueSize: usize, + > ThreadsafeFunction +{ + // See [napi_create_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function) + // for more information. pub(crate) fn create< V: ToNapiValue, - R: 'static + Send + FnMut(ThreadSafeCallContext) -> Result>, + R: 'static + Send + FnMut(ThreadsafeCallContext) -> Result>, >( env: sys::napi_env, func: sys::napi_value, - max_queue_size: usize, callback: R, ) -> Result { let mut async_resource_name = ptr::null_mut(); - let s = "napi_rs_threadsafe_function"; - let len = s.len(); - let s = CString::new(s)?; - check_status!(unsafe { - sys::napi_create_string_utf8(env, s.as_ptr(), len, &mut async_resource_name) - })?; + static THREAD_SAFE_FUNCTION_ASYNC_RESOURCE_NAME: &str = "napi_rs_threadsafe_function"; + + #[cfg(feature = "experimental")] + { + check_status!(unsafe { + let mut copied = false; + sys::node_api_create_external_string_latin1( + env, + THREAD_SAFE_FUNCTION_ASYNC_RESOURCE_NAME.as_ptr().cast(), + 27, + None, + ptr::null_mut(), + &mut async_resource_name, + &mut copied, + ) + })?; + } + + #[cfg(not(feature = "experimental"))] + { + check_status!(unsafe { + sys::napi_create_string_utf8( + env, + THREAD_SAFE_FUNCTION_ASYNC_RESOURCE_NAME.as_ptr().cast(), + 27, + &mut async_resource_name, + ) + })?; + } let mut raw_tsfn = ptr::null_mut(); let callback_ptr = Box::into_raw(Box::new(callback)); @@ -346,23 +282,35 @@ impl ThreadsafeFunction { func, ptr::null_mut(), async_resource_name, - max_queue_size, + MaxQueueSize, 1, - Arc::downgrade(&handle).into_raw() as *mut c_void, // pass handler to thread_finalize_cb + Arc::downgrade(&handle).into_raw().cast_mut().cast(), // pass handler to thread_finalize_cb Some(thread_finalize_cb::), callback_ptr.cast(), - Some(call_js_cb::), + Some(call_js_cb::), &mut raw_tsfn, ) })?; handle.set_raw(raw_tsfn); + // Weak ThreadsafeFunction will not prevent the event loop from exiting + if Weak { + check_status!( + unsafe { sys::napi_unref_threadsafe_function(env, raw_tsfn) }, + "Unref threadsafe function failed in Weak mode" + )?; + } + Ok(ThreadsafeFunction { handle, _phantom: PhantomData, }) } + #[deprecated( + since = "2.17.0", + note = "Please use `ThreadsafeFunction::clone` instead of manually increasing the reference count" + )] /// See [napi_ref_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_ref_threadsafe_function) /// for more information. /// @@ -377,6 +325,10 @@ impl ThreadsafeFunction { }) } + #[deprecated( + since = "2.17.0", + note = "Please use `ThreadsafeFunction::clone` instead of manually decreasing the reference count" + )] /// See [napi_unref_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_unref_threadsafe_function) /// for more information. pub fn unref(&mut self, env: &Env) -> Result<()> { @@ -395,6 +347,10 @@ impl ThreadsafeFunction { self.handle.with_read_aborted(|aborted| aborted) } + #[deprecated( + since = "2.17.0", + note = "Drop all references to the ThreadsafeFunction will automatically release it" + )] pub fn abort(self) -> Result<()> { self.handle.with_write_aborted(|mut aborted_guard| { if !*aborted_guard { @@ -416,7 +372,9 @@ impl ThreadsafeFunction { } } -impl ThreadsafeFunction { +impl + ThreadsafeFunction +{ /// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function) /// for more information. pub fn call(&self, value: Result, mode: ThreadsafeFunctionCallMode) -> Status { @@ -432,7 +390,7 @@ impl ThreadsafeFunction { ThreadsafeFunctionCallJsBackData { data, call_variant: ThreadsafeFunctionCallVariant::Direct, - callback: Box::new(|_d: Result| Ok(())), + callback: Box::new(|_d: Result, _| Ok(())), } }))) .cast(), @@ -443,7 +401,8 @@ impl ThreadsafeFunction { }) } - pub fn call_with_return_value Result<()>>( + /// Call the ThreadsafeFunction, and handle the return value with a callback + pub fn call_with_return_value, Env) -> Result<()>>( &self, value: Result, mode: ThreadsafeFunctionCallMode, @@ -461,9 +420,7 @@ impl ThreadsafeFunction { ThreadsafeFunctionCallJsBackData { data, call_variant: ThreadsafeFunctionCallVariant::WithCallback, - callback: Box::new(move |d: Result| { - d.and_then(|d| D::from_napi_value(d.0.env, d.0.value).and_then(cb)) - }), + callback: Box::new(move |d: Result, env: Env| cb(d, env)), } }))) .cast(), @@ -475,8 +432,9 @@ impl ThreadsafeFunction { } #[cfg(feature = "tokio_rt")] - pub async fn call_async(&self, value: Result) -> Result { - let (sender, receiver) = tokio::sync::oneshot::channel::>(); + /// Call the ThreadsafeFunction, and handle the return value with in `async` way + pub async fn call_async(&self, value: Result) -> Result { + let (sender, receiver) = tokio::sync::oneshot::channel::>(); self.handle.with_read_aborted(|aborted| { if aborted { @@ -491,9 +449,9 @@ impl ThreadsafeFunction { ThreadsafeFunctionCallJsBackData { data, call_variant: ThreadsafeFunctionCallVariant::WithCallback, - callback: Box::new(move |d: Result| { + callback: Box::new(move |d: Result, _| { sender - .send(d.and_then(|d| D::from_napi_value(d.0.env, d.0.value))) + .send(d) // The only reason for send to return Err is if the receiver isn't listening // Not hiding the error would result in a napi_fatal_error call, it's safe to ignore it instead. .or(Ok(())) @@ -519,7 +477,9 @@ impl ThreadsafeFunction { } } -impl ThreadsafeFunction { +impl + ThreadsafeFunction +{ /// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function) /// for more information. pub fn call(&self, value: T, mode: ThreadsafeFunctionCallMode) -> Status { @@ -534,7 +494,7 @@ impl ThreadsafeFunction { Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData { data: value, call_variant: ThreadsafeFunctionCallVariant::Direct, - callback: Box::new(|_d: Result| Ok(())), + callback: Box::new(|_d: Result, _: Env| Ok(())), })) .cast(), mode.into(), @@ -544,7 +504,8 @@ impl ThreadsafeFunction { }) } - pub fn call_with_return_value Result<()>>( + /// Call the ThreadsafeFunction, and handle the return value with a callback + pub fn call_with_return_value, Env) -> Result<()>>( &self, value: T, mode: ThreadsafeFunctionCallMode, @@ -561,9 +522,7 @@ impl ThreadsafeFunction { Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData { data: value, call_variant: ThreadsafeFunctionCallVariant::WithCallback, - callback: Box::new(move |d: Result| { - d.and_then(|d| D::from_napi_value(d.0.env, d.0.value).and_then(cb)) - }), + callback: Box::new(cb), })) .cast(), mode.into(), @@ -574,8 +533,9 @@ impl ThreadsafeFunction { } #[cfg(feature = "tokio_rt")] - pub async fn call_async(&self, value: T) -> Result { - let (sender, receiver) = tokio::sync::oneshot::channel::(); + /// Call the ThreadsafeFunction, and handle the return value with in `async` way + pub async fn call_async(&self, value: T) -> Result { + let (sender, receiver) = tokio::sync::oneshot::channel::(); self.handle.with_read_aborted(|aborted| { if aborted { @@ -588,15 +548,13 @@ impl ThreadsafeFunction { Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData { data: value, call_variant: ThreadsafeFunctionCallVariant::WithCallback, - callback: Box::new(move |d: Result| { + callback: Box::new(move |d, _| { d.and_then(|d| { - D::from_napi_value(d.0.env, d.0.value).and_then(move |d| { - sender - .send(d) - // The only reason for send to return Err is if the receiver isn't listening - // Not hiding the error would result in a napi_fatal_error call, it's safe to ignore it instead. - .or(Ok(())) - }) + sender + .send(d) + // The only reason for send to return Err is if the receiver isn't listening + // Not hiding the error would result in a napi_fatal_error call, it's safe to ignore it instead. + .or(Ok(())) }) }), })) @@ -612,16 +570,15 @@ impl ThreadsafeFunction { } } -#[allow(unused_variables)] unsafe extern "C" fn thread_finalize_cb( - env: sys::napi_env, + #[allow(unused_variables)] env: sys::napi_env, finalize_data: *mut c_void, finalize_hint: *mut c_void, ) where - R: 'static + Send + FnMut(ThreadSafeCallContext) -> Result>, + R: 'static + Send + FnMut(ThreadsafeCallContext) -> Result>, { - let handle_option = - unsafe { Weak::from_raw(finalize_data.cast::()).upgrade() }; + let handle_option: Option> = + unsafe { sync::Weak::from_raw(finalize_data.cast()).upgrade() }; if let Some(handle) = handle_option { handle.with_write_aborted(|mut aborted_guard| { @@ -635,29 +592,31 @@ unsafe extern "C" fn thread_finalize_cb( drop(unsafe { Box::::from_raw(finalize_hint.cast()) }); } -unsafe extern "C" fn call_js_cb( +unsafe extern "C" fn call_js_cb< + T: 'static, + Return: FromNapiValue, + V: ToNapiValue, + R, + const CalleeHandled: bool, +>( raw_env: sys::napi_env, js_callback: sys::napi_value, context: *mut c_void, data: *mut c_void, ) where - R: 'static + Send + FnMut(ThreadSafeCallContext) -> Result>, - ES: ErrorStrategy::T, + R: 'static + Send + FnMut(ThreadsafeCallContext) -> Result>, { // env and/or callback can be null when shutting down if raw_env.is_null() || js_callback.is_null() { return; } - let ctx: &mut R = unsafe { Box::leak(Box::from_raw(context.cast())) }; + let callback: &mut R = unsafe { Box::leak(Box::from_raw(context.cast())) }; let val = unsafe { - match ES::VALUE { - ErrorStrategy::CalleeHandled::VALUE => { - *Box::>>::from_raw(data.cast()) - } - ErrorStrategy::Fatal::VALUE => Ok(*Box::>::from_raw( - data.cast(), - )), + if CalleeHandled { + *Box::>>::from_raw(data.cast()) + } else { + Ok(*Box::>::from_raw(data.cast())) } }; @@ -665,8 +624,8 @@ unsafe extern "C" fn call_js_cb( unsafe { sys::napi_get_undefined(raw_env, &mut recv) }; let ret = val.and_then(|v| { - (ctx)(ThreadSafeCallContext { - env: unsafe { Env::from_raw(raw_env) }, + (callback)(ThreadsafeCallContext { + env: Env::from_raw(raw_env), value: v.data, }) .map(|ret| (ret, v.call_variant, v.callback)) @@ -680,7 +639,7 @@ unsafe extern "C" fn call_js_cb( let values = values .into_iter() .map(|v| unsafe { ToNapiValue::to_napi_value(raw_env, v) }); - let args: Result> = if ES::VALUE == ErrorStrategy::CalleeHandled::VALUE { + let args: Result> = if CalleeHandled { let mut js_null = ptr::null_mut(); unsafe { sys::napi_get_null(raw_env, &mut js_null) }; ::core::iter::once(Ok(js_null)).chain(values).collect() @@ -699,62 +658,45 @@ unsafe extern "C" fn call_js_cb( &mut return_value, ) }, - Err(e) => match ES::VALUE { - ErrorStrategy::Fatal::VALUE => unsafe { - sys::napi_fatal_exception(raw_env, JsError::from(e).into_value(raw_env)) - }, - ErrorStrategy::CalleeHandled::VALUE => unsafe { - sys::napi_call_function( - raw_env, - recv, - js_callback, - 1, - [JsError::from(e).into_value(raw_env)].as_mut_ptr(), - &mut return_value, - ) - }, - }, + Err(e) => { + if CalleeHandled { + unsafe { sys::napi_fatal_exception(raw_env, JsError::from(e).into_value(raw_env)) } + } else { + unsafe { + sys::napi_call_function( + raw_env, + recv, + js_callback, + 1, + [JsError::from(e).into_value(raw_env)].as_mut_ptr(), + &mut return_value, + ) + } + } + } }; if let ThreadsafeFunctionCallVariant::WithCallback = call_variant { // throw Error in JavaScript callback let callback_arg = if status == sys::Status::napi_pending_exception { let mut exception = ptr::null_mut(); status = unsafe { sys::napi_get_and_clear_last_exception(raw_env, &mut exception) }; - Err( - JsUnknown(crate::Value { - env: raw_env, - value: exception, - value_type: crate::ValueType::Unknown, - }) - .into(), - ) + let mut error_reference = ptr::null_mut(); + unsafe { sys::napi_create_reference(raw_env, exception, 1, &mut error_reference) }; + Err(Error { + maybe_raw: error_reference, + status: Status::from(status), + reason: "".to_owned(), + }) } else { - Ok(JsUnknown(crate::Value { - env: raw_env, - value: return_value, - value_type: crate::ValueType::Unknown, - })) + unsafe { Return::from_napi_value(raw_env, return_value) } }; - if let Err(err) = callback(callback_arg) { - let message = format!( - "Failed to convert return value in ThreadsafeFunction callback into Rust value: {}", - err - ); - let message_length = message.len(); - let c_message = CString::new(message).unwrap(); - unsafe { - sys::napi_fatal_error( - "threadsafe_function.rs:749\0".as_ptr().cast(), - 26, - c_message.as_ptr(), - message_length, - ) - }; + if let Err(err) = callback(callback_arg, Env::from_raw(raw_env)) { + unsafe { sys::napi_fatal_exception(raw_env, JsError::from(err).into_value(raw_env)) }; } } status } - Err(e) if ES::VALUE == ErrorStrategy::Fatal::VALUE => unsafe { + Err(e) if !CalleeHandled => unsafe { sys::napi_fatal_exception(raw_env, JsError::from(e).into_value(raw_env)) }, Err(e) => unsafe { @@ -783,27 +725,27 @@ unsafe extern "C" fn call_js_cb( assert!(stat == sys::Status::napi_ok || stat == sys::Status::napi_pending_exception); } else { let error_code: Status = status.into(); - let error_code_string = format!("{:?}", error_code); + let error_code_string = format!("{}", error_code); let mut error_code_value = ptr::null_mut(); assert_eq!( unsafe { sys::napi_create_string_utf8( raw_env, - error_code_string.as_ptr() as *const _, + error_code_string.as_ptr().cast(), error_code_string.len(), &mut error_code_value, ) }, sys::Status::napi_ok, ); - let error_msg = "Call JavaScript callback failed in threadsafe function"; + static ERROR_MSG: &str = "Call JavaScript callback failed in threadsafe function"; let mut error_msg_value = ptr::null_mut(); assert_eq!( unsafe { sys::napi_create_string_utf8( raw_env, - error_msg.as_ptr() as *const _, - error_msg.len(), + ERROR_MSG.as_ptr().cast(), + ERROR_MSG.len(), &mut error_msg_value, ) }, @@ -823,99 +765,6 @@ unsafe extern "C" fn call_js_cb( } } -/// Helper -macro_rules! type_level_enum {( - $( #[doc = $doc:tt] )* - $pub:vis - enum $EnumName:ident { - $( - $( #[doc = $doc_variant:tt] )* - $Variant:ident - ),* $(,)? - } -) => (type_level_enum! { // This requires the macro to be in scope when called. - with_docs! { - $( #[doc = $doc] )* - /// - /// ### Type-level `enum` - /// - /// Until `const_generics` can handle custom `enum`s, this pattern must be - /// implemented at the type level. - /// - /// We thus end up with: - /// - /// ```rust,ignore - /// #[type_level_enum] - #[doc = ::core::concat!( - " enum ", ::core::stringify!($EnumName), " {", - )] - $( - #[doc = ::core::concat!( - " ", ::core::stringify!($Variant), ",", - )] - )* - #[doc = " }"] - /// ``` - /// - #[doc = ::core::concat!( - "With [`", ::core::stringify!($EnumName), "::T`](#reexports) \ - being the type-level \"enum type\":", - )] - /// - /// ```rust,ignore - #[doc = ::core::concat!( - "" - )] - /// ``` - } - #[allow(warnings)] - $pub mod $EnumName { - #[doc(no_inline)] - pub use $EnumName as T; - - super::type_level_enum! { - with_docs! { - #[doc = ::core::concat!( - "See [`", ::core::stringify!($EnumName), "`]\ - [super::", ::core::stringify!($EnumName), "]" - )] - } - pub trait $EnumName : __sealed::$EnumName + ::core::marker::Sized + 'static { - const VALUE: __value::$EnumName; - } - } - - mod __sealed { pub trait $EnumName {} } - - mod __value { - #[derive(Debug, PartialEq, Eq)] - pub enum $EnumName { $( $Variant ),* } - } - - $( - $( #[doc = $doc_variant] )* - pub enum $Variant {} - impl __sealed::$EnumName for $Variant {} - impl $EnumName for $Variant { - const VALUE: __value::$EnumName = __value::$EnumName::$Variant; - } - impl $Variant { - pub const VALUE: __value::$EnumName = __value::$EnumName::$Variant; - } - )* - } -});( - with_docs! { - $( #[doc = $doc:expr] )* - } - $item:item -) => ( - $( #[doc = $doc] )* - $item -)} - -use type_level_enum; - pub struct UnknownReturnValue; impl TypeName for UnknownReturnValue { diff --git a/examples/napi-compat-mode/__tests__/function.spec.ts b/examples/napi-compat-mode/__tests__/function.spec.ts index 0c8e30a5..d1546ec4 100644 --- a/examples/napi-compat-mode/__tests__/function.spec.ts +++ b/examples/napi-compat-mode/__tests__/function.spec.ts @@ -36,7 +36,7 @@ test('should be able to create function from closure', (t) => { for (let i = 0; i < 100; i++) { t.is( bindings.testCreateFunctionFromClosure()( - ...Array.from({ length: i }).map((_, i) => i), + ...Array.from({ length: i }, (_, i) => i), ), `arguments length: ${i}`, ) diff --git a/examples/napi-compat-mode/src/class.rs b/examples/napi-compat-mode/src/class.rs index 7a2460b5..484f31f0 100644 --- a/examples/napi-compat-mode/src/class.rs +++ b/examples/napi-compat-mode/src/class.rs @@ -1,13 +1,16 @@ use std::convert::TryInto; -use napi::{CallContext, JsFunction, JsNumber, JsObject, JsUndefined, Property, Result}; +use napi::{ + bindgen_prelude::{Function, Unknown}, + CallContext, JsNumber, JsObject, JsUndefined, Property, Result, +}; struct NativeClass { value: i32, } #[js_function(1)] -fn create_test_class(ctx: CallContext) -> Result { +fn create_test_class(ctx: CallContext) -> Result> { let add_count_method = Property::new("addCount")?.with_method(add_count); let add_native_count = Property::new("addNativeCount")?.with_method(add_native_count); let renew_wrapped = Property::new("renewWrapped")?.with_method(renew_wrapped); @@ -56,7 +59,7 @@ fn renew_wrapped(ctx: CallContext) -> Result { } #[js_function(1)] -fn new_test_class(ctx: CallContext) -> Result { +fn new_test_class(ctx: CallContext) -> Result { let add_count_method = Property::new("addCount")?.with_method(add_count); let add_native_count = Property::new("addNativeCount")?.with_method(add_native_count); let properties = vec![add_count_method, add_native_count]; @@ -65,7 +68,7 @@ fn new_test_class(ctx: CallContext) -> Result { .env .define_class("TestClass", test_class_constructor, properties.as_slice())?; - test_class.new_instance(&[ctx.env.create_int32(42)?]) + test_class.new_instance(42) } pub fn register_js(exports: &mut JsObject) -> Result<()> { diff --git a/examples/napi-compat-mode/src/function.rs b/examples/napi-compat-mode/src/function.rs index c51bc9f5..6e62e0b8 100644 --- a/examples/napi-compat-mode/src/function.rs +++ b/examples/napi-compat-mode/src/function.rs @@ -1,4 +1,6 @@ -use napi::{CallContext, JsError, JsFunction, JsNull, JsObject, JsUnknown, Result}; +use napi::{ + bindgen_prelude::Function, CallContext, JsError, JsFunction, JsNull, JsObject, JsUnknown, Result, +}; #[js_function(1)] pub fn call_function(ctx: CallContext) -> Result { @@ -44,15 +46,16 @@ pub fn call_function_error(ctx: CallContext) -> Result { } #[js_function(0)] -pub fn test_create_function_from_closure(ctx: CallContext) -> Result { +pub fn test_create_function_from_closure(ctx: CallContext) -> Result> { ctx .env .create_function_from_closure("functionFromClosure", move |ctx| { - if ctx.length != 0 { - let max: u32 = ctx.get(ctx.length - 1)?; - assert_eq!(max, ctx.length as u32 - 1); + if ctx.length() != 0 { + let args = ctx.arguments::()?; + let max = args.last().unwrap(); + assert_eq!(*max, ctx.length() as u32 - 1); } - Ok(format!("arguments length: {}", ctx.length)) + Ok(format!("arguments length: {}", ctx.length())) }) } diff --git a/examples/napi-compat-mode/src/global.rs b/examples/napi-compat-mode/src/global.rs index 6dcff50d..b4443ba6 100644 --- a/examples/napi-compat-mode/src/global.rs +++ b/examples/napi-compat-mode/src/global.rs @@ -1,10 +1,10 @@ use std::convert::TryInto; -use napi::{CallContext, JsFunction, JsNumber, JsObject, JsTimeout, JsUndefined, Result}; +use napi::{CallContext, JsNumber, JsObject, JsTimeout, JsUndefined, Result}; #[js_function(2)] pub fn set_timeout(ctx: CallContext) -> Result { - let handler: JsFunction = ctx.get(0)?; + let handler = ctx.get(0)?; let timeout: JsNumber = ctx.get(1)?; ctx .env diff --git a/examples/napi-compat-mode/src/lib.rs b/examples/napi-compat-mode/src/lib.rs index 74c89192..d0d5b220 100644 --- a/examples/napi-compat-mode/src/lib.rs +++ b/examples/napi-compat-mode/src/lib.rs @@ -1,5 +1,6 @@ #![allow(unused_variables)] #![allow(clippy::uninlined_format_args)] +#![allow(deprecated)] #[macro_use] extern crate napi_derive; diff --git a/examples/napi-compat-mode/src/napi4/mod.rs b/examples/napi-compat-mode/src/napi4/mod.rs index 27b375d1..33c1840f 100644 --- a/examples/napi-compat-mode/src/napi4/mod.rs +++ b/examples/napi-compat-mode/src/napi4/mod.rs @@ -1,4 +1,4 @@ -use napi::{Env, JsObject, Property, Result}; +use napi::{bindgen_prelude::Unknown, Env, JsObject, Property, Result}; mod deferred; mod tsfn; @@ -26,7 +26,7 @@ pub fn register_js(exports: &mut JsObject, env: &Env) -> Result<()> { exports.create_named_method("testTsfnWithRef", test_tsfn_with_ref)?; exports.create_named_method("testDeferred", deferred::test_deferred)?; - let obj = env.define_class( + let obj = env.define_class::( "A", constructor, &[ diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md index d6b71e9a..3b9bd06e 100644 --- a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md @@ -251,7 +251,7 @@ Generated by [AVA](https://avajs.dev). ␊ export function acceptThreadsafeFunction(func: (err: Error | null, arg: number) => any): void␊ ␊ - export function acceptThreadsafeFunctionFatal(func: (arg: number) => any): void␊ + export function acceptThreadsafeFunctionFatal(func: (arg: number) => void): void␊ ␊ export function acceptThreadsafeFunctionTupleArgs(func: (err: Error | null, arg0: number, arg1: boolean, arg2: string) => any): void␊ ␊ @@ -310,6 +310,8 @@ Generated by [AVA](https://avajs.dev). ␊ export function bufferPassThrough(buf: Buffer): Promise␊ ␊ + export function buildThreadsafeFunctionFromFunction(callback: (arg0: number, arg1: number) => number): void␊ + ␊ export interface C {␊ baz: number␊ }␊ @@ -330,9 +332,9 @@ Generated by [AVA](https://avajs.dev). ␊ export function callFunctionWithArgAndCtx(ctx: Animal, cb: (arg: string) => void, name: string): void␊ ␊ - export function callLongThreadsafeFunction(callback: (...args: any[]) => any): void␊ + export function callLongThreadsafeFunction(tsfn: (err: Error | null, arg: number) => unknown): void␊ ␊ - export function callThreadsafeFunction(callback: (...args: any[]) => any): void␊ + export function callThreadsafeFunction(tsfn: (err: Error | null, arg: number) => unknown): void␊ ␊ export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void␊ ␊ @@ -639,11 +641,11 @@ Generated by [AVA](https://avajs.dev). ␊ export function threadsafeFunctionClosureCapture(func: (...args: any[]) => any): void␊ ␊ - export function threadsafeFunctionFatalMode(cb: (...args: any[]) => any): void␊ + export function threadsafeFunctionFatalMode(cb: (arg: boolean) => unknown): void␊ ␊ - export function threadsafeFunctionFatalModeError(cb: (...args: any[]) => any): void␊ + export function threadsafeFunctionFatalModeError(cb: (arg: boolean) => string): void␊ ␊ - export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void␊ + export function threadsafeFunctionThrowError(cb: (err: Error | null, arg: boolean) => unknown): void␊ ␊ export function throwAsyncError(): Promise␊ ␊ @@ -653,15 +655,15 @@ Generated by [AVA](https://avajs.dev). ␊ export function toJsObj(): object␊ ␊ - export function tsfnAsyncCall(func: (...args: any[]) => any): Promise␊ + export function tsfnAsyncCall(func: (arg0: number, arg1: number, arg2: number) => string): Promise␊ ␊ - export function tsfnCallWithCallback(func: (...args: any[]) => any): void␊ + export function tsfnCallWithCallback(tsfn: (err: Error | null, ) => string): void␊ ␊ - export function tsfnReturnPromise(func: (err: Error | null, arg: number) => any): Promise␊ + export function tsfnReturnPromise(func: (err: Error | null, arg: number) => Promise): Promise␊ ␊ - export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => any): Promise␊ + export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => Promise): Promise␊ ␊ - export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => any): Promise␊ + export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => Promise): Promise␊ ␊ export function tsRename(a: { foo: number }): string[]␊ ␊ diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap index ab45356b85d54e6af71ecac0bff07bbc92e13ae8..1a6b9320eb7df20568b75642ea236bf5f959571b 100644 GIT binary patch literal 4810 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 diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.md b/examples/napi/__tests__/__snapshots__/values.spec.ts.md index 8a3e6fe6..5c0cacb5 100644 --- a/examples/napi/__tests__/__snapshots__/values.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/values.spec.ts.md @@ -19,6 +19,7 @@ Generated by [AVA](https://avajs.dev). 'cross-env', 'electron', 'lodash', + 'rxjs', 'sinon', 'vite', 'vite-plugin-node-polyfills', diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap index d4241d4d7d0d6ebb4343f3ab1adc96ea29a9ee41..2b1be196c853c9f3064c4e651ba44b589ce03635 100644 GIT binary patch literal 389 zcmV;00eb#HRzV*l-3e0Vm-COav1|q^DTl_cR(uIV|SJ<@dOMQ9%czR;YZTvK2b3(aI8+F5v!9 z6f(%0=rR@^Q9say$&%idcp~1258{hB0U<7kYvPu8B%X;^;+^;;AQIw?IEMBFk}L1! z4uDT-nhNL(7z-GZ5z)k0Kr(^hj5gAl$7yc#ZwbKdxM_perBTR6#Kvi3Y$dKUzp+8D z6>BkTqgmLM3a7>u5|>=JxK3x44hq9`?%kF?7YhjseHJ#&l5Or{SxF8%9QJbW5Hn>f zuDV=xl~H-{&T`Y^ru~1tKIbj}uxhS8;50#P|Aq`3x!#o15vS*Mnw4gAr)_2({ST!x jyM;EU8GO{N@pD(sQ_crlH7~rbl-KSX>}*6NWdZ;It8}%+ literal 378 zcmV-=0fqiSRzV9W}Xk*k*&m6J73C%E>1RWi`! zF71*rT_1}G00000000A3kxfp+Fc3yPP1FC9R=@^v1fpJ{Jq8jRCvmBRYeyb8QI}n@ z;|@h)!5O#&w_wQ;U@Dj(BE7`=z9)HNAJ>_2`2!xFRnWnx5|uAhTB5TWt*mh40v?V< zA%nc_Tt}iWP7e%VR?xR39*I}to%keXAjA!^BJPNqcp_eiH{yf%BF>>Vhj`_^+yn3` zNfH4g0rZ}b(GeFA35e$~S<*B*^|;85{w)ExowaT7x-tsc=&*6w8jZwl>UTEitzs== zZMqEmO5wt|LgI$&F4xJj)jDhZ!oe%xJv>60D$|n$p8QV diff --git a/examples/napi/__tests__/tsfn-error.cjs b/examples/napi/__tests__/tsfn-error.cjs index 6007bfa1..aecafb21 100644 --- a/examples/napi/__tests__/tsfn-error.cjs +++ b/examples/napi/__tests__/tsfn-error.cjs @@ -1,5 +1,5 @@ -import('../index.cjs').then( - ({ threadsafeFunctionFatalModeError }) => { - return threadsafeFunctionFatalModeError(() => {}) - }, -) +const { threadsafeFunctionFatalModeError } = require('../index.cjs') + +threadsafeFunctionFatalModeError(() => { + return false +}) diff --git a/examples/napi/__tests__/values.spec.ts b/examples/napi/__tests__/values.spec.ts index 76e68131..d10a9244 100644 --- a/examples/napi/__tests__/values.spec.ts +++ b/examples/napi/__tests__/values.spec.ts @@ -2,6 +2,7 @@ import { exec } from 'node:child_process' import { join } from 'node:path' import { fileURLToPath } from 'node:url' +import { Subject, take } from 'rxjs' import { spy } from 'sinon' import { @@ -170,6 +171,7 @@ import { throwSyntaxError, type AliasedStruct, returnObjectOnlyToJs, + buildThreadsafeFunctionFromFunction, } from '../index.cjs' import { test } from './test.framework.js' @@ -968,7 +970,7 @@ BigIntTest('from i128 i64', (t) => { t.is(bigintFromI128(), BigInt('-100')) }) -Napi4Test('call thread safe function', (t) => { +Napi4Test('call ThreadsafeFunction', (t) => { let i = 0 let value = 0 return new Promise((resolve) => { @@ -980,14 +982,14 @@ Napi4Test('call thread safe function', (t) => { resolve() t.is( value, - Array.from({ length: 100 }, (_, i) => i + 1).reduce((a, b) => a + b), + Array.from({ length: 100 }, (_, i) => i).reduce((a, b) => a + b), ) } }) }) }) -Napi4Test('throw error from thread safe function', async (t) => { +Napi4Test('throw error from ThreadsafeFunction', async (t) => { const throwPromise = new Promise((_, reject) => { threadsafeFunctionThrowError(reject) }) @@ -995,7 +997,7 @@ Napi4Test('throw error from thread safe function', async (t) => { t.is(err?.message, 'ThrowFromNative') }) -Napi4Test('thread safe function closure capture data', (t) => { +Napi4Test('ThreadsafeFunction closure capture data', (t) => { return new Promise((resolve) => { threadsafeFunctionClosureCapture(() => { resolve() @@ -1024,7 +1026,7 @@ Napi4Test('throw error from thread safe function fatal mode', (t) => { t.is(code, 1) const stderrMsg = stderr.toString('utf8') console.info(stderrMsg) - t.true(stderrMsg.includes(`Error: Generic tsfn error`)) + t.true(stderrMsg.includes(`Error: Failed to convert JavaScript value`)) resolve() }) }) @@ -1060,8 +1062,7 @@ Napi4Test('call ThreadsafeFunction with callback', async (t) => { Napi4Test('async call ThreadsafeFunction', async (t) => { await t.notThrowsAsync(() => - tsfnAsyncCall((err, arg1, arg2, arg3) => { - t.is(err, null) + tsfnAsyncCall((arg1, arg2, arg3) => { t.is(arg1, 0) t.is(arg2, 1) t.is(arg3, 2) @@ -1163,6 +1164,20 @@ Napi4Test('object only from js', (t) => { }) }) +Napi4Test('build ThreadsafeFunction from Function', (t) => { + const subject = new Subject() + const fn = (a: number, b: number) => { + t.is(a, 1) + t.is(b, 2) + subject.next() + return a * b + } + + buildThreadsafeFunctionFromFunction(fn) + + return subject.pipe(take(3)) +}) + Napi4Test('promise in either', async (t) => { t.is(await promiseInEither(1), false) t.is(await promiseInEither(20), true) diff --git a/examples/napi/electron.cjs b/examples/napi/electron.cjs index 0d35c543..4f7c9952 100644 --- a/examples/napi/electron.cjs +++ b/examples/napi/electron.cjs @@ -85,7 +85,7 @@ async function main() { assert( value === - Array.from({ length: 100 }, (_, i) => i + 1).reduce((a, b) => a + b), + Array.from({ length: 100 }, (_, i) => i).reduce((a, b) => a + b), ) console.info(createExternalTypedArray()) } diff --git a/examples/napi/index.cjs b/examples/napi/index.cjs index eb2b6fb3..2cf9a4bd 100644 --- a/examples/napi/index.cjs +++ b/examples/napi/index.cjs @@ -398,6 +398,7 @@ module.exports.bigintFromI128 = nativeBinding.bigintFromI128 module.exports.bigintFromI64 = nativeBinding.bigintFromI64 module.exports.bigintGetU64AsString = nativeBinding.bigintGetU64AsString module.exports.bufferPassThrough = nativeBinding.bufferPassThrough +module.exports.buildThreadsafeFunctionFromFunction = nativeBinding.buildThreadsafeFunctionFromFunction module.exports.call0 = nativeBinding.call0 module.exports.call1 = nativeBinding.call1 module.exports.call2 = nativeBinding.call2 diff --git a/examples/napi/index.d.cts b/examples/napi/index.d.cts index 19f89483..b4959e23 100644 --- a/examples/napi/index.d.cts +++ b/examples/napi/index.d.cts @@ -241,7 +241,7 @@ export function acceptSlice(fixture: Uint8Array): bigint export function acceptThreadsafeFunction(func: (err: Error | null, arg: number) => any): void -export function acceptThreadsafeFunctionFatal(func: (arg: number) => any): void +export function acceptThreadsafeFunctionFatal(func: (arg: number) => void): void export function acceptThreadsafeFunctionTupleArgs(func: (err: Error | null, arg0: number, arg1: boolean, arg2: string) => any): void @@ -300,6 +300,8 @@ export function bigintGetU64AsString(bi: bigint): string export function bufferPassThrough(buf: Buffer): Promise +export function buildThreadsafeFunctionFromFunction(callback: (arg0: number, arg1: number) => number): void + export interface C { baz: number } @@ -320,9 +322,9 @@ export function callFunctionWithArg(cb: (arg0: number, arg1: number) => number, export function callFunctionWithArgAndCtx(ctx: Animal, cb: (arg: string) => void, name: string): void -export function callLongThreadsafeFunction(callback: (...args: any[]) => any): void +export function callLongThreadsafeFunction(tsfn: (err: Error | null, arg: number) => unknown): void -export function callThreadsafeFunction(callback: (...args: any[]) => any): void +export function callThreadsafeFunction(tsfn: (err: Error | null, arg: number) => unknown): void export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void @@ -629,11 +631,11 @@ export function testSerdeRoundtrip(data: any): any export function threadsafeFunctionClosureCapture(func: (...args: any[]) => any): void -export function threadsafeFunctionFatalMode(cb: (...args: any[]) => any): void +export function threadsafeFunctionFatalMode(cb: (arg: boolean) => unknown): void -export function threadsafeFunctionFatalModeError(cb: (...args: any[]) => any): void +export function threadsafeFunctionFatalModeError(cb: (arg: boolean) => string): void -export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void +export function threadsafeFunctionThrowError(cb: (err: Error | null, arg: boolean) => unknown): void export function throwAsyncError(): Promise @@ -643,15 +645,15 @@ export function throwSyntaxError(error: string, code?: string | undefined | null export function toJsObj(): object -export function tsfnAsyncCall(func: (...args: any[]) => any): Promise +export function tsfnAsyncCall(func: (arg0: number, arg1: number, arg2: number) => string): Promise -export function tsfnCallWithCallback(func: (...args: any[]) => any): void +export function tsfnCallWithCallback(tsfn: (err: Error | null, ) => string): void -export function tsfnReturnPromise(func: (err: Error | null, arg: number) => any): Promise +export function tsfnReturnPromise(func: (err: Error | null, arg: number) => Promise): Promise -export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => any): Promise +export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => Promise): Promise -export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => any): Promise +export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => Promise): Promise export function tsRename(a: { foo: number }): string[] diff --git a/examples/napi/package.json b/examples/napi/package.json index 098f9a69..acc66a10 100644 --- a/examples/napi/package.json +++ b/examples/napi/package.json @@ -24,6 +24,7 @@ "cross-env": "7.0.3", "electron": "^29.0.1", "lodash": "^4.17.21", + "rxjs": "^7.8.1", "sinon": "^17.0.1", "vite": "^5.0.12", "vite-plugin-node-polyfills": "^0.19.0", diff --git a/examples/napi/src/callback.rs b/examples/napi/src/callback.rs index 87156629..86f1d1dd 100644 --- a/examples/napi/src/callback.rs +++ b/examples/napi/src/callback.rs @@ -2,7 +2,7 @@ use std::{env, format}; use napi::{ bindgen_prelude::*, - threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, + threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, JsUnknown, }; @@ -65,7 +65,7 @@ fn callback_return_promise Result>( if ret.is_promise()? { let p = Promise::::from_unknown(ret)?; let fn_out_tsfn: ThreadsafeFunction = fn_out - .create_threadsafe_function(0, |ctx: ThreadSafeCallContext| Ok(vec![ctx.value]))?; + .create_threadsafe_function(|ctx: ThreadsafeCallContext| Ok(vec![ctx.value]))?; env .execute_tokio_future( async move { diff --git a/examples/napi/src/date.rs b/examples/napi/src/date.rs index 584e6d15..e0566d2e 100644 --- a/examples/napi/src/date.rs +++ b/examples/napi/src/date.rs @@ -15,7 +15,7 @@ fn chrono_date_to_millis(input: chrono::DateTime) -> i64 { #[napi] fn chrono_date_add_1_minute(input: chrono::DateTime) -> chrono::DateTime { - input + Duration::minutes(1) + Duration::try_minutes(1).map(|d| input + d).unwrap() } #[napi(object)] @@ -26,7 +26,7 @@ pub struct Dates { #[napi] pub fn chrono_native_date_time(date: chrono::NaiveDateTime) -> i64 { - date.timestamp_millis() + date.and_utc().timestamp_millis() } #[napi] diff --git a/examples/napi/src/enum.rs b/examples/napi/src/enum.rs index 7192662a..444f4065 100644 --- a/examples/napi/src/enum.rs +++ b/examples/napi/src/enum.rs @@ -19,6 +19,7 @@ pub enum Status { Ready, } +#[allow(clippy::enum_variant_names)] #[napi(string_enum = "lowercase")] pub enum StringEnum { VariantOne, diff --git a/examples/napi/src/function.rs b/examples/napi/src/function.rs index f456a425..737c5836 100644 --- a/examples/napi/src/function.rs +++ b/examples/napi/src/function.rs @@ -1,5 +1,8 @@ +#![allow(deprecated)] + use napi::{ bindgen_prelude::{ClassInstance, Function, FunctionRef}, + threadsafe_function::ThreadsafeFunctionCallMode, Env, JsFunction, JsObject, Result, }; @@ -74,3 +77,30 @@ pub fn reference_as_callback( ) -> Result { callback.borrow_back(&env)?.call((arg0, arg1)) } + +#[napi] +pub fn build_threadsafe_function_from_function(callback: Function<(u32, u32), u32>) -> Result<()> { + let tsfn = callback.build_threadsafe_function().build()?; + std::thread::spawn(move || { + tsfn.call((1, 2), ThreadsafeFunctionCallMode::NonBlocking); + }); + let tsfn_max_queue_size_1 = callback + .build_threadsafe_function() + .max_queue_size::<1>() + .build()?; + + std::thread::spawn(move || { + tsfn_max_queue_size_1.call((1, 2), ThreadsafeFunctionCallMode::NonBlocking); + }); + + let tsfn_weak = callback + .build_threadsafe_function() + .weak::() + .build()?; + + std::thread::spawn(move || { + tsfn_weak.call((1, 2), ThreadsafeFunctionCallMode::NonBlocking); + }); + + Ok(()) +} diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs index b5192452..4d8f0133 100644 --- a/examples/napi/src/lib.rs +++ b/examples/napi/src/lib.rs @@ -3,6 +3,7 @@ #![allow(clippy::disallowed_names)] #![allow(clippy::uninlined_format_args)] #![allow(clippy::new_without_default)] +#![allow(deprecated)] #[macro_use] extern crate napi_derive; diff --git a/examples/napi/src/threadsafe_function.rs b/examples/napi/src/threadsafe_function.rs index 0bbeb2c6..a3238358 100644 --- a/examples/napi/src/threadsafe_function.rs +++ b/examples/napi/src/threadsafe_function.rs @@ -2,14 +2,12 @@ use std::{thread, time::Duration}; use napi::{ bindgen_prelude::*, - threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode}, - JsBoolean, JsString, + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode, UnknownReturnValue}, + JsString, }; #[napi] -pub fn call_threadsafe_function(callback: JsFunction) -> Result<()> { - let tsfn: ThreadsafeFunction = - callback.create_threadsafe_function(0, |ctx| Ok(vec![ctx.value + 1]))?; +pub fn call_threadsafe_function(tsfn: ThreadsafeFunction) -> Result<()> { for n in 0..100 { let tsfn = tsfn.clone(); thread::spawn(move || { @@ -20,9 +18,9 @@ pub fn call_threadsafe_function(callback: JsFunction) -> Result<()> { } #[napi] -pub fn call_long_threadsafe_function(callback: JsFunction) -> Result<()> { - let tsfn: ThreadsafeFunction = - callback.create_threadsafe_function(0, |ctx| Ok(vec![ctx.value + 1]))?; +pub fn call_long_threadsafe_function( + tsfn: ThreadsafeFunction, +) -> Result<()> { thread::spawn(move || { for n in 0..10 { thread::sleep(Duration::from_millis(100)); @@ -33,11 +31,11 @@ pub fn call_long_threadsafe_function(callback: JsFunction) -> Result<()> { } #[napi] -pub fn threadsafe_function_throw_error(cb: JsFunction) -> Result<()> { - let tsfn: ThreadsafeFunction = - cb.create_threadsafe_function(0, |ctx| ctx.env.get_boolean(ctx.value).map(|v| vec![v]))?; +pub fn threadsafe_function_throw_error( + cb: ThreadsafeFunction, +) -> Result<()> { thread::spawn(move || { - tsfn.call( + cb.call( Err(Error::new( Status::GenericFailure, "ThrowFromNative".to_owned(), @@ -49,26 +47,23 @@ pub fn threadsafe_function_throw_error(cb: JsFunction) -> Result<()> { } #[napi] -pub fn threadsafe_function_fatal_mode(cb: JsFunction) -> Result<()> { - let tsfn: ThreadsafeFunction = - cb.create_threadsafe_function(0, |ctx| ctx.env.get_boolean(ctx.value).map(|v| vec![v]))?; +pub fn threadsafe_function_fatal_mode( + cb: ThreadsafeFunction, +) -> Result<()> { thread::spawn(move || { - tsfn.call(true, ThreadsafeFunctionCallMode::Blocking); + cb.call(true, ThreadsafeFunctionCallMode::Blocking); }); Ok(()) } #[napi] -pub fn threadsafe_function_fatal_mode_error(cb: JsFunction) -> Result<()> { - let tsfn: ThreadsafeFunction = - cb.create_threadsafe_function(0, |_ctx| { - Err::, Error>(Error::new( - Status::GenericFailure, - "Generic tsfn error".to_owned(), - )) - })?; +pub fn threadsafe_function_fatal_mode_error( + cb: ThreadsafeFunction, +) -> Result<()> { thread::spawn(move || { - tsfn.call(true, ThreadsafeFunctionCallMode::Blocking); + cb.call_with_return_value(true, ThreadsafeFunctionCallMode::Blocking, |ret, _| { + ret.map(|_| ()) + }); }); Ok(()) } @@ -77,8 +72,8 @@ pub fn threadsafe_function_fatal_mode_error(cb: JsFunction) -> Result<()> { fn threadsafe_function_closure_capture(func: JsFunction) -> napi::Result<()> { let str = "test"; let tsfn: ThreadsafeFunction<()> = func - .create_threadsafe_function(0, move |_| { - println!("{}", str); // str is NULL at this point + .create_threadsafe_function(move |_| { + println!("Captured in ThreadsafeFunction {}", str); // str is NULL at this point Ok(Vec::::new()) }) .unwrap(); @@ -89,13 +84,12 @@ fn threadsafe_function_closure_capture(func: JsFunction) -> napi::Result<()> { } #[napi] -pub fn tsfn_call_with_callback(func: JsFunction) -> napi::Result<()> { - let tsfn: ThreadsafeFunction<()> = - func.create_threadsafe_function(0, move |_| Ok(Vec::::new()))?; +pub fn tsfn_call_with_callback(tsfn: ThreadsafeFunction<(), String>) -> napi::Result<()> { tsfn.call_with_return_value( Ok(()), ThreadsafeFunctionCallMode::NonBlocking, - |value: String| { + |value: Result, _| { + let value = value.expect("Failed to retrieve value from JS"); println!("{}", value); assert_eq!(value, "ReturnFromJavaScriptRawCallback".to_owned()); Ok(()) @@ -105,12 +99,11 @@ pub fn tsfn_call_with_callback(func: JsFunction) -> napi::Result<()> { } #[napi(ts_return_type = "Promise")] -pub fn tsfn_async_call(env: Env, func: JsFunction) -> napi::Result { - let tsfn: ThreadsafeFunction<()> = - func.create_threadsafe_function(0, move |_| Ok(vec![0u32, 1u32, 2u32]))?; +pub fn tsfn_async_call(env: Env, func: Function<(u32, u32, u32), String>) -> napi::Result { + let tsfn = func.build_threadsafe_function().build()?; env.spawn_future(async move { - let msg: String = tsfn.call_async(Ok(())).await?; + let msg = tsfn.call_async((0, 1, 2)).await?; assert_eq!(msg, "ReturnFromJavaScriptRawCallback".to_owned()); Ok(()) }) @@ -124,7 +117,7 @@ pub fn accept_threadsafe_function(func: ThreadsafeFunction) { } #[napi] -pub fn accept_threadsafe_function_fatal(func: ThreadsafeFunction) { +pub fn accept_threadsafe_function_fatal(func: ThreadsafeFunction) { thread::spawn(move || { func.call(1, ThreadsafeFunctionCallMode::NonBlocking); }); @@ -141,15 +134,17 @@ pub fn accept_threadsafe_function_tuple_args(func: ThreadsafeFunction<(u32, bool } #[napi] -pub async fn tsfn_return_promise(func: ThreadsafeFunction) -> Result { - let val = func.call_async::>(Ok(1)).await?.await?; +pub async fn tsfn_return_promise(func: ThreadsafeFunction>) -> Result { + let val = func.call_async(Ok(1)).await?.await?; Ok(val + 2) } #[napi] -pub async fn tsfn_return_promise_timeout(func: ThreadsafeFunction) -> Result { +pub async fn tsfn_return_promise_timeout( + func: ThreadsafeFunction>, +) -> Result { use tokio::time::{self, Duration}; - let promise = func.call_async::>(Ok(1)).await?; + let promise = func.call_async(Ok(1)).await?; let sleep = time::sleep(Duration::from_nanos(1)); tokio::select! { _ = sleep => { @@ -162,6 +157,6 @@ pub async fn tsfn_return_promise_timeout(func: ThreadsafeFunction) -> Resul } #[napi] -pub async fn tsfn_throw_from_js(tsfn: ThreadsafeFunction) -> napi::Result { - tsfn.call_async::>(Ok(42)).await?.await +pub async fn tsfn_throw_from_js(tsfn: ThreadsafeFunction>) -> napi::Result { + tsfn.call_async(Ok(42)).await?.await } diff --git a/memory-testing/src/lib.rs b/memory-testing/src/lib.rs index ce61119a..a6f2c9e3 100644 --- a/memory-testing/src/lib.rs +++ b/memory-testing/src/lib.rs @@ -2,7 +2,7 @@ use std::thread::spawn; use napi::{ bindgen_prelude::*, - threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, + threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, }; #[macro_use] @@ -126,7 +126,7 @@ impl ChildReference { #[napi] pub fn leaking_func(env: Env, func: JsFunction) -> napi::Result<()> { let mut tsfn: ThreadsafeFunction = - func.create_threadsafe_function(0, |mut ctx: ThreadSafeCallContext| { + func.create_threadsafe_function(|mut ctx: ThreadsafeCallContext| { ctx.env.adjust_external_memory(ctx.value.len() as i64)?; ctx .env diff --git a/yarn.lock b/yarn.lock index f802d97f..150f596a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -533,6 +533,7 @@ __metadata: cross-env: "npm:7.0.3" electron: "npm:^29.0.1" lodash: "npm:^4.17.21" + rxjs: "npm:^7.8.1" sinon: "npm:^17.0.1" vite: "npm:^5.0.12" vite-plugin-node-polyfills: "npm:^0.19.0"