Merge pull request #868 from napi-rs/external

feat(napi): implement external value
This commit is contained in:
LongYinan 2021-11-21 16:45:31 +08:00 committed by GitHub
commit 6cd91b23d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 191 additions and 14 deletions

View file

@ -236,6 +236,7 @@ yarn test
| Array | Array<any> | 1 | v8.0.0 | | Array | Array<any> | 1 | v8.0.0 |
| Vec<T> | Array<T> | 1 | v8.0.0 | | Vec<T> | Array<T> | 1 | v8.0.0 |
| Buffer | Buffer | 1 | v8.0.0 | | Buffer | Buffer | 1 | v8.0.0 |
| External<T> | External<T> | 1 | v8.0.0 | |
| Null | null | 1 | v8.0.0 | | Null | null | 1 | v8.0.0 |
| Undefined/() | undefined | 1 | v8.0.0 | | Undefined/() | undefined | 1 | v8.0.0 |
| Result<()> | Error | 1 | v8.0.0 | | Result<()> | Error | 1 | v8.0.0 |

View file

@ -344,7 +344,10 @@ async function processIntermediateTypeFile(
.split('\n') .split('\n')
.map((line) => line.trim()) .map((line) => line.trim())
.filter(Boolean) .filter(Boolean)
let dts = '' let dts = `export class ExternalObject<T> {
private readonly __type: unique symbol;
[val: unique symbol]: T
}\n`
const classes = new Map<string, string>() const classes = new Map<string, string>()
const impls = new Map<string, string>() const impls = new Map<string, string>()

View file

@ -83,6 +83,7 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
("AbortSignal", "AbortSignal"), ("AbortSignal", "AbortSignal"),
("JsFunction", "(...args: any[]) => any"), ("JsFunction", "(...args: any[]) => any"),
("JsGlobal", "typeof global"), ("JsGlobal", "typeof global"),
("External", "ExternalObject<{}>"),
]); ]);
map map

View file

@ -8,6 +8,7 @@ mod bigint;
mod boolean; mod boolean;
mod buffer; mod buffer;
mod either; mod either;
mod external;
mod function; mod function;
mod map; mod map;
mod nil; mod nil;
@ -26,6 +27,7 @@ pub use array::*;
pub use bigint::*; pub use bigint::*;
pub use buffer::*; pub use buffer::*;
pub use either::*; pub use either::*;
pub use external::*;
#[cfg(feature = "napi4")] #[cfg(feature = "napi4")]
pub use function::*; pub use function::*;
pub use nil::*; pub use nil::*;

View file

