nj_core/
class.rs

1use std::ptr;
2
3use tracing::debug;
4use tracing::instrument;
5
6use crate::sys::napi_value;
7use crate::sys::napi_env;
8use crate::sys::napi_callback_info;
9use crate::sys::napi_ref;
10use crate::val::JsEnv;
11use crate::val::JsExports;
12use crate::val::JsCallback;
13use crate::NjError;
14use crate::IntoJs;
15use crate::PropertiesBuilder;
16
17pub struct JSObjectWrapper<T> {
18    wrapper: napi_ref,
19    inner: T,
20}
21
22impl<T> JSObjectWrapper<T> {
23    pub fn mut_inner(&mut self) -> &mut T {
24        &mut self.inner
25    }
26
27    pub fn inner(&self) -> &T {
28        &self.inner
29    }
30}
31
32impl<T> JSObjectWrapper<T>
33where
34    T: JSClass,
35{
36    /// wrap myself in the JS instance
37    /// and saved the reference
38    #[instrument(skip(self))]
39    fn wrap(self, js_env: &JsEnv, js_cb: JsCallback) -> Result<napi_value, NjError> {
40        let boxed_self = Box::new(self);
41        let raw_ptr = Box::into_raw(boxed_self); // rust no longer manages this struct
42        debug!(?raw_ptr, "box into raw");
43        let wrap = js_env.wrap(js_cb.this(), raw_ptr as *mut u8, T::js_finalize)?;
44
45        unsafe {
46            // save the wrap reference in wrapper container
47            let rust_ref: &mut Self = &mut *raw_ptr;
48            rust_ref.wrapper = wrap;
49        }
50
51        Ok(js_cb.this_owned())
52    }
53}
54
55pub trait JSClass: Sized {
56    const CLASS_NAME: &'static str;
57
58    // create rust object from argument
59    fn create_from_js(
60        js_env: &JsEnv,
61        cb: napi_callback_info,
62    ) -> Result<(Self, JsCallback), NjError>;
63
64    fn set_constructor(constructor: napi_ref);
65
66    fn get_constructor() -> napi_ref;
67
68    /// new instance
69    #[instrument]
70    fn new_instance(js_env: &JsEnv, js_args: Vec<napi_value>) -> Result<napi_value, NjError> {
71        debug!("new instance");
72        let constructor = js_env.get_reference_value(Self::get_constructor())?;
73        js_env.new_instance(constructor, js_args)
74    }
75
76    /// given instance, return my object
77    #[instrument]
78    fn unwrap_mut(js_env: &JsEnv, instance: napi_value) -> Result<&'static mut Self, NjError> {
79        Ok(js_env
80            .unwrap_mut::<JSObjectWrapper<Self>>(instance)?
81            .mut_inner())
82    }
83
84    fn unwrap(js_env: &JsEnv, instance: napi_value) -> Result<&'static Self, NjError> {
85        Ok(js_env.unwrap::<JSObjectWrapper<Self>>(instance)?.inner())
86    }
87
88    fn properties() -> PropertiesBuilder {
89        vec![].into()
90    }
91
92    /// define class and properties under exports
93    #[instrument]
94    fn js_init(js_exports: &mut JsExports) -> Result<(), NjError> {
95        let js_constructor =
96            js_exports
97                .env()
98                .define_class(Self::CLASS_NAME, Self::js_new, Self::properties())?;
99
100        debug!(?js_constructor, "class defined with constructor");
101
102        // save the constructor reference, we need this later in order to instantiate
103        let js_ref = js_exports.env().create_reference(js_constructor, 1)?;
104        debug!(?js_constructor, "created reference to constructor");
105        Self::set_constructor(js_ref);
106
107        js_exports.set_name_property(Self::CLASS_NAME, js_constructor)?;
108        Ok(())
109    }
110
111    /// call when Javascript class constructor is called
112    /// For example:  new Car(...)
113    #[instrument]
114    extern "C" fn js_new(env: napi_env, info: napi_callback_info) -> napi_value {
115        let js_env = JsEnv::new(env);
116
117        let result: Result<napi_value, NjError> = (|| {
118            debug!(clas = std::any::type_name::<Self>(), "getting new target");
119
120            let target = js_env.get_new_target(info)?;
121
122            if target.is_null() {
123                debug!("no target");
124                Err(NjError::NoPlainConstructor)
125            } else {
126                debug!(?target, "invoked as constructor");
127
128                let (rust_obj, js_cb) = Self::create_from_js(&js_env, info)?;
129                debug!(?js_cb, "created rust object");
130                let my_obj = JSObjectWrapper {
131                    inner: rust_obj,
132                    wrapper: ptr::null_mut(),
133                };
134
135                my_obj.wrap(&js_env, js_cb)
136            }
137        })();
138
139        result.into_js(&js_env)
140    }
141
142    extern "C" fn js_finalize(
143        _env: napi_env,
144        finalize_data: *mut ::std::os::raw::c_void,
145        _finalize_hint: *mut ::std::os::raw::c_void,
146    ) {
147        debug!("my object finalize");
148        unsafe {
149            let ptr: *mut JSObjectWrapper<Self> = finalize_data as *mut JSObjectWrapper<Self>;
150            let _ = Box::from_raw(ptr);
151        }
152    }
153}