nodex-api 0.2.3

rust binding to node_api.h
Documentation
use crate::prelude::*;
use std::marker::PhantomData;

#[derive(Copy, Clone, Debug)]
pub struct NapiThreadsafeFunction<Data, const N: usize>(
    NapiEnv,
    napi_threadsafe_function,
    PhantomData<Data>,
);

unsafe impl<Data, const N: usize> Send for NapiThreadsafeFunction<Data, N> {}
unsafe impl<Data, const N: usize> Sync for NapiThreadsafeFunction<Data, N> {}

impl<Data, const N: usize> NapiThreadsafeFunction<Data, N> {
    pub(crate) fn from_raw(env: NapiEnv, tsfn: napi_threadsafe_function) -> Self {
        NapiThreadsafeFunction(env, tsfn, PhantomData)
    }

    pub fn env(&self) -> NapiEnv {
        self.0
    }

    pub fn raw(&self) -> napi_threadsafe_function {
        self.1
    }

    #[allow(clippy::type_complexity)]
    /// Create a napi_threadsafe_function
    ///
    /// R: the returned value of function.
    /// N: the maximum size of the queue, 0 for no limit.
    pub fn new<R: NapiValueT>(
        env: NapiEnv,
        name: impl AsRef<str>,
        func: Function<R>,
        finalizer: impl FnOnce(NapiEnv) -> NapiResult<()>,
        callback: impl FnMut(Function<R>, Data) -> NapiResult<()>,
    ) -> NapiResult<NapiThreadsafeFunction<Data, N>>
    where
        R: NapiValueT,
    {
        unsafe extern "C" fn finalizer_trampoline(
            env: NapiEnv,
            finalizer: DataPointer,
            _: DataPointer,
        ) {
            let finalizer: Box<Box<dyn FnOnce(NapiEnv) -> NapiResult<()>>> =
                Box::from_raw(finalizer as _);

            if let Err(err) = finalizer(env) {
                log::error!("NapiThreadsafeFunction::finalizer(): {}", err);
            }
        }

        unsafe extern "C" fn call_js_trampoline<R: NapiValueT, Data>(
            env: NapiEnv,
            cb: napi_value,
            context: DataPointer,
            data: DataPointer,
        ) {
            let context: &mut Box<dyn FnMut(Function<R>, Data) -> NapiResult<()>> =
                std::mem::transmute(&mut *(context as *mut _));
            let data: Box<Data> = Box::from_raw(data as _);

            if let Err(e) = context(Function::<R>::from_raw(env, cb), *data) {
                log::error!("NapiThreadsafeFunction::call_js_trampoline: {}", e);
            }
        }

        let context: Box<Box<dyn FnMut(Function<R>, Data) -> NapiResult<()>>> =
            Box::new(Box::new(callback));
        // NB: leak here
        let context = Box::into_raw(context);
        let finalizer: Box<Box<dyn FnOnce(NapiEnv) -> NapiResult<()>>> =
            Box::new(Box::new(move |env| -> NapiResult<()> {
                unsafe {
                    Box::from_raw(context);
                }
                finalizer(env)
            }));

        let tsfn = napi_call!(
            =napi_create_threadsafe_function,
            env,
            func.raw(),
            std::ptr::null_mut(),
            env.string(name.as_ref())?.raw(),
            N,
            1,
            Box::into_raw(finalizer) as _,
            Some(finalizer_trampoline),
            context as _,
            Some(call_js_trampoline::<R, Data>),
        );

        Ok(NapiThreadsafeFunction(env, tsfn, PhantomData))
    }

    // pub fn context<C>(&self) -> NapiResult<&mut C> {
    //     let context = napi_call!(=napi_get_threadsafe_function_context, self.raw());
    //     unsafe { Ok(std::mem::transmute(context)) }
    // }

    /// This API should not be called with napi_tsfn_blocking from a JavaScript thread, because,
    /// if the queue is full, it may cause the JavaScript thread to deadlock.
    ///
    /// This API will return napi_closing if napi_release_threadsafe_function() was called with
    /// abort set to napi_tsfn_abort from any thread. The value is only added to the queue if
    /// the API returns napi_ok.
    ///
    /// This API may be called from any thread which makes use of func.
    pub fn call(&self, data: Data, mode: NapiThreadsafeFunctionCallMode) -> NapiResult<()> {
        napi_call!(
            napi_call_threadsafe_function,
            self.raw(),
            Box::into_raw(Box::new(data)) as _,
            mode,
        )
    }

    #[inline]
    pub fn blocking(&self, data: Data) -> NapiResult<()> {
        self.call(data, NapiThreadsafeFunctionCallMode::Blocking)
    }

    #[inline]
    pub fn non_blocking(&self, data: Data) -> NapiResult<()> {
        self.call(data, NapiThreadsafeFunctionCallMode::Nonblocking)
    }

    /// A thread should call this API before passing func to any other thread-safe function APIs
    /// to indicate that it will be making use of func. This prevents func from being destroyed
    /// when all other threads have stopped making use of it.
    ///
    /// This API may be called from any thread which will start making use of func.
    pub fn acquire(&self) -> NapiResult<()> {
        napi_call!(napi_acquire_threadsafe_function, self.raw())
    }

    /// A thread should call this API when it stops making use of func. Passing func to any
    /// thread-safe APIs after having called this API has undefined results, as func may have
    /// been destroyed.
    ///
    /// This API may be called from any thread which will stop making use of func.
    pub fn release(self) -> NapiResult<()> {
        napi_call!(
            napi_release_threadsafe_function,
            self.raw(),
            NapiTsfnReleaseMode::Release
        )
    }

    /// A thread should call this API when it stops making use of func. Passing func to any
    /// thread-safe APIs after having called this API has undefined results, as func may have
    /// been destroyed.
    ///
    /// This API may be called from any thread which will stop making use of func.
    pub fn abort(self) -> NapiResult<()> {
        napi_call!(
            napi_release_threadsafe_function,
            self.raw(),
            NapiTsfnReleaseMode::Abort
        )
    }

    /// This API is used to indicate that the event loop running on the main thread should not
    /// exit until func has been destroyed. Similar to uv_ref it is also idempotent.
    ///
    /// Neither does napi_unref_threadsafe_function mark the thread-safe functions as able to be
    /// destroyed nor does napi_ref_threadsafe_function prevent it from being destroyed.
    /// napi_acquire_threadsafe_function and napi_release_threadsafe_function are available for
    /// that purpose.
    ///
    /// This API may only be called from the main thread.
    pub fn refer(&self) -> NapiResult<()> {
        napi_call!(napi_ref_threadsafe_function, self.env(), self.raw())
    }

    /// This API is used to indicate that the event loop running on the main thread may exit
    /// before func is destroyed. Similar to uv_unref it is also idempotent.
    ///
    /// This API may only be called from the main thread.
    pub fn unref(&self) -> NapiResult<()> {
        napi_call!(napi_unref_threadsafe_function, self.env(), self.raw())
    }
}

pub type NapiTsfn<Data> = NapiThreadsafeFunction<Data, 0>;