@ -0,0 +1,110 @@
use std::any::TypeId;
use crate::{check_status, Error, Status, TaggedObject};
use super::{FromNapiValue, ToNapiValue};
pub struct External<T: 'static> {
obj: *mut TaggedObject<T>,
size_hint: usize,
pub adjusted_size: i64,
}
impl<T: 'static> External<T> {
pub fn new(value: T) -> Self {
Self {
obj: Box::into_raw(Box::new(TaggedObject::new(value))),
size_hint: 0,
adjusted_size: 0,
}
}
/// `size_hint` is a value to tell Node.js GC how much memory is used by this `External` object.
///
/// If getting the exact `size_hint` is difficult, you can provide an approximate value, it's only effect to the GC.
///
/// If your `External` object is not effect to GC, you can use `External::new` instead.
pub fn new_with_size_hint(value: T, size_hint: usize) -> Self {
Self {
obj: Box::into_raw(Box::new(TaggedObject::new(value))),
size_hint,
adjusted_size: 0,
}
}
}
impl<T: 'static> FromNapiValue for External<T> {
unsafe fn from_napi_value(
env: napi_sys::napi_env,
napi_val: napi_sys::napi_value,
) -> crate::Result<Self> {
let mut unknown_tagged_object = std::ptr::null_mut();
check_status!(
napi_sys::napi_get_value_external(env, napi_val, &mut unknown_tagged_object),
"Failed to get external value"
)?;
let type_id = unknown_tagged_object as *const TypeId;
if *type_id == TypeId::of::<T>() {
let tagged_object = unknown_tagged_object as *mut TaggedObject<T>;
Ok(Self {
obj: tagged_object,
size_hint: 0,
adjusted_size: 0,
})
} else {
Err(Error::new(
Status::InvalidArg,
"T on `get_value_external` is not the type of wrapped object".to_owned(),
))
}
}
}
impl<T: 'static> AsRef<T> for External<T> {
fn as_ref(&self) -> &T {
unsafe { Box::leak(Box::from_raw(self.obj)).object.as_ref().unwrap() }
}
}
impl<T: 'static> AsMut<T> for External<T> {
fn as_mut(&mut self) -> &mut T {
unsafe { Box::leak(Box::from_raw(self.obj)).object.as_mut().unwrap() }
}
}
impl<T: 'static> ToNapiValue for External<T> {
unsafe fn to_napi_value(
env: napi_sys::napi_env,
mut val: Self,
) -> crate::Result<napi_sys::napi_value> {
let mut napi_value = std::ptr::null_mut();
check_status!(
napi_sys::napi_create_external(
env,
val.obj as *mut _,
Some(crate::raw_finalize::<T>),
Box::into_raw(Box::new(Some(val.size_hint as i64))) as *mut _,
&mut napi_value
),
"Create external value failed"
)?;
let mut adjusted_external_memory_size = std::mem::MaybeUninit::new(0);
if val.size_hint != 0 {
check_status!(
napi_sys::napi_adjust_external_memory(
env,
val.size_hint as i64,
adjusted_external_memory_size.as_mut_ptr()
),
"Adjust external memory failed"
)?;
};
val.adjusted_size = adjusted_external_memory_size.assume_init();
Ok(napi_value)
}
}

View file

@ -879,10 +879,12 @@ impl Env {
) )
})?; })?;
if let Some(changed) = size_hint { if let Some(changed) = size_hint {
if changed != 0 {
let mut adjusted_value = 0i64; let mut adjusted_value = 0i64;
check_status!(unsafe { check_status!(unsafe {
sys::napi_adjust_external_memory(self.0, changed, &mut adjusted_value) sys::napi_adjust_external_memory(self.0, changed, &mut adjusted_value)
})?; })?;
}
}; };
Ok(unsafe { JsExternal::from_raw_unchecked(self.0, object_value) }) Ok(unsafe { JsExternal::from_raw_unchecked(self.0, object_value) })
} }
@ -1260,12 +1262,14 @@ pub(crate) unsafe extern "C" fn raw_finalize<T>(
if !finalize_hint.is_null() { if !finalize_hint.is_null() {
let size_hint = *Box::from_raw(finalize_hint as *mut Option<i64>); let size_hint = *Box::from_raw(finalize_hint as *mut Option<i64>);
if let Some(changed) = size_hint { if let Some(changed) = size_hint {
if changed != 0 {
let mut adjusted = 0i64; let mut adjusted = 0i64;
let status = sys::napi_adjust_external_memory(env, -changed, &mut adjusted); let status = sys::napi_adjust_external_memory(env, -changed, &mut adjusted);
debug_assert!( debug_assert!(
status == sys::Status::napi_ok, status == sys::Status::napi_ok,
"Calling napi_adjust_external_memory failed" "Calling napi_adjust_external_memory failed"
); );
}
}; };
} }
} }

View file

@ -8,7 +8,11 @@ Generated by [AVA](https://avajs.dev).
> Snapshot 1 > Snapshot 1
`export const DEFAULT_COST: number␊ `export class ExternalObject<T> {␊
private readonly __type: unique symbol;␊
[val: unique symbol]: T␊
}␊
export const DEFAULT_COST: number␊
export function getWords(): Array<string> export function getWords(): Array<string>
export function getNums(): Array<number> export function getNums(): Array<number>
export function sumNums(nums: Array<number>): number␊ export function sumNums(nums: Array<number>): number␊
@ -30,6 +34,10 @@ Generated by [AVA](https://avajs.dev).
export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }␊ export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }␊
export function enumToI32(e: CustomNumEnum): number␊ export function enumToI32(e: CustomNumEnum): number␊
export function throwError(): void␊ export function throwError(): void␊
export function createExternal(size: number): ExternalObject<number>
export function createExternalString(content: string): ExternalObject<string>
export function getExternal(external: ExternalObject<number>): number␊
export function mutateExternal(external: ExternalObject<number>, newVal: number): void␊
export function mapOption(val?: number | undefined | null): number | undefined | null␊ export function mapOption(val?: number | undefined | null): number | undefined | null␊
export function add(a: number, b: number): number␊ export function add(a: number, b: number): number␊
export function fibonacci(n: number): number␊ export function fibonacci(n: number): number␊

