use std::ptr;
use log::debug;
use crate::sys::napi_value;
use crate::sys::napi_env;
use crate::sys::napi_callback_info;
use crate::sys::napi_ref;
use crate::val::JsEnv;
use crate::val::JsExports;
use crate::val::JsCallback;
use crate::NjError;
use crate::IntoJs;
use crate::PropertiesBuilder;
pub struct JSObjectWrapper<T> {
wrapper: napi_ref,
inner: T,
}
impl<T> JSObjectWrapper<T> {
pub fn mut_inner(&mut self) -> &mut T {
&mut self.inner
}
pub fn inner(&self) -> &T {
&self.inner
}
}
impl<T> JSObjectWrapper<T>
where
T: JSClass,
{
fn wrap(self, js_env: &JsEnv, js_cb: JsCallback) -> Result<napi_value, NjError> {
let boxed_self = Box::new(self);
let raw_ptr = Box::into_raw(boxed_self);
let wrap = js_env.wrap(js_cb.this(), raw_ptr as *mut u8, T::js_finalize)?;
unsafe {
let rust_ref: &mut Self = &mut *raw_ptr;
rust_ref.wrapper = wrap;
}
js_env.delete_reference(wrap)?;
Ok(js_cb.this_owned())
}
}
pub trait JSClass: Sized {
const CLASS_NAME: &'static str;
fn create_from_js(
js_env: &JsEnv,
cb: napi_callback_info,
) -> Result<(Self, JsCallback), NjError>;
fn set_constructor(constructor: napi_ref);
fn get_constructor() -> napi_ref;
fn new_instance(js_env: &JsEnv, js_args: Vec<napi_value>) -> Result<napi_value, NjError> {
debug!("new instance with args: {:#?}", js_args);
let constructor = js_env.get_reference_value(Self::get_constructor())?;
js_env.new_instance(constructor, js_args)
}
fn unwrap_mut(js_env: &JsEnv, instance: napi_value) -> Result<&'static mut Self, NjError> {
Ok(js_env
.unwrap_mut::<JSObjectWrapper<Self>>(instance)?
.mut_inner())
}
fn unwrap(js_env: &JsEnv, instance: napi_value) -> Result<&'static Self, NjError> {
Ok(js_env.unwrap::<JSObjectWrapper<Self>>(instance)?.inner())
}
fn properties() -> PropertiesBuilder {
vec![].into()
}
fn js_init(js_exports: &mut JsExports) -> Result<(), NjError> {
let js_constructor =
js_exports
.env()
.define_class(Self::CLASS_NAME, Self::js_new, Self::properties())?;
let js_ref = js_exports.env().create_reference(js_constructor, 1)?;
Self::set_constructor(js_ref);
js_exports.set_name_property(Self::CLASS_NAME, js_constructor)?;
Ok(())
}
extern "C" fn js_new(env: napi_env, info: napi_callback_info) -> napi_value {
let js_env = JsEnv::new(env);
let result: Result<napi_value, NjError> = (|| {
debug!(
"Class constructor called: {:#?}",
std::any::type_name::<Self>()
);
let target = js_env.get_new_target(info)?;
if target.is_null() {
Err(NjError::NoPlainConstructor)
} else {
debug!("invoked as constructor");
let (rust_obj, js_cb) = Self::create_from_js(&js_env, info)?;
let my_obj = JSObjectWrapper {
inner: rust_obj,
wrapper: ptr::null_mut(),
};
my_obj.wrap(&js_env, js_cb)
}
})();
result.into_js(&js_env)
}
extern "C" fn js_finalize(
_env: napi_env,
finalize_data: *mut ::std::os::raw::c_void,
_finalize_hint: *mut ::std::os::raw::c_void,
) {
debug!("my object finalize");
unsafe {
let ptr: *mut JSObjectWrapper<Self> = finalize_data as *mut JSObjectWrapper<Self>;
let _ = Box::from_raw(ptr);
}
}
}