init
This commit is contained in:
commit
c05ab719b3
21 changed files with 1481 additions and 0 deletions
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_size = 2
|
||||
indent_style = space
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
target/
|
||||
*.log
|
||||
.DS_Store
|
||||
Cargo.lock
|
||||
node_modules
|
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "napi-rs"
|
||||
version = "0.1.0"
|
||||
authors = ["Nathan Sobo <nathan@github.com>", "Yinan Long <lynweklm@gmail.com>"]
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
futures = "0.1.17"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.32.1"
|
||||
cc = "1.0"
|
||||
glob = "0.2.11"
|
58
README.md
Normal file
58
README.md
Normal file
|
@ -0,0 +1,58 @@
|
|||
# napi
|
||||
|
||||
> This project was initialized from [xray](https://github.com/atom/xray)
|
||||
|
||||
A minimal library for building compiled Node add-ons in Rust.
|
||||
|
||||
This library depends on N-API and requires Node 8.9 or later. It is still pretty raw and has not been tested in a production setting.
|
||||
|
||||
One nice feature is that this crate allows you to build add-ons purely with the Rust toolchain and without involving `node-gyp`.
|
||||
|
||||
## Building
|
||||
|
||||
This repository is a Cargo crate *and* an npm module. Any napi-based add-on should also contain *both* `Cargo.toml` to make it a Cargo crate and a `package.json` to make it an npm module.
|
||||
|
||||
In your `Cargo.toml` you need to set the `crate-type` to `"cdylib"` so that cargo builds a C-style shared library that can be dynamically loaded by the Node executable. You'll also want to add this crate as a dependency.
|
||||
|
||||
```
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
```
|
||||
|
||||
Building napi-based add-ons directly with `cargo build` isn't recommended, because you'll need to provide a `NODE_INCLUDE_PATH` pointing to the `include` directory for the version of Node you're targeting, as well as some special linker flags that can't be specified in the Cargo configuration.
|
||||
|
||||
Instead, you'll want to use the `napi` script, which will be installed automatically at `node_modules/.bin/napi` if you include `napi` as a dependency in your add-on's `package.json`. The napi script supports the following subcommands.
|
||||
|
||||
* `napi build [--debug]` Runs `cargo build` with a `NODE_INCLUDE_PATH` based on the path of the Node executable used to run the script and the required linker flags. The optional `--debug` flag will build in debug mode. After building, the script renames the dynamic library to have the `.node` extension to match the convention in the Node.js ecosystem.
|
||||
* `napi check` Runs `cargo check` with a `NODE_INCLUDE_PATH` based on the Node executable used to run the script.
|
||||
|
||||
The `napi` script will be available on the `PATH` of any scripts you define in the `scripts` section of your `package.json`, enabling a setup like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-add-on",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "napi build",
|
||||
"build-debug": "napi build --debug",
|
||||
"check": "napi check"
|
||||
},
|
||||
"dependencies": {
|
||||
"napi": "https://github.com/atom/napi"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
So far, the `napi` build script has only been tested on macOS. See the included `test_module` for an example add-on.
|
||||
|
||||
## Testing
|
||||
|
||||
Because libraries that depend on this crate must be loaded into a Node executable in order to resolve symbols, all tests are written in JavaScript in the `test_module` subdirectory.
|
||||
|
||||
To run tests:
|
||||
|
||||
```sh
|
||||
cd test_module
|
||||
npm run build
|
||||
npm test
|
||||
```
|
52
build.rs
Normal file
52
build.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
extern crate bindgen;
|
||||
extern crate cc;
|
||||
extern crate glob;
|
||||
|
||||
use glob::glob;
|
||||
use std::env;
|
||||
|
||||
fn expect_env(key: &str) -> String {
|
||||
let value = env::var(key);
|
||||
if value.is_err() {
|
||||
eprintln!("{} environment variable is not defined.", key);
|
||||
eprintln!("Make sure you're running cargo via the `napi` wrapper script to assign correct environment variables and options.");
|
||||
std::process::exit(1);
|
||||
};
|
||||
value.unwrap()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let node_include_path = expect_env("NODE_INCLUDE_PATH");
|
||||
let node_major_version = expect_env("NODE_MAJOR_VERSION");
|
||||
|
||||
println!("cargo:rerun-if-env-changed=NODE_INCLUDE_PATH");
|
||||
for entry in glob("./src/sys/**/*.*").unwrap() {
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
entry.unwrap().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
// Activate the "node8" or "node9" feature for compatibility with
|
||||
// different versions of Node.js/N-API.
|
||||
println!("cargo:rustc-cfg=node{}", node_major_version);
|
||||
|
||||
bindgen::Builder::default()
|
||||
.header("src/sys/bindings.h")
|
||||
.clang_arg(String::from("-I") + &node_include_path)
|
||||
.rustified_enum("(napi_|uv_).+")
|
||||
.whitelist_function("(napi_|uv_|extras_).+")
|
||||
.whitelist_type("(napi_|uv_|extras_).+")
|
||||
.generate()
|
||||
.expect("Unable to generate napi bindings")
|
||||
.write_to_file("src/sys/bindings.rs")
|
||||
.expect("Unable to write napi bindings");
|
||||
|
||||
cc::Build::new()
|
||||
.cpp(true)
|
||||
.include(&node_include_path)
|
||||
.file("src/sys/bindings.cc")
|
||||
.flag("-std=c++0x")
|
||||
.flag("-Wno-unused-parameter")
|
||||
.compile("extras");
|
||||
}
|
32
package.json
Normal file
32
package.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "napi-rs",
|
||||
"version": "0.1.0",
|
||||
"description":
|
||||
"A minimal library for building compiled Node add-ons in Rust.",
|
||||
"bin": {
|
||||
"napi": "scripts/napi.js"
|
||||
},
|
||||
"scripts": {
|
||||
"check": "scripts/napi.js check"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/atom/napi.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/atom/napi/issues"
|
||||
},
|
||||
"homepage": "https://github.com/atom/napi#readme",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 80,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"arrowParens": "always",
|
||||
"parser": "typescript"
|
||||
}
|
||||
}
|
81
scripts/napi.js
Executable file
81
scripts/napi.js
Executable file
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const parseArgs = require('minimist')
|
||||
const cp = require('child_process')
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
const parsedNodeVersion = process.versions.node.match(/^(\d+)\.(\d+)\.(\d+)$/)
|
||||
const nodeMajorVersion = parseInt(parsedNodeVersion[1])
|
||||
const nodeMinorVersion = parseInt(parsedNodeVersion[2])
|
||||
|
||||
if (nodeMajorVersion < 8 || (nodeMajorVersion === 8 && nodeMinorVersion < 9)) {
|
||||
console.error('This build script should be run on Node 8.9 or greater')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const argv = parseArgs(process.argv.slice(2), {
|
||||
boolean: ['release'],
|
||||
})
|
||||
|
||||
const subcommand = argv._[0] || 'build'
|
||||
|
||||
const nodeIncludePath = path.join(
|
||||
process.argv[0],
|
||||
'..',
|
||||
'..',
|
||||
'include',
|
||||
'node',
|
||||
)
|
||||
|
||||
const moduleName = path.basename(process.cwd()).replace('-', '_')
|
||||
process.env.NODE_INCLUDE_PATH = nodeIncludePath
|
||||
process.env.NODE_MAJOR_VERSION = nodeMajorVersion
|
||||
|
||||
const platform = os.platform()
|
||||
let libExt, platformArgs
|
||||
|
||||
// Platform based massaging for build commands
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
libExt = '.dylib'
|
||||
platformArgs = '-undefined dynamic_lookup -export_dynamic'
|
||||
break
|
||||
case 'win32':
|
||||
libExt = '.dll'
|
||||
platformArgs = '-undefined dynamic_lookup -export_dynamic'
|
||||
break
|
||||
case 'linux':
|
||||
libExt = '.so'
|
||||
platformArgs = '-undefined=dynamic_lookup -export_dynamic'
|
||||
break
|
||||
default:
|
||||
console.error(
|
||||
'Operating system not currently supported or recognized by the build script',
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
switch (subcommand) {
|
||||
case 'build':
|
||||
const releaseFlag = argv.release ? '--release' : ''
|
||||
const targetDir = argv.release ? 'release' : 'debug'
|
||||
cp.execSync(
|
||||
`cargo rustc ${releaseFlag} -- -Clink-args=\"${platformArgs}\"`,
|
||||
{ stdio: 'inherit' },
|
||||
)
|
||||
cp.execSync(`mkdir -p target/${targetDir}`)
|
||||
cp.execSync(
|
||||
`cp ${path.join(
|
||||
process.cwd(),
|
||||
'target',
|
||||
targetDir,
|
||||
'lib' + moduleName + libExt,
|
||||
)} target/${targetDir}/${moduleName}.node`,
|
||||
{ stdio: 'inherit' },
|
||||
)
|
||||
break
|
||||
case 'check':
|
||||
cp.execSync(`cargo check`, { stdio: 'inherit' })
|
||||
case 'doc':
|
||||
cp.execSync(`cargo doc`, { stdio: 'inherit' })
|
||||
}
|
125
src/executor.rs
Normal file
125
src/executor.rs
Normal file
|
@ -0,0 +1,125 @@
|
|||
use super::sys;
|
||||
use futures::executor::{self, Notify, Spawn};
|
||||
use futures::future::{ExecuteError, Executor};
|
||||
use futures::{Async, Future};
|
||||
use std::mem;
|
||||
use std::os::raw::c_void;
|
||||
use std::ptr;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
pub struct LibuvExecutor {
|
||||
event_loop: *mut sys::uv_loop_t,
|
||||
}
|
||||
|
||||
struct Task<T: 'static + Future> {
|
||||
spawn: Spawn<T>,
|
||||
notify_handle: Arc<TaskNotifyHandle>,
|
||||
}
|
||||
|
||||
struct TaskNotifyHandle(RwLock<Option<UvAsyncHandle>>);
|
||||
|
||||
struct UvAsyncHandle(Box<sys::uv_async_t>);
|
||||
|
||||
impl LibuvExecutor {
|
||||
pub fn new(event_loop: *mut sys::uv_loop_t) -> Self {
|
||||
Self { event_loop }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Executor<F> for LibuvExecutor
|
||||
where
|
||||
F: 'static + Future<Item = (), Error = ()>,
|
||||
{
|
||||
fn execute(&self, future: F) -> Result<(), ExecuteError<F>> {
|
||||
let spawn = executor::spawn(future);
|
||||
|
||||
unsafe {
|
||||
let mut task = Box::new(Task {
|
||||
spawn,
|
||||
notify_handle: mem::uninitialized(),
|
||||
});
|
||||
|
||||
ptr::write(
|
||||
&mut task.notify_handle,
|
||||
Arc::new(TaskNotifyHandle::new(
|
||||
self.event_loop,
|
||||
Some(poll_future_on_main_thread::<F>),
|
||||
mem::transmute_copy(&task),
|
||||
)),
|
||||
);
|
||||
|
||||
if !task.poll_future() {
|
||||
mem::forget(task)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + Future> Task<T> {
|
||||
fn poll_future(&mut self) -> bool {
|
||||
match self.spawn.poll_future_notify(&self.notify_handle, 0) {
|
||||
Ok(Async::Ready(_)) => {
|
||||
let mut handle = self.notify_handle.0.write().unwrap().take().unwrap();
|
||||
handle.close();
|
||||
true
|
||||
}
|
||||
Ok(Async::NotReady) => false,
|
||||
Err(_) => panic!("Future yielded an error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskNotifyHandle {
|
||||
fn new(event_loop: *mut sys::uv_loop_t, callback: sys::uv_async_cb, data: *mut c_void) -> Self {
|
||||
TaskNotifyHandle(RwLock::new(Some(UvAsyncHandle::new(
|
||||
event_loop, callback, data,
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
impl Notify for TaskNotifyHandle {
|
||||
fn notify(&self, _id: usize) {
|
||||
if let Some(ref uv_handle) = *self.0.read().unwrap() {
|
||||
unsafe {
|
||||
sys::uv_async_send(mem::transmute_copy(&uv_handle.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UvAsyncHandle {
|
||||
fn new(event_loop: *mut sys::uv_loop_t, callback: sys::uv_async_cb, data: *mut c_void) -> Self {
|
||||
unsafe {
|
||||
let mut handle = UvAsyncHandle(Box::new(mem::uninitialized()));
|
||||
let status = sys::uv_async_init(event_loop, mem::transmute_copy(&handle.0), callback);
|
||||
assert!(status == 0, "Non-zero status returned from uv_async_init");
|
||||
handle.0.data = data;
|
||||
handle
|
||||
}
|
||||
}
|
||||
|
||||
fn close(self) {
|
||||
unsafe {
|
||||
sys::uv_close(mem::transmute_copy(&self.0), Some(drop_handle_after_close));
|
||||
mem::forget(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for UvAsyncHandle {}
|
||||
unsafe impl Sync for UvAsyncHandle {}
|
||||
|
||||
extern "C" fn drop_handle_after_close(handle: *mut sys::uv_handle_t) {
|
||||
unsafe {
|
||||
Box::from_raw(handle);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn poll_future_on_main_thread<T: 'static + Future>(handle: *mut sys::uv_async_t) {
|
||||
let mut task: Box<Task<T>> = unsafe { Box::from_raw((*handle).data as *mut Task<T>) };
|
||||
if !task.poll_future() {
|
||||
mem::forget(task); // Don't drop task if it isn't complete.
|
||||
}
|
||||
}
|
820
src/lib.rs
Normal file
820
src/lib.rs
Normal file
|
@ -0,0 +1,820 @@
|
|||
pub extern crate futures;
|
||||
use std::any::TypeId;
|
||||
use std::ffi::CString;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::os::raw::{c_char, c_void};
|
||||
use std::ptr;
|
||||
use std::string::String as RustString;
|
||||
|
||||
mod executor;
|
||||
pub mod sys;
|
||||
|
||||
pub use sys::Status;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub type Callback = extern "C" fn(sys::napi_env, sys::napi_callback_info) -> sys::napi_value;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
status: Status,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Env(sys::napi_env);
|
||||
|
||||
// Value types
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Any;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Undefined;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Boolean;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Number;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct String;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Object;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Function;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Value<'env, T> {
|
||||
env: &'env Env,
|
||||
raw_value: sys::napi_value,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
pub struct Ref<T> {
|
||||
raw_env: sys::napi_env,
|
||||
raw_ref: sys::napi_ref,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
pub struct AsyncContext {
|
||||
raw_env: sys::napi_env,
|
||||
raw_context: sys::napi_async_context,
|
||||
raw_resource: sys::napi_ref,
|
||||
}
|
||||
|
||||
pub struct Deferred(sys::napi_deferred);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Property {
|
||||
name: RustString,
|
||||
raw_descriptor: sys::napi_property_descriptor,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct TaggedObject<T> {
|
||||
type_id: TypeId,
|
||||
object: Option<T>,
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! register_module {
|
||||
($module_name:ident, $init:ident) => {
|
||||
#[no_mangle]
|
||||
#[cfg_attr(target_os = "linux", link_section = ".ctors")]
|
||||
#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
|
||||
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
|
||||
pub static __REGISTER_MODULE: extern "C" fn() = {
|
||||
use std::io::Write;
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr;
|
||||
use $crate::sys;
|
||||
|
||||
extern "C" fn register_module() {
|
||||
static mut MODULE_DESCRIPTOR: Option<sys::napi_module> = None;
|
||||
unsafe {
|
||||
MODULE_DESCRIPTOR = Some(sys::napi_module {
|
||||
nm_version: 1,
|
||||
nm_flags: 0,
|
||||
nm_filename: concat!(file!(), "\0").as_ptr() as *const c_char,
|
||||
nm_register_func: Some(init_module),
|
||||
nm_modname: concat!(stringify!($module_name), "\0").as_ptr() as *const c_char,
|
||||
nm_priv: 0 as *mut _,
|
||||
reserved: [0 as *mut _; 4],
|
||||
});
|
||||
|
||||
sys::napi_module_register(MODULE_DESCRIPTOR.as_mut().unwrap() as *mut sys::napi_module);
|
||||
}
|
||||
|
||||
extern "C" fn init_module(
|
||||
raw_env: sys::napi_env,
|
||||
raw_exports: sys::napi_value,
|
||||
) -> sys::napi_value {
|
||||
let env = Env::from_raw(raw_env);
|
||||
let mut exports: Value<Object> = Value::from_raw(&env, raw_exports);
|
||||
|
||||
let result = $init(&env, &mut exports);
|
||||
|
||||
match result {
|
||||
Ok(Some(exports)) => exports.into_raw(),
|
||||
Ok(None) => ptr::null_mut(),
|
||||
Err(e) => {
|
||||
let _ = writeln!(::std::io::stderr(), "Error initializing module: {:?}", e);
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register_module
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! callback {
|
||||
($callback_expr:expr) => {{
|
||||
use std::io::Write;
|
||||
use std::mem;
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr;
|
||||
use $crate::sys;
|
||||
use $crate::{Any, Env, Status, Value};
|
||||
|
||||
extern "C" fn raw_callback(
|
||||
raw_env: sys::napi_env,
|
||||
cb_info: sys::napi_callback_info,
|
||||
) -> sys::napi_value {
|
||||
const MAX_ARGC: usize = 8;
|
||||
let mut argc = MAX_ARGC;
|
||||
let mut raw_args: [$crate::sys::napi_value; MAX_ARGC] = unsafe { mem::uninitialized() };
|
||||
let mut raw_this = ptr::null_mut();
|
||||
|
||||
unsafe {
|
||||
let status = sys::napi_get_cb_info(
|
||||
raw_env,
|
||||
cb_info,
|
||||
&mut argc,
|
||||
&mut raw_args[0],
|
||||
&mut raw_this,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
}
|
||||
|
||||
let env = Env::from_raw(raw_env);
|
||||
let this = Value::from_raw(&env, raw_this);
|
||||
let mut args: [Value<Any>; 8] = unsafe { mem::uninitialized() };
|
||||
for (i, raw_arg) in raw_args.into_iter().enumerate() {
|
||||
args[i] = Value::from_raw(&env, *raw_arg)
|
||||
}
|
||||
|
||||
let callback = $callback_expr;
|
||||
let result = callback(&env, this, &args[0..argc]);
|
||||
|
||||
match result {
|
||||
Ok(Some(result)) => result.into_raw(),
|
||||
Ok(None) => env.get_undefined().into_raw(),
|
||||
Err(e) => {
|
||||
let _ = writeln!(::std::io::stderr(), "Error calling function: {:?}", e);
|
||||
let message = format!("{:?}", e);
|
||||
unsafe {
|
||||
$crate::sys::napi_throw_error(raw_env, ptr::null(), message.as_ptr() as *const c_char);
|
||||
}
|
||||
env.get_undefined().into_raw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
raw_callback
|
||||
}};
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn new(status: Status) -> Self {
|
||||
Error { status: status }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::ffi::NulError> for Error {
|
||||
fn from(_error: std::ffi::NulError) -> Self {
|
||||
Error {
|
||||
status: Status::StringContainsNull,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Env {
|
||||
pub fn from_raw(env: sys::napi_env) -> Self {
|
||||
Env(env)
|
||||
}
|
||||
|
||||
pub fn get_undefined<'a>(&'a self) -> Value<'a, Undefined> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
let status = unsafe { sys::napi_get_undefined(self.0, &mut raw_value) };
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
Value::from_raw(self, raw_value)
|
||||
}
|
||||
|
||||
pub fn get_boolean(&self, value: bool) -> Value<Boolean> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
let status = unsafe { sys::napi_get_boolean(self.0, value, &mut raw_value) };
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
Value::from_raw(self, raw_value)
|
||||
}
|
||||
|
||||
pub fn create_int64<'a>(&'a self, int: i64) -> Value<'a, Number> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
let status =
|
||||
unsafe { sys::napi_create_int64(self.0, int, (&mut raw_value) as *mut sys::napi_value) };
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
Value::from_raw(self, raw_value)
|
||||
}
|
||||
|
||||
pub fn create_string<'a, 'b>(&'a self, s: &'b str) -> Value<'a, String> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
let status = unsafe {
|
||||
sys::napi_create_string_utf8(self.0, s.as_ptr() as *const c_char, s.len(), &mut raw_value)
|
||||
};
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
Value::from_raw(self, raw_value)
|
||||
}
|
||||
|
||||
pub fn create_string_utf16<'a, 'b>(&'a self, chars: &[u16]) -> Value<'a, String> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
let status =
|
||||
unsafe { sys::napi_create_string_utf16(self.0, chars.as_ptr(), chars.len(), &mut raw_value) };
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
Value::from_raw(self, raw_value)
|
||||
}
|
||||
|
||||
pub fn create_object<'a>(&'a self) -> Value<'a, Object> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
let status = unsafe { sys::napi_create_object(self.0, &mut raw_value) };
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
Value::from_raw(self, raw_value)
|
||||
}
|
||||
|
||||
pub fn create_array_with_length(&self, length: usize) -> Value<Object> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
let status = unsafe { sys::napi_create_array_with_length(self.0, length, &mut raw_value) };
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
Value::from_raw(self, raw_value)
|
||||
}
|
||||
|
||||
pub fn create_function<'a, 'b>(
|
||||
&'a self,
|
||||
name: &'b str,
|
||||
callback: Callback,
|
||||
) -> Value<'a, Function> {
|
||||
let mut raw_result = ptr::null_mut();
|
||||
let status = unsafe {
|
||||
sys::napi_create_function(
|
||||
self.0,
|
||||
name.as_ptr() as *const c_char,
|
||||
name.len(),
|
||||
Some(callback),
|
||||
callback as *mut c_void,
|
||||
&mut raw_result,
|
||||
)
|
||||
};
|
||||
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
|
||||
Value::from_raw(self, raw_result)
|
||||
}
|
||||
|
||||
pub fn create_reference<T>(&self, value: &Value<T>) -> Ref<T> {
|
||||
let mut raw_ref = ptr::null_mut();
|
||||
unsafe {
|
||||
let status = sys::napi_create_reference(self.0, value.raw_value, 1, &mut raw_ref);
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
};
|
||||
|
||||
Ref {
|
||||
raw_env: self.0,
|
||||
raw_ref,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_reference_value<T: ValueType>(&self, reference: &Ref<T>) -> Value<T> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
unsafe {
|
||||
let status = sys::napi_get_reference_value(self.0, reference.raw_ref, &mut raw_value);
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
};
|
||||
|
||||
Value::from_raw(self, raw_value)
|
||||
}
|
||||
|
||||
pub fn define_class<'a, 'b>(
|
||||
&'a self,
|
||||
name: &'b str,
|
||||
constructor_cb: Callback,
|
||||
properties: Vec<Property>,
|
||||
) -> Value<'a, Function> {
|
||||
let mut raw_result = ptr::null_mut();
|
||||
let raw_properties = properties
|
||||
.into_iter()
|
||||
.map(|prop| prop.into_raw(self))
|
||||
.collect::<Vec<sys::napi_property_descriptor>>();
|
||||
|
||||
let status = unsafe {
|
||||
sys::napi_define_class(
|
||||
self.0,
|
||||
name.as_ptr() as *const c_char,
|
||||
name.len(),
|
||||
Some(constructor_cb),
|
||||
ptr::null_mut(),
|
||||
raw_properties.len(),
|
||||
raw_properties.as_ptr(),
|
||||
&mut raw_result,
|
||||
)
|
||||
};
|
||||
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
|
||||
Value::from_raw(self, raw_result)
|
||||
}
|
||||
|
||||
pub fn wrap<T: 'static>(&self, js_object: &mut Value<Object>, native_object: T) -> Result<()> {
|
||||
let status = unsafe {
|
||||
sys::napi_wrap(
|
||||
self.0,
|
||||
js_object.raw_value,
|
||||
Box::into_raw(Box::new(TaggedObject::new(native_object))) as *mut c_void,
|
||||
Some(raw_finalize::<T>),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
check_status(status).or(Ok(()))
|
||||
}
|
||||
|
||||
pub fn unwrap<T: 'static>(&self, js_object: &Value<Object>) -> Result<&mut T> {
|
||||
unsafe {
|
||||
let mut unknown_tagged_object: *mut c_void = ptr::null_mut();
|
||||
let status = sys::napi_unwrap(self.0, js_object.raw_value, &mut unknown_tagged_object);
|
||||
check_status(status)?;
|
||||
|
||||
let type_id: *const TypeId = mem::transmute(unknown_tagged_object);
|
||||
if *type_id == TypeId::of::<T>() {
|
||||
let tagged_object: *mut TaggedObject<T> = mem::transmute(unknown_tagged_object);
|
||||
(*tagged_object).object.as_mut().ok_or(Error {
|
||||
status: Status::InvalidArg,
|
||||
})
|
||||
} else {
|
||||
Err(Error {
|
||||
status: Status::InvalidArg,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drop_wrapped<T: 'static>(&self, js_object: Value<Object>) -> Result<()> {
|
||||
unsafe {
|
||||
let mut unknown_tagged_object: *mut c_void = ptr::null_mut();
|
||||
let status = sys::napi_unwrap(self.0, js_object.raw_value, &mut unknown_tagged_object);
|
||||
check_status(status)?;
|
||||
|
||||
let type_id: *const TypeId = mem::transmute(unknown_tagged_object);
|
||||
if *type_id == TypeId::of::<T>() {
|
||||
let tagged_object: *mut TaggedObject<T> = mem::transmute(unknown_tagged_object);
|
||||
(*tagged_object).object = None;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error {
|
||||
status: Status::InvalidArg,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn async_init(&self, resource: Option<Value<Object>>, name: &str) -> AsyncContext {
|
||||
let raw_resource = resource
|
||||
.map(|r| r.into_raw())
|
||||
.unwrap_or_else(|| self.create_object().into_raw());
|
||||
let raw_name = self.create_string(name).into_raw();
|
||||
|
||||
let mut raw_context = ptr::null_mut();
|
||||
let mut raw_resource_ref = ptr::null_mut();
|
||||
unsafe {
|
||||
let status = sys::napi_async_init(self.0, raw_resource, raw_name, &mut raw_context);
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
|
||||
let status = sys::napi_create_reference(self.0, raw_resource, 1, &mut raw_resource_ref);
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
}
|
||||
|
||||
AsyncContext {
|
||||
raw_env: self.0,
|
||||
raw_resource: raw_resource_ref,
|
||||
raw_context,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_promise(&self) -> (Value<Object>, Deferred) {
|
||||
let mut raw_promise = ptr::null_mut();
|
||||
let mut raw_deferred = ptr::null_mut();
|
||||
|
||||
unsafe {
|
||||
sys::napi_create_promise(self.0, &mut raw_deferred, &mut raw_promise);
|
||||
}
|
||||
|
||||
(Value::from_raw(self, raw_promise), Deferred(raw_deferred))
|
||||
}
|
||||
|
||||
pub fn resolve_deferred<T: ValueType>(&self, deferred: Deferred, value: Value<T>) {
|
||||
unsafe {
|
||||
sys::napi_resolve_deferred(self.0, deferred.0, value.into_raw());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_executor(&self) -> executor::LibuvExecutor {
|
||||
let event_loop = unsafe { sys::uv_default_loop() };
|
||||
executor::LibuvExecutor::new(event_loop)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ValueType: Copy {
|
||||
fn matches_raw_type(raw_type: sys::napi_valuetype) -> bool;
|
||||
}
|
||||
|
||||
impl ValueType for Any {
|
||||
fn matches_raw_type(_raw_type: sys::napi_valuetype) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueType for Undefined {
|
||||
fn matches_raw_type(raw_type: sys::napi_valuetype) -> bool {
|
||||
raw_type == sys::napi_valuetype::napi_undefined
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueType for Boolean {
|
||||
fn matches_raw_type(raw_type: sys::napi_valuetype) -> bool {
|
||||
raw_type == sys::napi_valuetype::napi_boolean
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueType for Number {
|
||||
fn matches_raw_type(raw_type: sys::napi_valuetype) -> bool {
|
||||
raw_type == sys::napi_valuetype::napi_number
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueType for String {
|
||||
fn matches_raw_type(raw_type: sys::napi_valuetype) -> bool {
|
||||
raw_type == sys::napi_valuetype::napi_string
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueType for Object {
|
||||
fn matches_raw_type(raw_type: sys::napi_valuetype) -> bool {
|
||||
raw_type == sys::napi_valuetype::napi_object
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueType for Function {
|
||||
fn matches_raw_type(raw_type: sys::napi_valuetype) -> bool {
|
||||
raw_type == sys::napi_valuetype::napi_function
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, T: ValueType> Value<'env, T> {
|
||||
pub fn from_raw(env: &'env Env, raw_value: sys::napi_value) -> Self {
|
||||
Self {
|
||||
env,
|
||||
raw_value,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_raw(self) -> sys::napi_value {
|
||||
self.raw_value
|
||||
}
|
||||
|
||||
pub fn try_into<S: ValueType>(self) -> Result<Value<'env, S>> {
|
||||
unsafe {
|
||||
let mut value_type: sys::napi_valuetype = mem::uninitialized();
|
||||
let status = sys::napi_typeof(self.env.0, self.raw_value, &mut value_type);
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
if S::matches_raw_type(value_type) {
|
||||
Ok(mem::transmute(self))
|
||||
} else {
|
||||
Err(Error {
|
||||
status: Status::GenericFailure,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn coerce_to_number(self) -> Result<Value<'env, Number>> {
|
||||
let mut new_raw_value = ptr::null_mut();
|
||||
let status =
|
||||
unsafe { sys::napi_coerce_to_number(self.env.0, self.raw_value, &mut new_raw_value) };
|
||||
check_status(status)?;
|
||||
Ok(Value {
|
||||
env: self.env,
|
||||
raw_value: self.raw_value,
|
||||
_marker: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn coerce_to_string(self) -> Result<Value<'env, String>> {
|
||||
let mut new_raw_value = ptr::null_mut();
|
||||
let status =
|
||||
unsafe { sys::napi_coerce_to_string(self.env.0, self.raw_value, &mut new_raw_value) };
|
||||
check_status(status)?;
|
||||
Ok(Value {
|
||||
env: self.env,
|
||||
raw_value: self.raw_value,
|
||||
_marker: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn coerce_to_object(self) -> Result<Value<'env, Object>> {
|
||||
let mut new_raw_value = ptr::null_mut();
|
||||
let status = unsafe {
|
||||
sys::napi_coerce_to_object(
|
||||
self.env.0,
|
||||
self.raw_value,
|
||||
(&mut new_raw_value) as *mut sys::napi_value,
|
||||
)
|
||||
};
|
||||
check_status(status)?;
|
||||
Ok(Value {
|
||||
env: self.env,
|
||||
raw_value: self.raw_value,
|
||||
_marker: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env> Value<'env, String> {
|
||||
pub fn len(&self) -> usize {
|
||||
let mut raw_length = ptr::null_mut();
|
||||
unsafe {
|
||||
let status = sys::napi_get_named_property(
|
||||
self.env.0,
|
||||
self.raw_value,
|
||||
"length\0".as_ptr() as *const c_char,
|
||||
&mut raw_length,
|
||||
);
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
}
|
||||
let length: Value<Number> = Value::from_raw(self.env, raw_length);
|
||||
length.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env> Into<Vec<u16>> for Value<'env, String> {
|
||||
fn into(self) -> Vec<u16> {
|
||||
let mut result = Vec::with_capacity(self.len() + 1); // Leave room for trailing null byte
|
||||
|
||||
unsafe {
|
||||
let mut written_char_count = 0;
|
||||
let status = sys::napi_get_value_string_utf16(
|
||||
self.env.0,
|
||||
self.raw_value,
|
||||
result.as_mut_ptr(),
|
||||
result.capacity(),
|
||||
&mut written_char_count,
|
||||
);
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
result.set_len(written_char_count);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env> Into<usize> for Value<'env, Number> {
|
||||
fn into(self) -> usize {
|
||||
let mut result = 0;
|
||||
let status = unsafe { sys::napi_get_value_int64(self.env.0, self.raw_value, &mut result) };
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
result as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env> Into<i64> for Value<'env, Number> {
|
||||
fn into(self) -> i64 {
|
||||
let mut result = 0;
|
||||
let status = unsafe { sys::napi_get_value_int64(self.env.0, self.raw_value, &mut result) };
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env> Into<f64> for Value<'env, Number> {
|
||||
fn into(self) -> f64 {
|
||||
let mut result = 0_f64;
|
||||
let status = unsafe { sys::napi_get_value_double(self.env.0, self.raw_value, &mut result) };
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env> Value<'env, Object> {
|
||||
pub fn set_property<'a, K, V>(&mut self, key: Value<K>, value: Value<V>) -> Result<()> {
|
||||
let status = unsafe {
|
||||
sys::napi_set_property(
|
||||
self.raw_env(),
|
||||
self.raw_value(),
|
||||
key.raw_value,
|
||||
value.raw_value,
|
||||
)
|
||||
};
|
||||
check_status(status)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_named_property<'a, T, V: Into<Value<'a, T>>>(
|
||||
&mut self,
|
||||
name: &'a str,
|
||||
value: V,
|
||||
) -> Result<()> {
|
||||
let key = CString::new(name)?;
|
||||
let status = unsafe {
|
||||
sys::napi_set_named_property(
|
||||
self.raw_env(),
|
||||
self.raw_value(),
|
||||
key.as_ptr(),
|
||||
value.into().raw_value,
|
||||
)
|
||||
};
|
||||
check_status(status)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_named_property<T: ValueType>(&self, name: &str) -> Result<Value<T>> {
|
||||
let key = CString::new(name)?;
|
||||
let mut raw_value = ptr::null_mut();
|
||||
let status = unsafe {
|
||||
sys::napi_get_named_property(
|
||||
self.raw_env(),
|
||||
self.raw_value(),
|
||||
key.as_ptr(),
|
||||
&mut raw_value,
|
||||
)
|
||||
};
|
||||
check_status(status)?;
|
||||
Value::<Any>::from_raw(self.env, raw_value).try_into()
|
||||
}
|
||||
|
||||
pub fn set_index<'a, T>(&mut self, index: usize, value: Value<T>) -> Result<()> {
|
||||
self.set_property(self.env.create_int64(index as i64), value)
|
||||
}
|
||||
|
||||
fn raw_value(&self) -> sys::napi_value {
|
||||
self.raw_value
|
||||
}
|
||||
|
||||
fn raw_env(&self) -> sys::napi_env {
|
||||
self.env.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env> Value<'env, Function> {
|
||||
pub fn call(
|
||||
&self,
|
||||
this: Option<&Value<'env, Object>>,
|
||||
args: &[Value<'env, Any>],
|
||||
) -> Result<Value<'env, Any>> {
|
||||
let raw_this = this
|
||||
.map(|v| v.into_raw())
|
||||
.unwrap_or_else(|| self.env.get_undefined().into_raw());
|
||||
let mut raw_args: [sys::napi_value; 8] = unsafe { mem::uninitialized() };
|
||||
for (i, arg) in args.into_iter().enumerate() {
|
||||
raw_args[i] = arg.raw_value;
|
||||
}
|
||||
let mut return_value = ptr::null_mut();
|
||||
let status = unsafe {
|
||||
sys::napi_call_function(
|
||||
self.env.0,
|
||||
raw_this,
|
||||
self.raw_value,
|
||||
args.len(),
|
||||
&raw_args[0],
|
||||
&mut return_value,
|
||||
)
|
||||
};
|
||||
check_status(status)?;
|
||||
|
||||
Ok(Value::from_raw(self.env, return_value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Ref<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let mut ref_count = 0;
|
||||
let status = sys::napi_reference_unref(self.raw_env, self.raw_ref, &mut ref_count);
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
|
||||
if ref_count == 0 {
|
||||
let status = sys::napi_delete_reference(self.raw_env, self.raw_ref);
|
||||
debug_assert!(Status::from(status) == Status::Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncContext {
|
||||
pub fn enter<'a, F: 'a + FnOnce(&mut Env)>(&'a self, run_in_context: F) {
|
||||
let mut env = Env::from_raw(self.raw_env);
|
||||
let mut handle_scope = ptr::null_mut();
|
||||
let mut callback_scope = ptr::null_mut();
|
||||
let mut raw_resource = ptr::null_mut();
|
||||
|
||||
unsafe {
|
||||
sys::napi_open_handle_scope(env.0, &mut handle_scope);
|
||||
sys::napi_get_reference_value(env.0, self.raw_resource, &mut raw_resource);
|
||||
sys::extras_open_callback_scope(self.raw_context, raw_resource, &mut callback_scope);
|
||||
}
|
||||
run_in_context(&mut env);
|
||||
unsafe {
|
||||
sys::extras_close_callback_scope(callback_scope);
|
||||
sys::napi_close_handle_scope(env.0, handle_scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AsyncContext {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
sys::napi_delete_reference(self.raw_env, self.raw_resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Property {
|
||||
pub fn new(name: &str) -> Self {
|
||||
Property {
|
||||
name: RustString::from(name),
|
||||
raw_descriptor: sys::napi_property_descriptor {
|
||||
utf8name: ptr::null_mut(),
|
||||
name: ptr::null_mut(),
|
||||
method: None,
|
||||
getter: None,
|
||||
setter: None,
|
||||
value: ptr::null_mut(),
|
||||
attributes: sys::napi_property_attributes::napi_default,
|
||||
data: ptr::null_mut(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_value<T>(mut self, value: Value<T>) -> Self {
|
||||
self.raw_descriptor.value = value.raw_value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_method(mut self, callback: Callback) -> Self {
|
||||
self.raw_descriptor.method = Some(callback);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_getter(mut self, callback: Callback) -> Self {
|
||||
self.raw_descriptor.getter = Some(callback);
|
||||
self
|
||||
}
|
||||
|
||||
fn into_raw(mut self, env: &Env) -> sys::napi_property_descriptor {
|
||||
self.raw_descriptor.name = env.create_string(&self.name).into_raw();
|
||||
self.raw_descriptor
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> TaggedObject<T> {
|
||||
fn new(object: T) -> Self {
|
||||
TaggedObject {
|
||||
type_id: TypeId::of::<T>(),
|
||||
object: Some(object),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_status(code: sys::napi_status) -> Result<()> {
|
||||
let status = Status::from(code);
|
||||
match status {
|
||||
Status::Ok => Ok(()),
|
||||
_ => Err(Error { status }),
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn raw_finalize<T>(
|
||||
_raw_env: sys::napi_env,
|
||||
finalize_data: *mut c_void,
|
||||
_finalize_hint: *mut c_void,
|
||||
) {
|
||||
unsafe {
|
||||
let tagged_object: *mut TaggedObject<T> = mem::transmute(finalize_data);
|
||||
Box::from_raw(tagged_object);
|
||||
}
|
||||
}
|
27
src/sys/bindings.cc
Normal file
27
src/sys/bindings.cc
Normal file
|
@ -0,0 +1,27 @@
|
|||
#include "bindings.h"
|
||||
#include <stdio.h>
|
||||
#include <node.h>
|
||||
#include <v8.h>
|
||||
|
||||
static v8::Local<v8::Value> V8LocalValueFromJsValue(napi_value v)
|
||||
{
|
||||
v8::Local<v8::Value> local;
|
||||
memcpy(&local, &v, sizeof(v));
|
||||
return local;
|
||||
}
|
||||
|
||||
void extras_open_callback_scope(napi_async_context napi_async_context,
|
||||
napi_value napi_resource_object,
|
||||
extras_callback_scope *result)
|
||||
{
|
||||
v8::Isolate *isolate = v8::Isolate::GetCurrent();
|
||||
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
||||
v8::Local<v8::Object> resource_object = V8LocalValueFromJsValue(napi_resource_object)->ToObject(context).ToLocalChecked();
|
||||
node::async_context *node_async_context = reinterpret_cast<node::async_context *>(napi_async_context);
|
||||
*result = reinterpret_cast<extras_callback_scope>(new node::CallbackScope(isolate, resource_object, *node_async_context));
|
||||
}
|
||||
|
||||
void extras_close_callback_scope(extras_callback_scope callback_scope)
|
||||
{
|
||||
delete reinterpret_cast<node::CallbackScope *>(callback_scope);
|
||||
}
|
15
src/sys/bindings.h
Normal file
15
src/sys/bindings.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
#include <node_api.h>
|
||||
#include <uv.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct extras_callback_scope__ *extras_callback_scope;
|
||||
|
||||
EXTERN_C_START
|
||||
|
||||
NAPI_EXTERN void extras_open_callback_scope(napi_async_context napi_async_context,
|
||||
napi_value napi_resource_object,
|
||||
extras_callback_scope *result);
|
||||
|
||||
NAPI_EXTERN void extras_close_callback_scope(extras_callback_scope callback_scope);
|
||||
|
||||
EXTERN_C_END
|
3
src/sys/bindings.rs
Normal file
3
src/sys/bindings.rs
Normal file
File diff suppressed because one or more lines are too long
17
src/sys/mod.rs
Normal file
17
src/sys/mod.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(dead_code)]
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(expl_impl_clone_on_copy))]
|
||||
|
||||
include!("bindings.rs");
|
||||
|
||||
#[cfg(node8)]
|
||||
mod node8;
|
||||
#[cfg(node8)]
|
||||
pub use self::node8::Status;
|
||||
|
||||
#[cfg(node9)]
|
||||
mod node9;
|
||||
#[cfg(node9)]
|
||||
pub use self::node9::Status;
|
44
src/sys/node8.rs
Normal file
44
src/sys/node8.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use super::napi_status;
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub enum Status {
|
||||
Ok,
|
||||
InvalidArg,
|
||||
ObjectExpected,
|
||||
StringExpected,
|
||||
NameExpected,
|
||||
FunctionExpected,
|
||||
NumberExpected,
|
||||
BooleanExpected,
|
||||
ArrayExpected,
|
||||
GenericFailure,
|
||||
PendingException,
|
||||
Cancelled,
|
||||
EscapeCalledTwice,
|
||||
HandleScopeMismatch,
|
||||
StringContainsNull,
|
||||
}
|
||||
|
||||
impl From<napi_status> for Status {
|
||||
fn from(code: napi_status) -> Self {
|
||||
use self::napi_status::*;
|
||||
use Status::*;
|
||||
|
||||
match code {
|
||||
napi_ok => Ok,
|
||||
napi_invalid_arg => InvalidArg,
|
||||
napi_object_expected => ObjectExpected,
|
||||
napi_string_expected => StringExpected,
|
||||
napi_name_expected => NameExpected,
|
||||
napi_function_expected => FunctionExpected,
|
||||
napi_number_expected => NumberExpected,
|
||||
napi_boolean_expected => BooleanExpected,
|
||||
napi_array_expected => ArrayExpected,
|
||||
napi_generic_failure => GenericFailure,
|
||||
napi_pending_exception => PendingException,
|
||||
napi_cancelled => Cancelled,
|
||||
napi_escape_called_twice => EscapeCalledTwice,
|
||||
napi_handle_scope_mismatch => HandleScopeMismatch,
|
||||
}
|
||||
}
|
||||
}
|
46
src/sys/node9.rs
Normal file
46
src/sys/node9.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
use super::napi_status;
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub enum Status {
|
||||
Ok,
|
||||
InvalidArg,
|
||||
ObjectExpected,
|
||||
StringExpected,
|
||||
NameExpected,
|
||||
FunctionExpected,
|
||||
NumberExpected,
|
||||
BooleanExpected,
|
||||
ArrayExpected,
|
||||
GenericFailure,
|
||||
PendingException,
|
||||
Cancelled,
|
||||
EscapeCalledTwice,
|
||||
HandleScopeMismatch,
|
||||
CallbackScopeMismatch,
|
||||
StringContainsNull,
|
||||
}
|
||||
|
||||
impl From<napi_status> for Status {
|
||||
fn from(code: napi_status) -> Self {
|
||||
use self::napi_status::*;
|
||||
use Status::*;
|
||||
|
||||
match code {
|
||||
napi_ok => Ok,
|
||||
napi_invalid_arg => InvalidArg,
|
||||
napi_object_expected => ObjectExpected,
|
||||
napi_string_expected => StringExpected,
|
||||
napi_name_expected => NameExpected,
|
||||
napi_function_expected => FunctionExpected,
|
||||
napi_number_expected => NumberExpected,
|
||||
napi_boolean_expected => BooleanExpected,
|
||||
napi_array_expected => ArrayExpected,
|
||||
napi_generic_failure => GenericFailure,
|
||||
napi_pending_exception => PendingException,
|
||||
napi_cancelled => Cancelled,
|
||||
napi_escape_called_twice => EscapeCalledTwice,
|
||||
napi_handle_scope_mismatch => HandleScopeMismatch,
|
||||
napi_callback_scope_mismatch => CallbackScopeMismatch,
|
||||
}
|
||||
}
|
||||
}
|
10
test_module/Cargo.toml
Normal file
10
test_module/Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "test-module"
|
||||
version = "0.1.0"
|
||||
authors = ["Nathan Sobo <nathan@github.com>"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["dylib"]
|
||||
|
||||
[dependencies]
|
||||
napi-rs = {path = ".."}
|
13
test_module/package.json
Normal file
13
test_module/package.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "test-module",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "napi build",
|
||||
"build-release": "napi build --release",
|
||||
"check": "napi check",
|
||||
"test": "node ./tests.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"napi-rs": ".."
|
||||
}
|
||||
}
|
73
test_module/src/lib.rs
Normal file
73
test_module/src/lib.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
#[macro_use]
|
||||
extern crate napi_rs as napi;
|
||||
|
||||
use napi::{futures, Any, Env, Error, Object, Result, Status, Value};
|
||||
|
||||
register_module!(test_module, init);
|
||||
|
||||
fn init<'env>(
|
||||
env: &'env Env,
|
||||
exports: &'env mut Value<'env, Object>,
|
||||
) -> Result<Option<Value<'env, Object>>> {
|
||||
exports.set_named_property(
|
||||
"testSpawn",
|
||||
env.create_function("testSpawn", callback!(test_spawn)),
|
||||
)?;
|
||||
exports.set_named_property(
|
||||
"testThrow",
|
||||
env.create_function("testThrow", callback!(test_throw)),
|
||||
)?;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn test_spawn<'a>(
|
||||
env: &'a Env,
|
||||
_this: Value<'a, Any>,
|
||||
_args: &[Value<'a, Any>],
|
||||
) -> Result<Option<Value<'a, Any>>> {
|
||||
use futures::future::Executor;
|
||||
use futures::{Future, Stream};
|
||||
use std::{thread, time};
|
||||
|
||||
let async_context = env.async_init(None, "test_spawn");
|
||||
let (promise, deferred) = env.create_promise();
|
||||
let (tx, rx) = futures::sync::mpsc::unbounded();
|
||||
|
||||
let future = rx.for_each(|n: usize| {
|
||||
println!("Received value {:?}", n);
|
||||
futures::future::ok(())
|
||||
}).and_then(move |_| {
|
||||
async_context.enter(|env| {
|
||||
env.resolve_deferred(deferred, env.get_undefined());
|
||||
});
|
||||
futures::future::ok(())
|
||||
});
|
||||
|
||||
env.create_executor().execute(future).unwrap();
|
||||
|
||||
for _i in 0..10 {
|
||||
let thread_tx = tx.clone();
|
||||
thread::spawn(move || {
|
||||
let mut n = 0;
|
||||
loop {
|
||||
println!("send {:?}", n);
|
||||
thread_tx.unbounded_send(n).unwrap();
|
||||
n += 1;
|
||||
thread::sleep(time::Duration::from_millis(50));
|
||||
if n == 10 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Some(promise.try_into().unwrap()))
|
||||
}
|
||||
|
||||
fn test_throw<'a>(
|
||||
_env: &'a Env,
|
||||
_this: Value<'a, Any>,
|
||||
_args: &[Value<'a, Any>],
|
||||
) -> Result<Option<Value<'a, Any>>> {
|
||||
Err(Error::new(Status::GenericFailure))
|
||||
}
|
19
test_module/tests.js
Normal file
19
test_module/tests.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
const testModule = require('./target/debug/test_module')
|
||||
|
||||
function testSpawn() {
|
||||
console.log('=== Test spawning a future on libuv event loop')
|
||||
return testModule.testSpawn()
|
||||
}
|
||||
|
||||
function testThrow() {
|
||||
console.log('=== Test throwing from Rust')
|
||||
try {
|
||||
testModule.testThrow()
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
console.error('Expected function to throw an error')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
testSpawn().then(testThrow)
|
12
test_module/yarn.lock
Normal file
12
test_module/yarn.lock
Normal file
|
@ -0,0 +1,12 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
minimist@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
|
||||
napi-rs@..:
|
||||
version "0.1.0"
|
||||
dependencies:
|
||||
minimist "^1.2.0"
|
7
yarn.lock
Normal file
7
yarn.lock
Normal file
|
@ -0,0 +1,7 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
minimist@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
Loading…
Reference in a new issue