feat(napi): experimental class reference API

This commit is contained in:
LongYinan 2022-03-21 16:36:06 +08:00
parent 5994ef1b95
commit 5c3d1b2144
12 changed files with 286 additions and 31 deletions

View file

@ -238,18 +238,21 @@ impl NapiStruct {
"Failed to construct class `{}`", "Failed to construct class `{}`",
#js_name_str #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::check_status!(
napi::sys::napi_wrap( napi::sys::napi_wrap(
env, env,
result, result,
Box::into_raw(Box::new(val)) as *mut std::ffi::c_void, wrapped_value,
Some(napi::bindgen_prelude::raw_finalize_unchecked::<#name>), Some(napi::bindgen_prelude::raw_finalize_unchecked::<#name>),
std::ptr::null_mut(), std::ptr::null_mut(),
std::ptr::null_mut(), &mut object_ref,
), ),
"Failed to wrap native object of class `{}`", "Failed to wrap native object of class `{}`",
#js_name_str #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)); napi::bindgen_prelude::___CALL_FROM_FACTORY.with(|inner| inner.store(false, std::sync::atomic::Ordering::Relaxed));
Ok(result) Ok(result)
} else { } else {
@ -259,6 +262,12 @@ impl NapiStruct {
} }
} }
} }
impl #name {
pub fn create_reference(&self) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::Reference<#name>> {
napi::bindgen_prelude::Reference::<#name>::from_typeid(std::any::TypeId::of::<#name>())
}
}
} }
} }

View file

@ -73,7 +73,7 @@ optional = true
version = "1" version = "1"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
windows = { version = "0.33", features = [ windows = { version = "0.34", features = [
"Win32_System_WindowsProgramming", "Win32_System_WindowsProgramming",
"Win32_System_LibraryLoader", "Win32_System_LibraryLoader",
"Win32_Foundation", "Win32_Foundation",

View file

@ -64,29 +64,34 @@ impl<const N: usize> CallbackInfo<N> {
self.this self.this
} }
pub fn construct<T>(&self, js_name: &str, obj: T) -> Result<sys::napi_value> { pub fn construct<T: 'static>(&self, js_name: &str, obj: T) -> Result<sys::napi_value> {
let obj = Box::new(obj); let obj = Box::new(obj);
let this = self.this(); let this = self.this();
let value_ref = Box::into_raw(obj) as *mut c_void;
let mut object_ref = ptr::null_mut();
unsafe { unsafe {
check_status!( check_status!(
sys::napi_wrap( sys::napi_wrap(
self.env, self.env,
this, this,
Box::into_raw(obj) as *mut std::ffi::c_void, value_ref,
Some(raw_finalize_unchecked::<T>), Some(raw_finalize_unchecked::<T>),
ptr::null_mut(), ptr::null_mut(),
&mut std::ptr::null_mut() &mut object_ref
), ),
"Failed to initialize class `{}`", "Failed to initialize class `{}`",
js_name, js_name,
)?; )?;
}; };
Reference::<T>::add_ref(
std::any::TypeId::of::<T>(),
(value_ref, self.env, object_ref),
);
Ok(this) Ok(this)
} }
pub fn factory<T>(&self, js_name: &str, obj: T) -> Result<sys::napi_value> { pub fn factory<T: 'static>(&self, js_name: &str, obj: T) -> Result<sys::napi_value> {
let obj = Box::new(obj); let obj = Box::new(obj);
let this = self.this(); let this = self.this();
let mut instance = ptr::null_mut(); let mut instance = ptr::null_mut();
@ -102,18 +107,25 @@ impl<const N: usize> CallbackInfo<N> {
return Ok(ptr::null_mut()); 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!( check_status!(
sys::napi_wrap( sys::napi_wrap(
self.env, self.env,
instance, instance,
Box::into_raw(obj) as *mut std::ffi::c_void, value_ref,
Some(raw_finalize_unchecked::<T>), Some(raw_finalize_unchecked::<T>),
ptr::null_mut(), ptr::null_mut(),
&mut std::ptr::null_mut() &mut object_ref
), ),
"Failed to initialize class `{}`", "Failed to initialize class `{}`",
js_name, js_name,
)?; )?;
Reference::<T>::add_ref(
std::any::TypeId::of::<T>(),
(value_ref, self.env, object_ref),
);
}; };
Ok(instance) Ok(instance)

View file

@ -24,6 +24,7 @@ mod serde;
mod string; mod string;
mod symbol; mod symbol;
mod task; mod task;
mod value_ref;
#[cfg(feature = "napi5")] #[cfg(feature = "napi5")]
pub use crate::JsDate as Date; pub use crate::JsDate as Date;
@ -43,6 +44,7 @@ pub use promise::*;
pub use string::*; pub use string::*;
pub use symbol::*; pub use symbol::*;
pub use task::*; pub use task::*;
pub use value_ref::*;
#[cfg(feature = "latin1")] #[cfg(feature = "latin1")]
pub use string::latin1_string::*; pub use string::latin1_string::*;

View file

@ -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<HashMap<TypeId, RefInformation>> = Default::default();
}
/// ### Experimental feature
///
/// Create a `reference` from `Class` instance.
/// Unref the `Reference` when the `Reference` is dropped.
pub struct Reference<T> {
raw: *mut T,
napi_ref: crate::sys::napi_ref,
env: crate::sys::napi_env,
}
unsafe impl<T: Send> Send for Reference<T> {}
unsafe impl<T: Sync> Sync for Reference<T> {}
impl<T> Drop for Reference<T> {
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<T> Reference<T> {
#[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<Self> {
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<T: 'static> Reference<T> {
pub fn share_with<S, F: FnOnce(&'static mut T) -> Result<S>>(
self,
f: F,
) -> Result<SharedReference<T, S>> {
let s = f(Box::leak(unsafe { Box::from_raw(self.raw) }))?;
Ok(SharedReference {
raw: Box::into_raw(Box::new(s)),
owner: self,
})
}
}
impl<T> Deref for Reference<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { Box::leak(Box::from_raw(self.raw)) }
}
}
impl<T> DerefMut for Reference<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { Box::leak(Box::from_raw(self.raw)) }
}
}
impl<T> Clone for Reference<T> {
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<T, S> {
raw: *mut S,
owner: Reference<T>,
}
unsafe impl<T: Send, S: Send> Send for SharedReference<T, S> {}
unsafe impl<T: Sync, S: Sync> Sync for SharedReference<T, S> {}
impl<T, S: 'static> SharedReference<T, S> {
pub fn share_with<U, F: FnOnce(&'static mut S) -> Result<U>>(
self,
f: F,
) -> Result<SharedReference<T, U>> {
let s = f(Box::leak(unsafe { Box::from_raw(self.raw) }))?;
Ok(SharedReference {
raw: Box::into_raw(Box::new(s)),
owner: self.owner,
})
}
}
impl<T, S> Deref for SharedReference<T, S> {
type Target = S;
fn deref(&self) -> &Self::Target {
unsafe { Box::leak(Box::from_raw(self.raw)) }
}
}
impl<T, S> DerefMut for SharedReference<T, S> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { Box::leak(Box::from_raw(self.raw)) }
}
}
impl<T, S> Clone for SharedReference<T, S> {
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(),
}
}
}

View file

@ -1,8 +1,5 @@
mod callback_info; use std::ffi::c_void;
mod env; use std::mem;
mod error;
mod js_values;
mod module_register;
pub use callback_info::*; pub use callback_info::*;
pub use ctor::ctor; pub use ctor::ctor;
@ -11,29 +8,22 @@ pub use js_values::*;
pub use module_register::*; pub use module_register::*;
use super::sys; use super::sys;
use std::{ffi::c_void, mem};
mod callback_info;
mod env;
mod error;
mod js_values;
mod module_register;
/// # Safety /// # Safety
/// ///
/// called when node wrapper objects destroyed /// called when node wrapper objects destroyed
pub unsafe extern "C" fn raw_finalize_unchecked<T>( pub unsafe extern "C" fn raw_finalize_unchecked<T>(
env: sys::napi_env, _env: sys::napi_env,
finalize_data: *mut c_void, 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(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<i64>) };
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"
);
};
}
} }
/// # Safety /// # Safety

View file

@ -260,6 +260,13 @@ Generated by [AVA](https://avajs.dev).
export class JsClassForEither {␊ export class JsClassForEither {␊
constructor()␊ constructor()␊
}␊ }␊
export class JsRepo {␊
constructor(dir: string)␊
remote(): JsRemote␊
}␊
export class JsRemote {␊
name(): string␊
}␊
export namespace xxh3 {␊ export namespace xxh3 {␊
export const ALIGNMENT: number␊ export const ALIGNMENT: number␊
export function xxh3_64(input: Buffer): bigint␊ export function xxh3_64(input: Buffer): bigint␊

View file

@ -87,6 +87,7 @@ import {
derefUint8Array, derefUint8Array,
chronoDateAdd1Minute, chronoDateAdd1Minute,
bufferPassThrough, bufferPassThrough,
JsRepo,
} from '../' } from '../'
test('export const', (t) => { test('export const', (t) => {
@ -189,6 +190,11 @@ test('class Factory return Result', (t) => {
t.is(c.method(), 'not empty') 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) => { test('callback', (t) => {
getCwd((cwd) => { getCwd((cwd) => {
t.is(cwd, process.cwd()) t.is(cwd, process.cwd())

View file

@ -250,6 +250,13 @@ export class ClassWithFactory {
export class JsClassForEither { export class JsClassForEither {
constructor() constructor()
} }
export class JsRepo {
constructor(dir: string)
remote(): JsRemote
}
export class JsRemote {
name(): string
}
export namespace xxh3 { export namespace xxh3 {
export const ALIGNMENT: number export const ALIGNMENT: number
export function xxh3_64(input: Buffer): bigint export function xxh3_64(input: Buffer): bigint

View file

@ -32,6 +32,7 @@ mod nullable;
mod number; mod number;
mod object; mod object;
mod promise; mod promise;
mod reference;
mod serde; mod serde;
mod string; mod string;
mod symbol; mod symbol;

View file

@ -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<JsRemote> {
Ok(JsRemote {
inner: self
.create_reference()?
.share_with(|repo| Ok(repo.inner.remote()))?,
})
}
}
#[napi]
pub struct JsRemote {
inner: SharedReference<JsRepo, Remote<'static>>,
}
#[napi]
impl JsRemote {
#[napi]
pub fn name(&self) -> String {
self.inner.name()
}
}