View file

@ -48,6 +48,10 @@ import {
setSymbolInObj, setSymbolInObj,
createSymbol, createSymbol,
threadsafeFunctionFatalMode, threadsafeFunctionFatalMode,
createExternal,
getExternal,
mutateExternal,
createExternalString,
} from '../' } from '../'
test('export const', (t) => { test('export const', (t) => {
@ -237,13 +241,27 @@ test('either4', (t) => {
t.is(either4({ v: 'world' }), 'world'.length) t.is(either4({ v: 'world' }), 'world'.length)
}) })
test('async task without abort controller', async (t) => { test('external', (t) => {
t.is(await withoutAbortController(1, 2), 3) const FX = 42
const ext = createExternal(FX)
t.is(getExternal(ext), FX)
mutateExternal(ext, FX + 1)
t.is(getExternal(ext), FX + 1)
// @ts-expect-error
t.throws(() => getExternal({}))
const ext2 = createExternalString('wtf')
// @ts-expect-error
const e = t.throws(() => getExternal(ext2))
t.is(e.message, 'T on `get_value_external` is not the type of wrapped object')
}) })
const AbortSignalTest = const AbortSignalTest =
typeof AbortController !== 'undefined' ? test : test.skip typeof AbortController !== 'undefined' ? test : test.skip
AbortSignalTest('async task without abort controller', async (t) => {
t.is(await withoutAbortController(1, 2), 3)
})
AbortSignalTest('async task with abort controller', async (t) => { AbortSignalTest('async task with abort controller', async (t) => {
const ctrl = new AbortController() const ctrl = new AbortController()
const promise = withAbortController(1, 2, ctrl.signal) const promise = withAbortController(1, 2, ctrl.signal)

View file

@ -1,3 +1,7 @@
export class ExternalObject<T> {
private readonly __type: unique symbol;
[val: unique symbol]: T
}
export const DEFAULT_COST: number export const DEFAULT_COST: number
export function getWords(): Array<string> export function getWords(): Array<string>
export function getNums(): Array<number> export function getNums(): Array<number>
@ -20,6 +24,10 @@ export enum Kind { Dog = 0, Cat = 1, Duck = 2 }
export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 } export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }
export function enumToI32(e: CustomNumEnum): number export function enumToI32(e: CustomNumEnum): number
export function throwError(): void export function throwError(): void
export function createExternal(size: number): ExternalObject<number>
export function createExternalString(content: string): ExternalObject<string>
export function getExternal(external: ExternalObject<number>): number
export function mutateExternal(external: ExternalObject<number>, newVal: number): void
export function mapOption(val?: number | undefined | null): number | undefined | null export function mapOption(val?: number | undefined | null): number | undefined | null
export function add(a: number, b: number): number export function add(a: number, b: number): number
export function fibonacci(n: number): number export function fibonacci(n: number): number

View file

@ -0,0 +1,21 @@
use napi::bindgen_prelude::*;
#[napi]
pub fn create_external(size: u32) -> External<u32> {
External::new(size)
}
#[napi]
pub fn create_external_string(content: String) -> External<String> {
External::new(content)
}
#[napi]
pub fn get_external(external: External<u32>) -> u32 {
*external.as_ref()
}
#[napi]
pub fn mutate_external(mut external: External<u32>, new_val: u32) {
*external.as_mut() = new_val;
}

View file

@ -15,6 +15,7 @@ mod class_factory;
mod either; mod either;
mod r#enum; mod r#enum;
mod error; mod error;
mod external;
mod nullable; mod nullable;
mod number; mod number;
mod object; mod object;