diff --git a/crates/backend/src/codegen/struct.rs b/crates/backend/src/codegen/struct.rs index dcabd542..6775725d 100644 --- a/crates/backend/src/codegen/struct.rs +++ b/crates/backend/src/codegen/struct.rs @@ -238,18 +238,21 @@ impl NapiStruct { "Failed to construct class `{}`", #js_name_str )?; + let wrapped_value = Box::into_raw(Box::new(val)) as *mut std::ffi::c_void; + let mut object_ref = std::ptr::null_mut(); napi::check_status!( napi::sys::napi_wrap( env, result, - Box::into_raw(Box::new(val)) as *mut std::ffi::c_void, + wrapped_value, Some(napi::bindgen_prelude::raw_finalize_unchecked::<#name>), std::ptr::null_mut(), - std::ptr::null_mut(), + &mut object_ref, ), "Failed to wrap native object of class `{}`", #js_name_str )?; + napi::bindgen_prelude::Reference::<#name>::add_ref(std::any::TypeId::of::<#name>(), (wrapped_value, env, object_ref)); napi::bindgen_prelude::___CALL_FROM_FACTORY.with(|inner| inner.store(false, std::sync::atomic::Ordering::Relaxed)); Ok(result) } else { @@ -259,6 +262,12 @@ impl NapiStruct { } } } + + impl #name { + pub fn create_reference(&self) -> napi::bindgen_prelude::Result> { + napi::bindgen_prelude::Reference::<#name>::from_typeid(std::any::TypeId::of::<#name>()) + } + } } } diff --git a/crates/napi/Cargo.toml b/crates/napi/Cargo.toml index 614da154..138b1a65 100644 --- a/crates/napi/Cargo.toml +++ b/crates/napi/Cargo.toml @@ -73,7 +73,7 @@ optional = true version = "1" [target.'cfg(windows)'.dependencies] -windows = { version = "0.33", features = [ +windows = { version = "0.34", features = [ "Win32_System_WindowsProgramming", "Win32_System_LibraryLoader", "Win32_Foundation", diff --git a/crates/napi/src/bindgen_runtime/callback_info.rs b/crates/napi/src/bindgen_runtime/callback_info.rs index eb79236a..f59276f6 100644 --- a/crates/napi/src/bindgen_runtime/callback_info.rs +++ b/crates/napi/src/bindgen_runtime/callback_info.rs @@ -64,29 +64,34 @@ impl CallbackInfo { self.this } - pub fn construct(&self, js_name: &str, obj: T) -> Result { + pub fn construct(&self, js_name: &str, obj: T) -> Result { let obj = Box::new(obj); let this = self.this(); - + let value_ref = Box::into_raw(obj) as *mut c_void; + let mut object_ref = ptr::null_mut(); unsafe { check_status!( sys::napi_wrap( self.env, this, - Box::into_raw(obj) as *mut std::ffi::c_void, + value_ref, Some(raw_finalize_unchecked::), ptr::null_mut(), - &mut std::ptr::null_mut() + &mut object_ref ), "Failed to initialize class `{}`", js_name, )?; }; + Reference::::add_ref( + std::any::TypeId::of::(), + (value_ref, self.env, object_ref), + ); Ok(this) } - pub fn factory(&self, js_name: &str, obj: T) -> Result { + pub fn factory(&self, js_name: &str, obj: T) -> Result { let obj = Box::new(obj); let this = self.this(); let mut instance = ptr::null_mut(); @@ -102,18 +107,25 @@ impl CallbackInfo { return Ok(ptr::null_mut()); } + let mut object_ref = ptr::null_mut(); + let value_ref = Box::into_raw(obj) as *mut c_void; check_status!( sys::napi_wrap( self.env, instance, - Box::into_raw(obj) as *mut std::ffi::c_void, + value_ref, Some(raw_finalize_unchecked::), ptr::null_mut(), - &mut std::ptr::null_mut() + &mut object_ref ), "Failed to initialize class `{}`", js_name, )?; + + Reference::::add_ref( + std::any::TypeId::of::(), + (value_ref, self.env, object_ref), + ); }; Ok(instance) diff --git a/crates/napi/src/bindgen_runtime/js_values.rs b/crates/napi/src/bindgen_runtime/js_values.rs index 579e12bf..bae2cff2 100644 --- a/crates/napi/src/bindgen_runtime/js_values.rs +++ b/crates/napi/src/bindgen_runtime/js_values.rs @@ -24,6 +24,7 @@ mod serde; mod string; mod symbol; mod task; +mod value_ref; #[cfg(feature = "napi5")] pub use crate::JsDate as Date; @@ -43,6 +44,7 @@ pub use promise::*; pub use string::*; pub use symbol::*; pub use task::*; +pub use value_ref::*; #[cfg(feature = "latin1")] pub use string::latin1_string::*; diff --git a/crates/napi/src/bindgen_runtime/js_values/value_ref.rs b/crates/napi/src/bindgen_runtime/js_values/value_ref.rs new file mode 100644 index 00000000..b57dc565 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/value_ref.rs @@ -0,0 +1,163 @@ +use std::any::TypeId; +use std::cell::RefCell; +use std::collections::HashMap; +use std::ffi::c_void; +use std::ops::{Deref, DerefMut}; + +use crate::{check_status, Error, Result, Status}; + +type RefInformation = (*mut c_void, crate::sys::napi_env, crate::sys::napi_ref); + +thread_local! { + static REFERENCE_MAP: RefCell> = Default::default(); +} + +/// ### Experimental feature +/// +/// Create a `reference` from `Class` instance. +/// Unref the `Reference` when the `Reference` is dropped. +pub struct Reference { + raw: *mut T, + napi_ref: crate::sys::napi_ref, + env: crate::sys::napi_env, +} + +unsafe impl Send for Reference {} +unsafe impl Sync for Reference {} + +impl Drop for Reference { + fn drop(&mut self) { + let status = unsafe { crate::sys::napi_reference_unref(self.env, self.napi_ref, &mut 0) }; + debug_assert!( + status == crate::sys::Status::napi_ok, + "Reference unref failed" + ); + } +} + +impl Reference { + #[doc(hidden)] + pub fn add_ref(t: TypeId, value: RefInformation) { + REFERENCE_MAP.with(|map| { + map.borrow_mut().insert(t, value); + }); + } + + #[doc(hidden)] + pub fn from_typeid(t: TypeId) -> Result { + if let Some((wrapped_value, env, napi_ref)) = + REFERENCE_MAP.with(|map| map.borrow().get(&t).cloned()) + { + check_status!( + unsafe { crate::sys::napi_reference_ref(env, napi_ref, &mut 0) }, + "Failed to ref napi reference" + )?; + Ok(Self { + raw: wrapped_value as *mut T, + env, + napi_ref, + }) + } else { + Err(Error::new( + Status::InvalidArg, + format!("Class for Type {:?} not found", t), + )) + } + } +} + +impl Reference { + pub fn share_with Result>( + self, + f: F, + ) -> Result> { + let s = f(Box::leak(unsafe { Box::from_raw(self.raw) }))?; + Ok(SharedReference { + raw: Box::into_raw(Box::new(s)), + owner: self, + }) + } +} + +impl Deref for Reference { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { Box::leak(Box::from_raw(self.raw)) } + } +} + +impl DerefMut for Reference { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { Box::leak(Box::from_raw(self.raw)) } + } +} + +impl Clone for Reference { + fn clone(&self) -> Self { + let mut ref_count = 0; + let status = unsafe { crate::sys::napi_reference_ref(self.env, self.napi_ref, &mut ref_count) }; + debug_assert!( + status == crate::sys::Status::napi_ok, + "Reference ref failed" + ); + Self { + raw: self.raw, + napi_ref: self.napi_ref, + env: self.env, + } + } +} + +/// ### Experimental feature +/// +/// Create a `SharedReference` from an existed `Reference`. +pub struct SharedReference { + raw: *mut S, + owner: Reference, +} + +unsafe impl Send for SharedReference {} +unsafe impl Sync for SharedReference {} + +impl SharedReference { + pub fn share_with Result>( + self, + f: F, + ) -> Result> { + let s = f(Box::leak(unsafe { Box::from_raw(self.raw) }))?; + Ok(SharedReference { + raw: Box::into_raw(Box::new(s)), + owner: self.owner, + }) + } +} + +impl Deref for SharedReference { + type Target = S; + + fn deref(&self) -> &Self::Target { + unsafe { Box::leak(Box::from_raw(self.raw)) } + } +} + +impl DerefMut for SharedReference { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { Box::leak(Box::from_raw(self.raw)) } + } +} + +impl Clone for SharedReference { + fn clone(&self) -> Self { + let status = + unsafe { crate::sys::napi_reference_ref(self.owner.env, self.owner.napi_ref, &mut 0) }; + debug_assert!( + status == crate::sys::Status::napi_ok, + "Reference ref failed" + ); + Self { + raw: self.raw, + owner: self.owner.clone(), + } + } +} diff --git a/crates/napi/src/bindgen_runtime/mod.rs b/crates/napi/src/bindgen_runtime/mod.rs index 69297782..8d4526a5 100644 --- a/crates/napi/src/bindgen_runtime/mod.rs +++ b/crates/napi/src/bindgen_runtime/mod.rs @@ -1,8 +1,5 @@ -mod callback_info; -mod env; -mod error; -mod js_values; -mod module_register; +use std::ffi::c_void; +use std::mem; pub use callback_info::*; pub use ctor::ctor; @@ -11,29 +8,22 @@ pub use js_values::*; pub use module_register::*; use super::sys; -use std::{ffi::c_void, mem}; + +mod callback_info; +mod env; +mod error; +mod js_values; +mod module_register; /// # Safety /// /// called when node wrapper objects destroyed pub unsafe extern "C" fn raw_finalize_unchecked( - env: sys::napi_env, + _env: sys::napi_env, finalize_data: *mut c_void, - finalize_hint: *mut c_void, + _finalize_hint: *mut c_void, ) { - let obj = finalize_data as *mut T; - unsafe { Box::from_raw(obj) }; - if !finalize_hint.is_null() { - let size_hint = unsafe { *Box::from_raw(finalize_hint as *mut Option) }; - if let Some(changed) = size_hint { - let mut adjusted = 0i64; - let status = unsafe { sys::napi_adjust_external_memory(env, -changed, &mut adjusted) }; - debug_assert!( - status == sys::Status::napi_ok, - "Calling napi_adjust_external_memory failed" - ); - }; - } + unsafe { Box::from_raw(finalize_data as *mut T) }; } /// # Safety diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index abc7f874..bd02cfe7 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -260,6 +260,13 @@ Generated by [AVA](https://avajs.dev). export class JsClassForEither {␊ constructor()␊ }␊ + export class JsRepo {␊ + constructor(dir: string)␊ + remote(): JsRemote␊ + }␊ + export class JsRemote {␊ + name(): string␊ + }␊ export namespace xxh3 {␊ export const ALIGNMENT: number␊ export function xxh3_64(input: Buffer): bigint␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 993f8591..e36dd29b 100644 Binary files a/examples/napi/__test__/typegen.spec.ts.snap and b/examples/napi/__test__/typegen.spec.ts.snap differ diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index 9f32fce0..a687385a 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -87,6 +87,7 @@ import { derefUint8Array, chronoDateAdd1Minute, bufferPassThrough, + JsRepo, } from '../' test('export const', (t) => { @@ -189,6 +190,11 @@ test('class Factory return Result', (t) => { t.is(c.method(), 'not empty') }) +test('should be able to create object reference and shared reference', (t) => { + const repo = new JsRepo('.') + t.is(repo.remote().name(), 'origin') +}) + test('callback', (t) => { getCwd((cwd) => { t.is(cwd, process.cwd()) diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index 2365a292..eb825895 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -250,6 +250,13 @@ export class ClassWithFactory { export class JsClassForEither { constructor() } +export class JsRepo { + constructor(dir: string) + remote(): JsRemote +} +export class JsRemote { + name(): string +} export namespace xxh3 { export const ALIGNMENT: number export function xxh3_64(input: Buffer): bigint diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs index f6cbd464..611ce6c8 100644 --- a/examples/napi/src/lib.rs +++ b/examples/napi/src/lib.rs @@ -32,6 +32,7 @@ mod nullable; mod number; mod object; mod promise; +mod reference; mod serde; mod string; mod symbol; diff --git a/examples/napi/src/reference.rs b/examples/napi/src/reference.rs new file mode 100644 index 00000000..c33693b1 --- /dev/null +++ b/examples/napi/src/reference.rs @@ -0,0 +1,58 @@ +use napi::bindgen_prelude::*; + +pub struct Repository { + dir: String, +} + +impl Repository { + fn remote(&self) -> Remote { + Remote { inner: self } + } +} + +pub struct Remote<'repo> { + inner: &'repo Repository, +} + +impl<'repo> Remote<'repo> { + fn name(&self) -> String { + "origin".to_owned() + } +} + +#[napi] +pub struct JsRepo { + inner: Repository, +} + +#[napi] +impl JsRepo { + #[napi(constructor)] + pub fn new(dir: String) -> Self { + JsRepo { + inner: Repository { dir }, + } + } + + #[napi] + pub fn remote(&self) -> Result { + Ok(JsRemote { + inner: self + .create_reference()? + .share_with(|repo| Ok(repo.inner.remote()))?, + }) + } +} + +#[napi] +pub struct JsRemote { + inner: SharedReference>, +} + +#[napi] +impl JsRemote { + #[napi] + pub fn name(&self) -> String { + self.inner.name() + } +}