use std::path::Path;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
use std::sync::mpsc::channel;
use std::sync::mpsc::Sender;
use std::sync::OnceLock;
use super::internal;
use super::NodejsWorker;
use crate::internal::NodejsMainEvent;
use crate::napi::JsObject;
use crate::napi::JsUnknown;
use crate::Env;
use crate::NodejsOptions;
static NODEJS: OnceLock<crate::Result<NodejsRef>> = OnceLock::new();
pub(crate) static NODEJS_CONTEXT_COUNT: AtomicU32 = AtomicU32::new(0);
pub type NodejsRef = Sender<NodejsMainEvent>;
pub struct Nodejs {
tx_main: NodejsRef,
}
impl Nodejs {
pub fn load_with_args<P: AsRef<Path>, Args: AsRef<str>>(
path: P,
args: &[Args],
) -> crate::Result<Nodejs> {
NODEJS_CONTEXT_COUNT.fetch_add(1, Ordering::AcqRel);
let nodejs = NODEJS.get_or_init(move || {
let _ = libnode_sys::load::cdylib(path);
let tx_main = internal::start_node_instance(args)?;
Ok(tx_main)
});
match nodejs {
Ok(nodejs) => Ok(Self {
tx_main: nodejs.clone(),
}),
Err(err) => Err(err.clone()),
}
}
pub fn load_default<P: AsRef<Path>>(path: P) -> crate::Result<Nodejs> {
Self::load_with_args(path, &[] as &[&str])
}
pub fn load(options: NodejsOptions) -> crate::Result<Nodejs> {
Self::load_with_args(options.libnode_path.clone(), &options.as_argv())
}
pub fn napi_module_register<
S: AsRef<str>,
F: 'static + Sync + Send + Fn(Env, JsObject) -> crate::Result<JsObject>,
>(
&self,
module_name: S,
register_function: F,
) -> crate::Result<()> {
internal::napi_module_register(module_name, register_function)
}
pub fn spawn_worker_thread(&self) -> crate::Result<NodejsWorker> {
self.spawn_worker_thread_with_options(&NodejsOptions::default())
}
pub fn spawn_worker_thread_with_options(
&self,
options: &NodejsOptions,
) -> crate::Result<NodejsWorker> {
NodejsWorker::start(options, self.tx_main.clone())
}
pub fn eval<Code: AsRef<str>>(
&self,
code: Code,
callback: impl 'static + Send + FnOnce(Env, JsUnknown),
) -> crate::Result<()> {
self
.tx_main
.send(NodejsMainEvent::Eval {
code: code.as_ref().to_string(),
callback: Box::new(callback),
})
.unwrap();
Ok(())
}
pub fn eval_blocking<Code: AsRef<str>>(
&self,
code: Code,
) -> crate::Result<()> {
let (tx, rx) = channel();
self
.tx_main
.send(NodejsMainEvent::Eval {
code: code.as_ref().to_string(),
callback: Box::new(move |_env, _val| {
tx.send(Ok(())).unwrap();
}),
})
.ok();
rx.recv().unwrap()
}
pub fn eval_typescript<Code: AsRef<str>>(
&self,
code: Code,
callback: impl 'static + Send + FnOnce(Env, JsUnknown),
) -> crate::Result<()> {
self
.tx_main
.send(NodejsMainEvent::EvalTypeScript {
code: code.as_ref().to_string(),
callback: Box::new(callback),
})
.unwrap();
Ok(())
}
pub fn eval_typescript_blocking<Code: AsRef<str>>(
&self,
code: Code,
) -> crate::Result<()> {
let (tx, rx) = channel();
self
.tx_main
.send(NodejsMainEvent::EvalTypeScript {
code: code.as_ref().to_string(),
callback: Box::new(move |_env, _val| {
tx.send(Ok(())).unwrap();
}),
})
.ok();
rx.recv().unwrap()
}
pub fn exec_blocking<F: 'static + Send + FnOnce(Env) -> crate::Result<()>>(
&self,
callback: F,
) -> crate::Result<()> {
let (tx, rx) = channel();
self
.tx_main
.send(NodejsMainEvent::Exec {
callback: Box::new(move |env| {
let result = callback(env);
tx.send(Ok(())).unwrap();
result
}),
})
.ok();
rx.recv().unwrap()
}
pub fn exec<F: 'static + Send + FnOnce(Env) -> crate::Result<()>>(
&self,
callback: F,
) -> crate::Result<()> {
self
.tx_main
.send(NodejsMainEvent::Exec {
callback: Box::new(callback),
})
.unwrap();
Ok(())
}
pub fn require<Specifier: AsRef<str>>(
&self,
specifier: Specifier,
) -> crate::Result<()> {
let (tx, rx) = channel();
self
.tx_main
.send(NodejsMainEvent::Require {
specifier: specifier.as_ref().to_string(),
resolve: tx,
})
.ok();
rx.recv().unwrap()
}
pub fn import<Specifier: AsRef<str>>(
&self,
specifier: Specifier,
) -> crate::Result<()> {
let (tx, rx) = channel();
self
.tx_main
.send(NodejsMainEvent::Import {
specifier: specifier.as_ref().to_string(),
resolve: tx,
})
.ok();
rx.recv().unwrap()
}
}
impl Drop for Nodejs {
fn drop(&mut self) {
let context_count = NODEJS_CONTEXT_COUNT.fetch_sub(1, Ordering::AcqRel);
if context_count == 1 {
let (tx, rx) = channel();
self
.tx_main
.send(NodejsMainEvent::StopMain { resolve: tx })
.unwrap();
rx.recv().unwrap();
}
}
}