Merge pull request #868 from napi-rs/external
feat(napi): implement external value
This commit is contained in:
commit
6cd91b23d7
12 changed files with 191 additions and 14 deletions
|
@ -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 |
|
||||||
|
|
|
@ -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>()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
110
crates/napi/src/bindgen_runtime/js_values/external.rs
Normal file
110
crates/napi/src/bindgen_runtime/js_values/external.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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␊
|
||||||
|
|
Binary file not shown.
|
@ -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)
|
||||||
|
|
8
examples/napi/index.d.ts
vendored
8
examples/napi/index.d.ts
vendored
|
@ -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
|
||||||
|
|
21
examples/napi/src/external.rs
Normal file
21
examples/napi/src/external.rs
Normal 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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue