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 <andywangsy@gmail.com>
This commit is contained in:
parent
693f0ac269
commit
4719caa643
43 changed files with 815 additions and 664 deletions
|
@ -37,7 +37,7 @@ fn bench_threadsafe_function(ctx: CallContext) -> Result<JsUndefined> {
|
|||
let tsfn = ctx.env.create_threadsafe_function(
|
||||
&callback,
|
||||
0,
|
||||
|mut ctx: ThreadSafeCallContext<(usize, Ref<JsBufferValue>)>| {
|
||||
|mut ctx: ThreadsafeCallContext<(usize, Ref<JsBufferValue>)>| {
|
||||
ctx
|
||||
.env
|
||||
.create_uint32(ctx.value.0 as u32)
|
||||
|
|
|
@ -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::<bindgen_prelude::Unknown>(
|
||||
"TestClass",
|
||||
test_class_constructor,
|
||||
&[
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#![allow(clippy::uninlined_format_args)]
|
||||
#![allow(deprecated)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
||||
|
|
|
@ -212,6 +212,7 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, (&'static str, bool, bool)>> = 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::<Vec<_>>()
|
||||
|
@ -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
|
||||
|
|
|
@ -114,11 +114,9 @@ unsafe extern "C" fn complete<T: Task>(
|
|||
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<T: Task>(
|
|||
}
|
||||
};
|
||||
}
|
||||
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) };
|
||||
|
|
|
@ -32,7 +32,7 @@ impl ValidateNapiValue for DateTime<Utc> {
|
|||
impl ToNapiValue for NaiveDateTime {
|
||||
unsafe fn to_napi_value(env: sys::napi_env, val: NaiveDateTime) -> Result<sys::napi_value> {
|
||||
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<Utc> {
|
|||
|
||||
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::<Utc>::from_naive_utc_and_offset(naive, Utc))
|
||||
Ok(DateTime::<Utc>::from_naive_utc_and_offset(
|
||||
naive.naive_utc(),
|
||||
Utc,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Vec<sys::napi_value>>;
|
||||
}
|
||||
|
||||
impl<T: ToNapiValue> JsValuesTupleIntoVec for T {
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
fn into_vec(self, env: sys::napi_env) -> Result<Vec<sys::napi_value>> {
|
||||
Ok(vec![unsafe {
|
||||
<T as ToNapiValue>::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<Self>
|
||||
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<Vec<sys::napi_value>> {
|
||||
#[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<Self> {
|
||||
#[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<Args>,
|
||||
|
@ -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<Self> {
|
||||
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<String> {
|
||||
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<FunctionRef<Args, Return>> {
|
||||
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<Unknown> {
|
||||
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<Args, Return> {
|
||||
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<FunctionRef<Args, Return>> {
|
||||
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<Args>,
|
||||
_return: std::marker::PhantomData<Return>,
|
||||
}
|
||||
|
||||
impl<
|
||||
Args: JsValuesTupleIntoVec,
|
||||
Return: FromNapiValue,
|
||||
const Weak: bool,
|
||||
const MaxQueueSize: usize,
|
||||
> ThreadsafeFunctionBuilder<Args, Return, Weak, MaxQueueSize>
|
||||
{
|
||||
pub fn weak<const NewWeak: bool>(
|
||||
self,
|
||||
) -> ThreadsafeFunctionBuilder<Args, Return, NewWeak, MaxQueueSize> {
|
||||
ThreadsafeFunctionBuilder {
|
||||
env: self.env,
|
||||
value: self.value,
|
||||
_args: std::marker::PhantomData,
|
||||
_return: std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_queue_size<const NewMaxQueueSize: usize>(
|
||||
self,
|
||||
) -> ThreadsafeFunctionBuilder<Args, Return, Weak, NewMaxQueueSize> {
|
||||
ThreadsafeFunctionBuilder {
|
||||
env: self.env,
|
||||
value: self.value,
|
||||
_args: std::marker::PhantomData,
|
||||
_return: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<ThreadsafeFunction<Args, Return, false, Weak, MaxQueueSize>> {
|
||||
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<Args: JsValuesTupleIntoVec, Return: FromNapiValue> {
|
||||
pub struct FunctionRef<Args: JsValuesTupleIntoVec, Return> {
|
||||
pub(crate) inner: sys::napi_ref,
|
||||
pub(crate) env: sys::napi_env,
|
||||
_args: std::marker::PhantomData<Args>,
|
||||
_return: std::marker::PhantomData<Return>,
|
||||
}
|
||||
|
||||
unsafe impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> Sync for FunctionRef<Args, Return> {}
|
||||
unsafe impl<Args: JsValuesTupleIntoVec, Return> Sync for FunctionRef<Args, Return> {}
|
||||
|
||||
impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> FunctionRef<Args, Return> {
|
||||
impl<Args: JsValuesTupleIntoVec, Return> FunctionRef<Args, Return> {
|
||||
pub fn borrow_back<'scope>(&self, env: &'scope Env) -> Result<Function<'scope, Args, Return>> {
|
||||
let mut value = ptr::null_mut();
|
||||
check_status!(
|
||||
|
@ -158,14 +316,14 @@ impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> FunctionRef<Args, Return
|
|||
}
|
||||
}
|
||||
|
||||
impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> Drop for FunctionRef<Args, Return> {
|
||||
impl<Args: JsValuesTupleIntoVec, Return> Drop for FunctionRef<Args, Return> {
|
||||
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<Args: JsValuesTupleIntoVec, Return: FromNapiValue> TypeName for FunctionRef<Args, Return> {
|
||||
impl<Args: JsValuesTupleIntoVec, Return> TypeName for FunctionRef<Args, Return> {
|
||||
fn type_name() -> &'static str {
|
||||
"Function"
|
||||
}
|
||||
|
@ -175,9 +333,7 @@ impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> TypeName for FunctionRef
|
|||
}
|
||||
}
|
||||
|
||||
impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> FromNapiValue
|
||||
for FunctionRef<Args, Return>
|
||||
{
|
||||
impl<Args: JsValuesTupleIntoVec, Return> FromNapiValue for FunctionRef<Args, Return> {
|
||||
unsafe fn from_napi_value(env: sys::napi_env, value: sys::napi_value) -> Result<Self> {
|
||||
let mut reference = ptr::null_mut();
|
||||
check_status!(
|
||||
|
@ -198,6 +354,55 @@ impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> 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<T: FromNapiValue>(&self) -> Result<T> {
|
||||
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<Args: TupleFromSliceValues>(&self) -> Result<Args> {
|
||||
unsafe { Args::from_slice_values(self.env.0, self.args) }
|
||||
}
|
||||
|
||||
/// Get the arguments Vec from the JavaScript function call.
|
||||
pub fn arguments<T: FromNapiValue>(&self) -> Result<Vec<T>> {
|
||||
self
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| unsafe { <T as FromNapiValue>::from_napi_value(self.env.0, *arg) })
|
||||
.collect::<Result<Vec<T>>>()
|
||||
}
|
||||
|
||||
/// Get the `this` from the JavaScript function call.
|
||||
pub fn this<This: FromNapiValue>(&self) -> Result<This> {
|
||||
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<Return> {
|
||||
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<Return: FromNapiValue>(&self) -> Result<Return> {
|
||||
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() })?;
|
||||
|
||||
|
|
|
@ -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::<Unknown, Unknown>("onabort", on_abort)?,
|
||||
)?;
|
||||
Ok(AbortSignal {
|
||||
raw_work: async_work_inner,
|
||||
raw_deferred: raw_promise,
|
||||
|
|
|
@ -35,7 +35,7 @@ pub unsafe extern "C" fn raw_finalize_unchecked<T: ObjectFinalize>(
|
|||
_finalize_hint: *mut c_void,
|
||||
) {
|
||||
let data: Box<T> = 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;
|
||||
|
|
|
@ -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<u8> = vec![];
|
||||
|
@ -58,7 +60,7 @@ impl From<sys::napi_env> 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<JsString> {
|
||||
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<JsFunction> {
|
||||
pub fn create_function<Args: JsValuesTupleIntoVec, Return>(
|
||||
&self,
|
||||
name: &str,
|
||||
callback: Callback,
|
||||
) -> Result<Function<Args, Return>> {
|
||||
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::<Args, Return>::from_napi_value(self.0, raw_result) }
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi5")]
|
||||
pub fn create_function_from_closure<R, F>(&self, name: &str, callback: F) -> Result<JsFunction>
|
||||
pub fn create_function_from_closure<Args: JsValuesTupleIntoVec, Return, F>(
|
||||
&self,
|
||||
name: &str,
|
||||
callback: F,
|
||||
) -> Result<Function<Args, Return>>
|
||||
where
|
||||
F: 'static + Fn(crate::CallContext<'_>) -> Result<R>,
|
||||
R: ToNapiValue,
|
||||
Return: ToNapiValue,
|
||||
F: 'static + Fn(FunctionCallContext) -> Result<Return>,
|
||||
{
|
||||
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::<R, F>),
|
||||
Some(trampoline::<Return, F>),
|
||||
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<Args: JsValuesTupleIntoVec>(
|
||||
&self,
|
||||
name: &str,
|
||||
constructor_cb: Callback,
|
||||
properties: &[Property],
|
||||
) -> Result<JsFunction> {
|
||||
) -> Result<Function<Args, Unknown>> {
|
||||
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<T>) -> Result<Vec<V>>,
|
||||
R: 'static + Send + FnMut(ThreadsafeCallContext<T>) -> Result<Vec<V>>,
|
||||
>(
|
||||
&self,
|
||||
func: &JsFunction,
|
||||
max_queue_size: usize,
|
||||
_max_queue_size: usize,
|
||||
callback: R,
|
||||
) -> Result<ThreadsafeFunction<T>> {
|
||||
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<T, Hint, F>(
|
|||
{
|
||||
let (value, callback) = unsafe { *Box::from_raw(finalize_data as *mut (TaggedObject<T>, 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<Hint, Finalize>(
|
|||
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<Arg, F>(
|
|||
|
||||
#[cfg(feature = "napi5")]
|
||||
pub(crate) unsafe extern "C" fn trampoline<
|
||||
R: ToNapiValue,
|
||||
F: Fn(crate::CallContext) -> Result<R>,
|
||||
Return: ToNapiValue,
|
||||
F: Fn(FunctionCallContext) -> Result<Return>,
|
||||
>(
|
||||
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 { <R as ToNapiValue>::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 { <Return as ToNapiValue>::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)
|
||||
})
|
||||
|
|
|
@ -20,7 +20,7 @@ pub type Result<T, S = Status> = std::result::Result<T, Error<S>>;
|
|||
/// 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<S: AsRef<str> = Status> {
|
||||
pub status: S,
|
||||
pub reason: String,
|
||||
|
@ -28,6 +28,17 @@ pub struct Error<S: AsRef<str> = Status> {
|
|||
pub(crate) maybe_raw: sys::napi_ref,
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>> std::fmt::Debug for Error<S> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Error {{ status: {:?}, reason: {:?} }}",
|
||||
self.status.as_ref(),
|
||||
self.reason
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>> ToNapiValue for Error<S> {
|
||||
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
|
||||
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!(
|
||||
|
|
|
@ -23,7 +23,7 @@ struct DeferredTrace(sys::napi_ref);
|
|||
#[cfg(feature = "deferred_trace")]
|
||||
impl DeferredTrace {
|
||||
fn new(raw_env: sys::napi_env) -> Result<Self> {
|
||||
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<sys::napi_value> {
|
||||
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<Data: ToNapiValue, Resolver: FnOnce(Env) ->
|
|||
let deferred_data: Box<DeferredData<Data, Resolver>> = 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| {
|
||||
|
|
|
@ -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<T, V, F, ES>(
|
||||
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<ThreadsafeFunction<T, ES>>
|
||||
) -> Result<ThreadsafeFunction<T, Return, ES, Weak, MaxQueueSize>>
|
||||
where
|
||||
T: 'static,
|
||||
Return: crate::bindgen_runtime::FromNapiValue,
|
||||
V: ToNapiValue,
|
||||
F: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>,
|
||||
ES: crate::threadsafe_function::ErrorStrategy::T,
|
||||
F: 'static + Send + FnMut(ThreadsafeCallContext<T>) -> Result<Vec<V>>,
|
||||
{
|
||||
ThreadsafeFunction::create(self.0.env, self.0.value, max_queue_size, callback)
|
||||
ThreadsafeFunction::create(self.0.env, self.0.value, callback)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<V: NapiRaw>(&self, value: V) -> Result<std::string::String> {
|
||||
let func: JsFunction = self.get_named_property_unchecked("stringify")?;
|
||||
let result = func
|
||||
.call(None, &[value])
|
||||
.map(|ret| unsafe { ret.cast::<JsString>() })?;
|
||||
result.into_utf8()?.as_str().map(|s| s.to_owned())
|
||||
let func: Function<V, std::string::String> = self.get_named_property_unchecked("stringify")?;
|
||||
func.call(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl JsGlobal {
|
||||
pub fn set_interval(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> {
|
||||
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<JsTimeout> {
|
||||
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<JsUndefined> {
|
||||
let func: JsFunction = self.get_named_property_unchecked("clearInterval")?;
|
||||
func
|
||||
.call(None, &[timer.into_unknown()])
|
||||
.and_then(|ret| ret.try_into())
|
||||
let func: Function<JsTimeout, JsUndefined> =
|
||||
self.get_named_property_unchecked("clearInterval")?;
|
||||
func.call(timer)
|
||||
}
|
||||
|
||||
pub fn set_timeout(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> {
|
||||
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<JsTimeout> {
|
||||
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<JsUndefined> {
|
||||
let func: JsFunction = self.get_named_property_unchecked("clearTimeout")?;
|
||||
func
|
||||
.call(None, &[timer.into_unknown()])
|
||||
.and_then(|ret| ret.try_into())
|
||||
let func: Function<JsTimeout, JsUndefined> =
|
||||
self.get_named_property_unchecked("clearTimeout")?;
|
||||
func.call(timer)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(deprecated)]
|
||||
|
||||
use std::convert::TryFrom;
|
||||
#[cfg(feature = "napi5")]
|
||||
use std::ffi::c_void;
|
||||
|
|
|
@ -76,7 +76,7 @@ unsafe extern "C" fn finalize_callback<T, Hint, F>(
|
|||
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) };
|
||||
|
|
|
@ -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))?))?;
|
||||
|
|
|
@ -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<Self::JsValue>;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
/// Into this method if `compute` return `Err`
|
||||
fn reject(&mut self, _env: Env, err: Error) -> Result<Self::JsValue> {
|
||||
fn reject(&mut self, env: Env, err: Error) -> Result<Self::JsValue> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T> = ThreadsafeCallContext<T>;
|
||||
|
||||
/// ThreadSafeFunction Context object
|
||||
/// the `value` is the value passed to `call` method
|
||||
pub struct ThreadSafeCallContext<T: 'static> {
|
||||
pub struct ThreadsafeCallContext<T: 'static> {
|
||||
pub env: Env,
|
||||
pub value: T,
|
||||
}
|
||||
|
@ -36,68 +41,6 @@ impl From<ThreadsafeFunctionCallMode> 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<Args…>` 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::<Vec<_>>(),
|
||||
/// )
|
||||
/// },
|
||||
/// 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<sys::napi_threadsafe_function__>,
|
||||
aborted: RwLock<bool>,
|
||||
|
@ -178,10 +121,10 @@ enum ThreadsafeFunctionCallVariant {
|
|||
WithCallback,
|
||||
}
|
||||
|
||||
struct ThreadsafeFunctionCallJsBackData<T> {
|
||||
struct ThreadsafeFunctionCallJsBackData<T, Return = Unknown> {
|
||||
data: T,
|
||||
call_variant: ThreadsafeFunctionCallVariant,
|
||||
callback: Box<dyn FnOnce(Result<JsUnknown>) -> Result<()>>,
|
||||
callback: Box<dyn FnOnce(Result<Return>, Env) -> Result<()>>,
|
||||
}
|
||||
|
||||
/// Communicate with the addon's main thread by invoking a JavaScript function from other threads.
|
||||
|
@ -190,58 +133,70 @@ struct ThreadsafeFunctionCallJsBackData<T> {
|
|||
/// 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<JsUndefined> {
|
||||
/// let func = ctx.get::<JsFunction>(0)?;
|
||||
///
|
||||
/// let tsfn =
|
||||
/// ctx
|
||||
/// .env
|
||||
/// .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext<Vec<u32>>| {
|
||||
/// ctx.value
|
||||
/// .iter()
|
||||
/// .map(|v| ctx.env.create_uint32(*v))
|
||||
/// .collect::<Result<Vec<JsNumber>>>()
|
||||
/// })?;
|
||||
///
|
||||
/// #[napi]
|
||||
/// pub fn call_threadsafe_function(callback: ThreadsafeFunction<(u32, bool, String), ()>) {
|
||||
/// let tsfn_cloned = tsfn.clone();
|
||||
///
|
||||
/// thread::spawn(move || {
|
||||
/// let output: Vec<u32> = 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<u32> = 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<T: 'static, ES: ErrorStrategy::T = ErrorStrategy::CalleeHandled> {
|
||||
pub struct ThreadsafeFunction<
|
||||
T: 'static,
|
||||
Return: FromNapiValue + 'static = Unknown,
|
||||
const CalleeHandled: bool = true,
|
||||
const Weak: bool = false,
|
||||
const MaxQueueSize: usize = 0,
|
||||
> {
|
||||
handle: Arc<ThreadsafeFunctionHandle>,
|
||||
_phantom: PhantomData<(T, ES)>,
|
||||
_phantom: PhantomData<(T, Return)>,
|
||||
}
|
||||
|
||||
unsafe impl<T: 'static, ES: ErrorStrategy::T> Send for ThreadsafeFunction<T, ES> {}
|
||||
unsafe impl<T: 'static, ES: ErrorStrategy::T> Sync for ThreadsafeFunction<T, ES> {}
|
||||
unsafe impl<
|
||||
T: 'static,
|
||||
Return: FromNapiValue,
|
||||
const CalleeHandled: bool,
|
||||
const Weak: bool,
|
||||
const MaxQueueSize: usize,
|
||||
> Send for ThreadsafeFunction<T, Return, { CalleeHandled }, { Weak }, { MaxQueueSize }>
|
||||
{
|
||||
}
|
||||
|
||||
impl<T: 'static, ES: ErrorStrategy::T> Clone for ThreadsafeFunction<T, ES> {
|
||||
unsafe impl<
|
||||
T: 'static,
|
||||
Return: FromNapiValue,
|
||||
const CalleeHandled: bool,
|
||||
const Weak: bool,
|
||||
const MaxQueueSize: usize,
|
||||
> Sync for ThreadsafeFunction<T, Return, { CalleeHandled }, { Weak }, { MaxQueueSize }>
|
||||
{
|
||||
}
|
||||
|
||||
impl<
|
||||
T: 'static,
|
||||
Return: FromNapiValue,
|
||||
const CalleeHandled: bool,
|
||||
const Weak: bool,
|
||||
const MaxQueueSize: usize,
|
||||
> Clone for ThreadsafeFunction<T, Return, { CalleeHandled }, { Weak }, { MaxQueueSize }>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
self.handle.with_read_aborted(|aborted| {
|
||||
if aborted {
|
||||
|
@ -256,86 +211,67 @@ impl<T: 'static, ES: ErrorStrategy::T> Clone for ThreadsafeFunction<T, ES> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: ToNapiValue> JsValuesTupleIntoVec for T {
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
fn into_vec(self, env: sys::napi_env) -> Result<Vec<sys::napi_value>> {
|
||||
Ok(vec![unsafe {
|
||||
<T as ToNapiValue>::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<Vec<sys::napi_value>> {
|
||||
#[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<T: JsValuesTupleIntoVec + 'static, ES: ErrorStrategy::T> FromNapiValue
|
||||
for ThreadsafeFunction<T, ES>
|
||||
impl<
|
||||
T: JsValuesTupleIntoVec + 'static,
|
||||
Return: FromNapiValue,
|
||||
const CalleeHandled: bool,
|
||||
const Weak: bool,
|
||||
const MaxQueueSize: usize,
|
||||
> FromNapiValue for ThreadsafeFunction<T, Return, { CalleeHandled }, { Weak }, { MaxQueueSize }>
|
||||
{
|
||||
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||
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<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
|
||||
/// 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<T, Return, { CalleeHandled }, { Weak }, { MaxQueueSize }>
|
||||
{
|
||||
// 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<T>) -> Result<Vec<V>>,
|
||||
R: 'static + Send + FnMut(ThreadsafeCallContext<T>) -> Result<Vec<V>>,
|
||||
>(
|
||||
env: sys::napi_env,
|
||||
func: sys::napi_value,
|
||||
max_queue_size: usize,
|
||||
callback: R,
|
||||
) -> Result<Self> {
|
||||
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<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
|
|||
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::<T, V, R>),
|
||||
callback_ptr.cast(),
|
||||
Some(call_js_cb::<T, V, R, ES>),
|
||||
Some(call_js_cb::<T, Return, V, R, CalleeHandled>),
|
||||
&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<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
|
|||
})
|
||||
}
|
||||
|
||||
#[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<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
|
|||
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<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
|
||||
impl<T: 'static, Return: FromNapiValue + 'static, const Weak: bool, const MaxQueueSize: usize>
|
||||
ThreadsafeFunction<T, Return, true, { Weak }, { MaxQueueSize }>
|
||||
{
|
||||
/// 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<T>, mode: ThreadsafeFunctionCallMode) -> Status {
|
||||
|
@ -432,7 +390,7 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
|
|||
ThreadsafeFunctionCallJsBackData {
|
||||
data,
|
||||
call_variant: ThreadsafeFunctionCallVariant::Direct,
|
||||
callback: Box::new(|_d: Result<JsUnknown>| Ok(())),
|
||||
callback: Box::new(|_d: Result<Return>, _| Ok(())),
|
||||
}
|
||||
})))
|
||||
.cast(),
|
||||
|
@ -443,7 +401,8 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn call_with_return_value<D: FromNapiValue, F: 'static + FnOnce(D) -> Result<()>>(
|
||||
/// Call the ThreadsafeFunction, and handle the return value with a callback
|
||||
pub fn call_with_return_value<F: 'static + FnOnce(Result<Return>, Env) -> Result<()>>(
|
||||
&self,
|
||||
value: Result<T>,
|
||||
mode: ThreadsafeFunctionCallMode,
|
||||
|
@ -461,9 +420,7 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
|
|||
ThreadsafeFunctionCallJsBackData {
|
||||
data,
|
||||
call_variant: ThreadsafeFunctionCallVariant::WithCallback,
|
||||
callback: Box::new(move |d: Result<JsUnknown>| {
|
||||
d.and_then(|d| D::from_napi_value(d.0.env, d.0.value).and_then(cb))
|
||||
}),
|
||||
callback: Box::new(move |d: Result<Return>, env: Env| cb(d, env)),
|
||||
}
|
||||
})))
|
||||
.cast(),
|
||||
|
@ -475,8 +432,9 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
|
|||
}
|
||||
|
||||
#[cfg(feature = "tokio_rt")]
|
||||
pub async fn call_async<D: 'static + FromNapiValue>(&self, value: Result<T>) -> Result<D> {
|
||||
let (sender, receiver) = tokio::sync::oneshot::channel::<Result<D>>();
|
||||
/// Call the ThreadsafeFunction, and handle the return value with in `async` way
|
||||
pub async fn call_async(&self, value: Result<T>) -> Result<Return> {
|
||||
let (sender, receiver) = tokio::sync::oneshot::channel::<Result<Return>>();
|
||||
|
||||
self.handle.with_read_aborted(|aborted| {
|
||||
if aborted {
|
||||
|
@ -491,9 +449,9 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
|
|||
ThreadsafeFunctionCallJsBackData {
|
||||
data,
|
||||
call_variant: ThreadsafeFunctionCallVariant::WithCallback,
|
||||
callback: Box::new(move |d: Result<JsUnknown>| {
|
||||
callback: Box::new(move |d: Result<Return>, _| {
|
||||
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<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
|
||||
impl<T: 'static, Return: FromNapiValue + 'static, const Weak: bool, const MaxQueueSize: usize>
|
||||
ThreadsafeFunction<T, Return, false, { Weak }, { MaxQueueSize }>
|
||||
{
|
||||
/// 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<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
|
|||
Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData {
|
||||
data: value,
|
||||
call_variant: ThreadsafeFunctionCallVariant::Direct,
|
||||
callback: Box::new(|_d: Result<JsUnknown>| Ok(())),
|
||||
callback: Box::new(|_d: Result<Return>, _: Env| Ok(())),
|
||||
}))
|
||||
.cast(),
|
||||
mode.into(),
|
||||
|
@ -544,7 +504,8 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn call_with_return_value<D: FromNapiValue, F: 'static + FnOnce(D) -> Result<()>>(
|
||||
/// Call the ThreadsafeFunction, and handle the return value with a callback
|
||||
pub fn call_with_return_value<F: 'static + FnOnce(Result<Return>, Env) -> Result<()>>(
|
||||
&self,
|
||||
value: T,
|
||||
mode: ThreadsafeFunctionCallMode,
|
||||
|
@ -561,9 +522,7 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
|
|||
Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData {
|
||||
data: value,
|
||||
call_variant: ThreadsafeFunctionCallVariant::WithCallback,
|
||||
callback: Box::new(move |d: Result<JsUnknown>| {
|
||||
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<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
|
|||
}
|
||||
|
||||
#[cfg(feature = "tokio_rt")]
|
||||
pub async fn call_async<D: 'static + FromNapiValue>(&self, value: T) -> Result<D> {
|
||||
let (sender, receiver) = tokio::sync::oneshot::channel::<D>();
|
||||
/// Call the ThreadsafeFunction, and handle the return value with in `async` way
|
||||
pub async fn call_async(&self, value: T) -> Result<Return> {
|
||||
let (sender, receiver) = tokio::sync::oneshot::channel::<Return>();
|
||||
|
||||
self.handle.with_read_aborted(|aborted| {
|
||||
if aborted {
|
||||
|
@ -588,15 +548,13 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
|
|||
Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData {
|
||||
data: value,
|
||||
call_variant: ThreadsafeFunctionCallVariant::WithCallback,
|
||||
callback: Box::new(move |d: Result<JsUnknown>| {
|
||||
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<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
unsafe extern "C" fn thread_finalize_cb<T: 'static, V: ToNapiValue, R>(
|
||||
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<T>) -> Result<Vec<V>>,
|
||||
R: 'static + Send + FnMut(ThreadsafeCallContext<T>) -> Result<Vec<V>>,
|
||||
{
|
||||
let handle_option =
|
||||
unsafe { Weak::from_raw(finalize_data.cast::<ThreadsafeFunctionHandle>()).upgrade() };
|
||||
let handle_option: Option<Arc<ThreadsafeFunctionHandle>> =
|
||||
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<T: 'static, V: ToNapiValue, R>(
|
|||
drop(unsafe { Box::<R>::from_raw(finalize_hint.cast()) });
|
||||
}
|
||||
|
||||
unsafe extern "C" fn call_js_cb<T: 'static, V: ToNapiValue, R, ES>(
|
||||
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<T>) -> Result<Vec<V>>,
|
||||
ES: ErrorStrategy::T,
|
||||
R: 'static + Send + FnMut(ThreadsafeCallContext<T>) -> Result<Vec<V>>,
|
||||
{
|
||||
// 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::<Result<ThreadsafeFunctionCallJsBackData<T>>>::from_raw(data.cast())
|
||||
}
|
||||
ErrorStrategy::Fatal::VALUE => Ok(*Box::<ThreadsafeFunctionCallJsBackData<T>>::from_raw(
|
||||
data.cast(),
|
||||
)),
|
||||
if CalleeHandled {
|
||||
*Box::<Result<ThreadsafeFunctionCallJsBackData<T, Return>>>::from_raw(data.cast())
|
||||
} else {
|
||||
Ok(*Box::<ThreadsafeFunctionCallJsBackData<T, Return>>::from_raw(data.cast()))
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -665,8 +624,8 @@ unsafe extern "C" fn call_js_cb<T: 'static, V: ToNapiValue, R, ES>(
|
|||
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<T: 'static, V: ToNapiValue, R, ES>(
|
|||
let values = values
|
||||
.into_iter()
|
||||
.map(|v| unsafe { ToNapiValue::to_napi_value(raw_env, v) });
|
||||
let args: Result<Vec<sys::napi_value>> = if ES::VALUE == ErrorStrategy::CalleeHandled::VALUE {
|
||||
let args: Result<Vec<sys::napi_value>> = 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<T: 'static, V: ToNapiValue, R, ES>(
|
|||
&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<T: 'static, V: ToNapiValue, R, ES>(
|
|||
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<T: 'static, V: ToNapiValue, R, ES>(
|
|||
}
|
||||
}
|
||||
|
||||
/// 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!(
|
||||
"<Param: ", ::core::stringify!($EnumName), "::T>"
|
||||
)]
|
||||
/// ```
|
||||
}
|
||||
#[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 {
|
||||
|
|
|
@ -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}`,
|
||||
)
|
||||
|
|
|
@ -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<JsFunction> {
|
||||
fn create_test_class(ctx: CallContext) -> Result<Function<Unknown, Unknown>> {
|
||||
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<JsUndefined> {
|
|||
}
|
||||
|
||||
#[js_function(1)]
|
||||
fn new_test_class(ctx: CallContext) -> Result<JsObject> {
|
||||
fn new_test_class(ctx: CallContext) -> Result<Unknown> {
|
||||
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<JsObject> {
|
|||
.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<()> {
|
||||
|
|
|
@ -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<JsNull> {
|
||||
|
@ -44,15 +46,16 @@ pub fn call_function_error(ctx: CallContext) -> Result<JsUnknown> {
|
|||
}
|
||||
|
||||
#[js_function(0)]
|
||||
pub fn test_create_function_from_closure(ctx: CallContext) -> Result<JsFunction> {
|
||||
pub fn test_create_function_from_closure(ctx: CallContext) -> Result<Function<u32, String>> {
|
||||
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::<u32>()?;
|
||||
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()))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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<JsTimeout> {
|
||||
let handler: JsFunction = ctx.get(0)?;
|
||||
let handler = ctx.get(0)?;
|
||||
let timeout: JsNumber = ctx.get(1)?;
|
||||
ctx
|
||||
.env
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#![allow(unused_variables)]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
#![allow(deprecated)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
||||
|
|
|
@ -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::<Unknown>(
|
||||
"A",
|
||||
constructor,
|
||||
&[
|
||||
|
|
|
@ -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<Buffer>␊
|
||||
␊
|
||||
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<void>␊
|
||||
␊
|
||||
|
@ -653,15 +655,15 @@ Generated by [AVA](https://avajs.dev).
|
|||
␊
|
||||
export function toJsObj(): object␊
|
||||
␊
|
||||
export function tsfnAsyncCall(func: (...args: any[]) => any): Promise<void>␊
|
||||
export function tsfnAsyncCall(func: (arg0: number, arg1: number, arg2: number) => string): Promise<void>␊
|
||||
␊
|
||||
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<number>␊
|
||||
export function tsfnReturnPromise(func: (err: Error | null, arg: number) => Promise<number>): Promise<number>␊
|
||||
␊
|
||||
export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => any): Promise<number>␊
|
||||
export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => Promise<number>): Promise<number>␊
|
||||
␊
|
||||
export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => any): Promise<number>␊
|
||||
export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => Promise<number>): Promise<number>␊
|
||||
␊
|
||||
export function tsRename(a: { foo: number }): string[]␊
|
||||
␊
|
||||
|
|
Binary file not shown.
|
@ -19,6 +19,7 @@ Generated by [AVA](https://avajs.dev).
|
|||
'cross-env',
|
||||
'electron',
|
||||
'lodash',
|
||||
'rxjs',
|
||||
'sinon',
|
||||
'vite',
|
||||
'vite-plugin-node-polyfills',
|
||||
|
|
Binary file not shown.
|
@ -1,5 +1,5 @@
|
|||
import('../index.cjs').then(
|
||||
({ threadsafeFunctionFatalModeError }) => {
|
||||
return threadsafeFunctionFatalModeError(() => {})
|
||||
},
|
||||
)
|
||||
const { threadsafeFunctionFatalModeError } = require('../index.cjs')
|
||||
|
||||
threadsafeFunctionFatalModeError(() => {
|
||||
return false
|
||||
})
|
||||
|
|
|
@ -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<void>()
|
||||
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)
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<Buffer>
|
||||
|
||||
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<void>
|
||||
|
||||
|
@ -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<void>
|
||||
export function tsfnAsyncCall(func: (arg0: number, arg1: number, arg2: number) => string): Promise<void>
|
||||
|
||||
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<number>
|
||||
export function tsfnReturnPromise(func: (err: Error | null, arg: number) => Promise<number>): Promise<number>
|
||||
|
||||
export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => any): Promise<number>
|
||||
export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => Promise<number>): Promise<number>
|
||||
|
||||
export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => any): Promise<number>
|
||||
export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => Promise<number>): Promise<number>
|
||||
|
||||
export function tsRename(a: { foo: number }): string[]
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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<T: Fn() -> Result<JsUnknown>>(
|
|||
if ret.is_promise()? {
|
||||
let p = Promise::<String>::from_unknown(ret)?;
|
||||
let fn_out_tsfn: ThreadsafeFunction<String> = fn_out
|
||||
.create_threadsafe_function(0, |ctx: ThreadSafeCallContext<String>| Ok(vec![ctx.value]))?;
|
||||
.create_threadsafe_function(|ctx: ThreadsafeCallContext<String>| Ok(vec![ctx.value]))?;
|
||||
env
|
||||
.execute_tokio_future(
|
||||
async move {
|
||||
|
|
|
@ -15,7 +15,7 @@ fn chrono_date_to_millis(input: chrono::DateTime<Utc>) -> i64 {
|
|||
|
||||
#[napi]
|
||||
fn chrono_date_add_1_minute(input: chrono::DateTime<Utc>) -> chrono::DateTime<Utc> {
|
||||
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]
|
||||
|
|
|
@ -19,6 +19,7 @@ pub enum Status {
|
|||
Ready,
|
||||
}
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[napi(string_enum = "lowercase")]
|
||||
pub enum StringEnum {
|
||||
VariantOne,
|
||||
|
|
|
@ -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<u32> {
|
||||
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::<true>()
|
||||
.build()?;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
tsfn_weak.call((1, 2), ThreadsafeFunctionCallMode::NonBlocking);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<u32, ErrorStrategy::CalleeHandled> =
|
||||
callback.create_threadsafe_function(0, |ctx| Ok(vec![ctx.value + 1]))?;
|
||||
pub fn call_threadsafe_function(tsfn: ThreadsafeFunction<u32, UnknownReturnValue>) -> 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<u32, ErrorStrategy::CalleeHandled> =
|
||||
callback.create_threadsafe_function(0, |ctx| Ok(vec![ctx.value + 1]))?;
|
||||
pub fn call_long_threadsafe_function(
|
||||
tsfn: ThreadsafeFunction<u32, UnknownReturnValue>,
|
||||
) -> 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<bool, ErrorStrategy::CalleeHandled> =
|
||||
cb.create_threadsafe_function(0, |ctx| ctx.env.get_boolean(ctx.value).map(|v| vec![v]))?;
|
||||
pub fn threadsafe_function_throw_error(
|
||||
cb: ThreadsafeFunction<bool, UnknownReturnValue>,
|
||||
) -> 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<bool, ErrorStrategy::Fatal> =
|
||||
cb.create_threadsafe_function(0, |ctx| ctx.env.get_boolean(ctx.value).map(|v| vec![v]))?;
|
||||
pub fn threadsafe_function_fatal_mode(
|
||||
cb: ThreadsafeFunction<bool, UnknownReturnValue, false>,
|
||||
) -> 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<bool, ErrorStrategy::Fatal> =
|
||||
cb.create_threadsafe_function(0, |_ctx| {
|
||||
Err::<Vec<JsBoolean>, Error>(Error::new(
|
||||
Status::GenericFailure,
|
||||
"Generic tsfn error".to_owned(),
|
||||
))
|
||||
})?;
|
||||
pub fn threadsafe_function_fatal_mode_error(
|
||||
cb: ThreadsafeFunction<bool, String, false>,
|
||||
) -> 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::<JsString>::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::<JsString>::new()))?;
|
||||
pub fn tsfn_call_with_callback(tsfn: ThreadsafeFunction<(), String>) -> napi::Result<()> {
|
||||
tsfn.call_with_return_value(
|
||||
Ok(()),
|
||||
ThreadsafeFunctionCallMode::NonBlocking,
|
||||
|value: String| {
|
||||
|value: Result<String>, _| {
|
||||
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<void>")]
|
||||
pub fn tsfn_async_call(env: Env, func: JsFunction) -> napi::Result<Object> {
|
||||
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<Object> {
|
||||
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<u32>) {
|
|||
}
|
||||
|
||||
#[napi]
|
||||
pub fn accept_threadsafe_function_fatal(func: ThreadsafeFunction<u32, ErrorStrategy::Fatal>) {
|
||||
pub fn accept_threadsafe_function_fatal(func: ThreadsafeFunction<u32, (), false>) {
|
||||
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<u32>) -> Result<u32> {
|
||||
let val = func.call_async::<Promise<u32>>(Ok(1)).await?.await?;
|
||||
pub async fn tsfn_return_promise(func: ThreadsafeFunction<u32, Promise<u32>>) -> Result<u32> {
|
||||
let val = func.call_async(Ok(1)).await?.await?;
|
||||
Ok(val + 2)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn tsfn_return_promise_timeout(func: ThreadsafeFunction<u32>) -> Result<u32> {
|
||||
pub async fn tsfn_return_promise_timeout(
|
||||
func: ThreadsafeFunction<u32, Promise<u32>>,
|
||||
) -> Result<u32> {
|
||||
use tokio::time::{self, Duration};
|
||||
let promise = func.call_async::<Promise<u32>>(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<u32>) -> Resul
|
|||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn tsfn_throw_from_js(tsfn: ThreadsafeFunction<u32>) -> napi::Result<u32> {
|
||||
tsfn.call_async::<Promise<u32>>(Ok(42)).await?.await
|
||||
pub async fn tsfn_throw_from_js(tsfn: ThreadsafeFunction<u32, Promise<u32>>) -> napi::Result<u32> {
|
||||
tsfn.call_async(Ok(42)).await?.await
|
||||
}
|
||||
|
|
|
@ -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<String> =
|
||||
func.create_threadsafe_function(0, |mut ctx: ThreadSafeCallContext<String>| {
|
||||
func.create_threadsafe_function(|mut ctx: ThreadsafeCallContext<String>| {
|
||||
ctx.env.adjust_external_memory(ctx.value.len() as i64)?;
|
||||
ctx
|
||||
.env
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue