feat(napi): experimental class reference API
This commit is contained in:
parent
5994ef1b95
commit
5c3d1b2144
12 changed files with 286 additions and 31 deletions
|
@ -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>())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
163
crates/napi/src/bindgen_runtime/js_values/value_ref.rs
Normal file
163
crates/napi/src/bindgen_runtime/js_values/value_ref.rs
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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␊
|
||||||
|
|
Binary file not shown.
|
@ -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())
|
||||||
|
|
7
examples/napi/index.d.ts
vendored
7
examples/napi/index.d.ts
vendored
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
58
examples/napi/src/reference.rs
Normal file
58
examples/napi/src/reference.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue