From 5c3d1b21449605d106e7b7fcea9a77dc1efa9085 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Mon, 21 Mar 2022 16:36:06 +0800 Subject: [PATCH] feat(napi): experimental class reference API --- crates/backend/src/codegen/struct.rs | 13 +- crates/napi/Cargo.toml | 2 +- .../napi/src/bindgen_runtime/callback_info.rs | 26 ++- crates/napi/src/bindgen_runtime/js_values.rs | 2 + .../bindgen_runtime/js_values/value_ref.rs | 163 ++++++++++++++++++ crates/napi/src/bindgen_runtime/mod.rs | 32 ++-- examples/napi/__test__/typegen.spec.ts.md | 7 + examples/napi/__test__/typegen.spec.ts.snap | Bin 2851 -> 2883 bytes examples/napi/__test__/values.spec.ts | 6 + examples/napi/index.d.ts | 7 + examples/napi/src/lib.rs | 1 + examples/napi/src/reference.rs | 58 +++++++ 12 files changed, 286 insertions(+), 31 deletions(-) create mode 100644 crates/napi/src/bindgen_runtime/js_values/value_ref.rs create mode 100644 examples/napi/src/reference.rs 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 993f8591bcb89e94e96ecd2d0f9aad44505ebb8a..e36dd29b9d2454d119704227b363900a8dcf8616 100644 GIT binary patch literal 2883 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 literal 2851 zcmV+;3*7WURzV{h<8U$3)AHE6x@YSEb z!hgT}<5yq*{_LA44~dc?k5wn&k`6;g9zI!vO{O;=Ih(jkGbJ8bo&gz7Nxyr%|LEjY z1F)M!q>A_UA#oNBS!y9h zS?nt=Vz8h3UZjEaS{>3&Q#ySiRm$V>E6w5!Q<6v#u^e4GOk5KEEOICq*jE)mnME29 z-pe`IR-=GQ97r%BQWx5i6$rH79vpU8PGjFosG5Mmv=V@GoQjA`_F_95jp}5gh;*2R ziVv=ZN7Jzt#KCRSXUNCkBHbYHXeVE^$_Y|52Zi#KYO|W(rm+F zojrTnm8V*FZ^#`K;0uq2lGZ~u()p9UGSRA}IiyEUrwx1V( zaW!G7iSwg$R^`I@MH*|+!q|FOld{MK&K#8n3AP%T!oyIAkkPopzBC3Tb#02PDz_$$ zpDyJGPe6B((mvx?tcMl4u$&>@DTy6O4X`ItW1gk-4m0)HBr#Os#3|GuaD*%(S2WDP zO=yZuO{~DZ1l0MhO=-M_jG!!$^-8(dxNmtJXsaUCZJJ){icQFl7(>9XxAQk|!P_NN z2PpjJeUB=5zhU1Gs8rJ&GxIMobCieTu-lCifJ1(HPx6Dv;5{~GL&zML1XKm7SppFo zkP+kyIHH(c7m8)lsQb8Hon9uDz@}jrSIFlmW>!!r*gB=*<0NIczlDoEk)`nb6rWG| z4Lm->$6Y?2ppwsV(dU}Z-!wAA)VQ%;=XSt$AojO5J=U2OW^wF*?R!##nN6mJ3HJv~q56d5#L}}JN%9N|vGD2wb zLit*Ifs1fV@497#@{_LIJz7lD{&~=xnWyC#1DGQM!!S=eXOO#|>zx(&>Bh@qE^7!2 zER)1M?A(g{nLjlmVcGD4Wi`u%9`NWM80Bf+-ybZEqMrM30-i46%gU2Q{f^4XAx*3) zPZ$h(!3*1Ott>$wDk<7kw;{`odS;6-;uIBm1w>nnJ1Z-gPZ?fP88}uLK`fCx{G}84 z4K06xp+`!Mp`RA$rsN5WDfkSGIk%9-MN!x83O?r-6||e135+8lr$t*;#9l)jKoWwZ zPEeCp(^1z=l@`eimiKj$h$ct6>-NAlubrA*Xra$x9t6$JEKHkBTX*mlmYTnN+`pvAK}oamcS45>B(Ud@OR8S9NY z(B-DkcMZm*2eR)Nwd^aqVQ7xirYYSwp#Lr!O#pfPO=vu3raMMYJc6+} zvcXr#J-0R|fRgoeBzK5P3Qgy;oHAOjYO;?*okV$)Olo=>dgur9G4RE^g66n|f9N_!PDd%$owEOn90U{l{ z|D3EDCh~UVq|4S394DTIY^5aCAe9JIlJ`abEYaE+*xA z2DiBiD<;(`UcQ_NkPu)J53i^79Y?EEd%uCLJ>{|VB+NHHEAO0OCctB5@hT1{UK&U`a(e6XhOYj~ ztSOVK3avpo!|IOx|E0GFz8xNlfPG{Razp{*G@4U>0uXx$41b(q&0BVZ{TmD4TyYPr(>s+YZxMwWbY zRT^s398)RLiL&uz&UEu1WT0%Uk%#1i?tHM}5b1~f7F?I54`%lRr0Gu?$BS!lb7*i` z7)YW78A6H4h9URcNJik@+BMLJtT-k-N&plqC2`0j++WV|cHDDzTNi8Y9hClr*Nu5Z z!{Rw}qh?!IwBqC4CA{otkfIivLW@Pv;bYL84X?y(&(ze`93e`e38k8mxg_DZ{1!xcY0I;_ny!tZ2{LcBZkMHI1EnSErA z4F!wDM^66xpMU>T@1uCv6G0 zfnt<5Z$%T6^FR(dR2~1nt4lHHI6LMWfa5uJr)$DHKfC6`ZqEQ#e5cRjk93fwDr8Nq zd`}l5GR4dE7{Q6s@PCH*{?~6a;Tp5+6Pip=Nr$j;w?ir~dm=PpP%ywTel{D;9`b?g zN{N4a@RFE^Iis1Ew>1X?lsz6e&00=s;V9tpv@B~lHZjzHYI01~q%N!szMUpr|1ZCu z=cO$uPfwK7XY)%{9c>{LjY5?TkTpuEisF#iRx#+wQZBqtLbx^HeKTN-laf~fm$|4sjJ zw?A-FLCy8+)=$r#Hr>6NCe4Vf@2wKm@G;m~f>+W8 zljN%kKi#|H`+$$3_;{`ecMZ1NbZzG { @@ -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() + } +}